mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-01 14:47:38 +00:00
2e3be68617
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
59 lines
1.7 KiB
Go
59 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
|
|
}
|