VictoriaMetrics/lib/bytesutil/fast_string_transformer.go
Aliaksandr Valialkin ed324aad66
lib/bytesutil: make sure that the string passed to FastStringMather.Match() is copied before using it as a key in the internal cache map
This prevents from possible corruption of the internal cache map
when the underlying byte slice used by the string key is modified.

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3227
2022-10-14 09:52:18 +03:00

60 lines
1.7 KiB
Go

package bytesutil
import (
"strings"
"sync"
"sync/atomic"
)
// FastStringTransformer implements fast transformer for strings.
//
// It caches transformed strings and returns them back on the next calls
// without calling the transformFunc, which may be expensive.
type FastStringTransformer struct {
m atomic.Value
mLen uint64
transformFunc func(s string) string
}
// NewFastStringTransformer creates new transformer, which applies transformFunc to strings passed to Transform()
//
// transformFunc must return the same result for the same input.
func NewFastStringTransformer(transformFunc func(s string) string) *FastStringTransformer {
var fst FastStringTransformer
fst.m.Store(&sync.Map{})
fst.transformFunc = transformFunc
return &fst
}
// Transform applies transformFunc to s and returns the result.
func (fst *FastStringTransformer) Transform(s string) string {
m := fst.m.Load().(*sync.Map)
v, ok := m.Load(s)
if ok {
// Fast path - the transformed s is found in the cache.
sp := v.(*string)
return *sp
}
// Slow path - transform s and store it in the cache.
sTransformed := fst.transformFunc(s)
// Make a copy of s in order to limit memory usage to the s length,
// since the s may point to bigger string.
// This also protects from the case when s contains unsafe string, which points to a temporary byte slice.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3227
s = strings.Clone(s)
if sTransformed == s {
// point sTransformed to just allocated s, since it may point to s,
// which, in turn, can point to bigger string.
sTransformed = s
}
sp := &sTransformed
m.Store(s, sp)
n := atomic.AddUint64(&fst.mLen, 1)
if n > 100e3 {
atomic.StoreUint64(&fst.mLen, 0)
fst.m.Store(&sync.Map{})
}
return sTransformed
}