diff options
author | 2023-08-03 10:34:35 +0100 | |
---|---|---|
committer | 2023-08-03 11:34:35 +0200 | |
commit | 00adf18c2470a69c255ea75990bbbae6e57eea89 (patch) | |
tree | d65408d4860b39f22f0aa853d25f57a37c65ee5c /vendor/codeberg.org/gruf/go-cache/v3/simple/cache.go | |
parent | [bugfix] Rework MultiError to wrap + unwrap errors properly (#2057) (diff) | |
download | gotosocial-00adf18c2470a69c255ea75990bbbae6e57eea89.tar.xz |
[feature] simpler cache size configuration (#2051)
* add automatic cache max size generation based on ratios of a singular fixed memory target
Signed-off-by: kim <grufwub@gmail.com>
* remove now-unused cache max-size config variables
Signed-off-by: kim <grufwub@gmail.com>
* slight ratio tweak
Signed-off-by: kim <grufwub@gmail.com>
* remove unused visibility config var
Signed-off-by: kim <grufwub@gmail.com>
* add secret little ratio config trick
Signed-off-by: kim <grufwub@gmail.com>
* fixed a word
Signed-off-by: kim <grufwub@gmail.com>
* update cache library to remove use of TTL in result caches + slice cache
Signed-off-by: kim <grufwub@gmail.com>
* update other cache usages to use correct interface
Signed-off-by: kim <grufwub@gmail.com>
* update example config to explain the cache memory target
Signed-off-by: kim <grufwub@gmail.com>
* update env parsing test with new config values
Signed-off-by: kim <grufwub@gmail.com>
* do some ratio twiddling
Signed-off-by: kim <grufwub@gmail.com>
* add missing header
* update envparsing with latest defaults
Signed-off-by: kim <grufwub@gmail.com>
* update size calculations to take into account result cache, simple cache and extra map overheads
Signed-off-by: kim <grufwub@gmail.com>
* tweak the ratios some more
Signed-off-by: kim <grufwub@gmail.com>
* more nan rampaging
Signed-off-by: kim <grufwub@gmail.com>
* fix envparsing script
Signed-off-by: kim <grufwub@gmail.com>
* update cache library, add sweep function to keep caches trim
Signed-off-by: kim <grufwub@gmail.com>
* sweep caches once a minute
Signed-off-by: kim <grufwub@gmail.com>
* add a regular job to sweep caches and keep under 80% utilisation
Signed-off-by: kim <grufwub@gmail.com>
* remove dead code
Signed-off-by: kim <grufwub@gmail.com>
* add new size library used to libraries section of readme
Signed-off-by: kim <grufwub@gmail.com>
* add better explanations for the mem-ratio numbers
Signed-off-by: kim <grufwub@gmail.com>
* update go-cache
Signed-off-by: kim <grufwub@gmail.com>
* library version bump
Signed-off-by: kim <grufwub@gmail.com>
* update cache.result{} size model estimation
Signed-off-by: kim <grufwub@gmail.com>
---------
Signed-off-by: kim <grufwub@gmail.com>
Diffstat (limited to 'vendor/codeberg.org/gruf/go-cache/v3/simple/cache.go')
-rw-r--r-- | vendor/codeberg.org/gruf/go-cache/v3/simple/cache.go | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/simple/cache.go b/vendor/codeberg.org/gruf/go-cache/v3/simple/cache.go new file mode 100644 index 000000000..0224871bc --- /dev/null +++ b/vendor/codeberg.org/gruf/go-cache/v3/simple/cache.go @@ -0,0 +1,454 @@ +package simple + +import ( + "sync" + + "codeberg.org/gruf/go-maps" +) + +// Entry represents an item in the cache. +type Entry struct { + Key any + Value any +} + +// Cache is the underlying Cache implementation, providing both the base Cache interface and unsafe access to underlying map to allow flexibility in building your own. +type Cache[Key comparable, Value any] struct { + // Evict is the hook that is called when an item is evicted from the cache. + Evict func(Key, Value) + + // Invalid is the hook that is called when an item's data in the cache is invalidated, includes Add/Set. + Invalid func(Key, Value) + + // Cache is the underlying hashmap used for this cache. + Cache maps.LRUMap[Key, *Entry] + + // Embedded mutex. + sync.Mutex +} + +// New returns a new initialized Cache with given initial length, maximum capacity and item TTL. +func New[K comparable, V any](len, cap int) *Cache[K, V] { + c := new(Cache[K, V]) + c.Init(len, cap) + return c +} + +// Init will initialize this cache with given initial length, maximum capacity and item TTL. +func (c *Cache[K, V]) Init(len, cap int) { + c.SetEvictionCallback(nil) + c.SetInvalidateCallback(nil) + c.Cache.Init(len, cap) +} + +// SetEvictionCallback: implements cache.Cache's SetEvictionCallback(). +func (c *Cache[K, V]) SetEvictionCallback(hook func(K, V)) { + c.locked(func() { c.Evict = hook }) +} + +// SetInvalidateCallback: implements cache.Cache's SetInvalidateCallback(). +func (c *Cache[K, V]) SetInvalidateCallback(hook func(K, V)) { + c.locked(func() { c.Invalid = hook }) +} + +// Get: implements cache.Cache's Get(). +func (c *Cache[K, V]) Get(key K) (V, bool) { + var ( + // did exist in cache? + ok bool + + // cached value. + v V + ) + + c.locked(func() { + var item *Entry + + // Check for item in cache + item, ok = c.Cache.Get(key) + if !ok { + return + } + + // Set item value. + v = item.Value.(V) + }) + + return v, ok +} + +// Add: implements cache.Cache's Add(). +func (c *Cache[K, V]) Add(key K, value V) bool { + var ( + // did exist in cache? + ok bool + + // was entry evicted? + ev bool + + // evicted key values. + evcK K + evcV V + + // hook func ptrs. + evict func(K, V) + ) + + c.locked(func() { + // Check if in cache. + ok = c.Cache.Has(key) + if ok { + return + } + + // Alloc new entry. + new := getEntry() + new.Key = key + new.Value = value + + // Add new entry to cache and catched any evicted item. + c.Cache.SetWithHook(key, new, func(_ K, item *Entry) { + evcK = item.Key.(K) + evcV = item.Value.(V) + ev = true + putEntry(item) + }) + + // Set hook func ptr. + evict = c.Evict + }) + + if ev && evict != nil { + // Pass to eviction hook. + evict(evcK, evcV) + } + + return !ok +} + +// Set: implements cache.Cache's Set(). +func (c *Cache[K, V]) Set(key K, value V) { + var ( + // did exist in cache? + ok bool + + // was entry evicted? + ev bool + + // old value. + oldV V + + // evicted key values. + evcK K + evcV V + + // hook func ptrs. + invalid func(K, V) + evict func(K, V) + ) + + c.locked(func() { + var item *Entry + + // Check for item in cache + item, ok = c.Cache.Get(key) + + if ok { + // Set old value. + oldV = item.Value.(V) + + // Update the existing item. + item.Value = value + } else { + // Alloc new entry. + new := getEntry() + new.Key = key + new.Value = value + + // Add new entry to cache and catched any evicted item. + c.Cache.SetWithHook(key, new, func(_ K, item *Entry) { + evcK = item.Key.(K) + evcV = item.Value.(V) + ev = true + putEntry(item) + }) + } + + // Set hook func ptrs. + invalid = c.Invalid + evict = c.Evict + }) + + if ok && invalid != nil { + // Pass to invalidate hook. + invalid(key, oldV) + } + + if ev && evict != nil { + // Pass to eviction hook. + evict(evcK, evcV) + } +} + +// CAS: implements cache.Cache's CAS(). +func (c *Cache[K, V]) CAS(key K, old V, new V, cmp func(V, V) bool) bool { + var ( + // did exist in cache? + ok bool + + // swapped value. + oldV V + + // hook func ptrs. + invalid func(K, V) + ) + + c.locked(func() { + var item *Entry + + // Check for item in cache + item, ok = c.Cache.Get(key) + if !ok { + return + } + + // Set old value. + oldV = item.Value.(V) + + // Perform the comparison + if !cmp(old, oldV) { + var zero V + oldV = zero + return + } + + // Update value. + item.Value = new + + // Set hook func ptr. + invalid = c.Invalid + }) + + if ok && invalid != nil { + // Pass to invalidate hook. + invalid(key, oldV) + } + + return ok +} + +// Swap: implements cache.Cache's Swap(). +func (c *Cache[K, V]) Swap(key K, swp V) V { + var ( + // did exist in cache? + ok bool + + // swapped value. + oldV V + + // hook func ptrs. + invalid func(K, V) + ) + + c.locked(func() { + var item *Entry + + // Check for item in cache + item, ok = c.Cache.Get(key) + if !ok { + return + } + + // Set old value. + oldV = item.Value.(V) + + // Update value. + item.Value = swp + + // Set hook func ptr. + invalid = c.Invalid + }) + + if ok && invalid != nil { + // Pass to invalidate hook. + invalid(key, oldV) + } + + return oldV +} + +// Has: implements cache.Cache's Has(). +func (c *Cache[K, V]) Has(key K) (ok bool) { + c.locked(func() { + ok = c.Cache.Has(key) + }) + return +} + +// Invalidate: implements cache.Cache's Invalidate(). +func (c *Cache[K, V]) Invalidate(key K) (ok bool) { + var ( + // old value. + oldV V + + // hook func ptrs. + invalid func(K, V) + ) + + c.locked(func() { + var item *Entry + + // Check for item in cache + item, ok = c.Cache.Get(key) + if !ok { + return + } + + // Set old value. + oldV = item.Value.(V) + + // Remove from cache map + _ = c.Cache.Delete(key) + + // Free entry + putEntry(item) + + // Set hook func ptrs. + invalid = c.Invalid + }) + + if ok && invalid != nil { + // Pass to invalidate hook. + invalid(key, oldV) + } + + return +} + +// InvalidateAll: implements cache.Cache's InvalidateAll(). +func (c *Cache[K, V]) InvalidateAll(keys ...K) (ok bool) { + var ( + // deleted items. + items []*Entry + + // hook func ptrs. + invalid func(K, V) + ) + + // Allocate a slice for invalidated. + items = make([]*Entry, 0, len(keys)) + + c.locked(func() { + for x := range keys { + var item *Entry + + // Check for item in cache + item, ok = c.Cache.Get(keys[x]) + if !ok { + continue + } + + // Append this old value. + items = append(items, item) + + // Remove from cache map + _ = c.Cache.Delete(keys[x]) + } + + // Set hook func ptrs. + invalid = c.Invalid + }) + + if invalid != nil { + for x := range items { + // Pass to invalidate hook. + k := items[x].Key.(K) + v := items[x].Value.(V) + invalid(k, v) + + // Free this entry. + putEntry(items[x]) + } + } + + return +} + +// Clear: implements cache.Cache's Clear(). +func (c *Cache[K, V]) Clear() { c.Trim(100) } + +// Trim will truncate the cache to ensure it stays within given percentage of total capacity. +func (c *Cache[K, V]) Trim(perc float64) { + var ( + // deleted items + items []*Entry + + // hook func ptrs. + invalid func(K, V) + ) + + c.locked(func() { + // Calculate number of cache items to truncate. + max := (perc / 100) * float64(c.Cache.Cap()) + diff := c.Cache.Len() - int(max) + if diff <= 0 { + return + } + + // Set hook func ptr. + invalid = c.Invalid + + // Truncate by calculated length. + items = c.truncate(diff, invalid) + }) + + if invalid != nil { + for x := range items { + // Pass to invalidate hook. + k := items[x].Key.(K) + v := items[x].Value.(V) + invalid(k, v) + + // Free this entry. + putEntry(items[x]) + } + } +} + +// Len: implements cache.Cache's Len(). +func (c *Cache[K, V]) Len() (l int) { + c.locked(func() { l = c.Cache.Len() }) + return +} + +// Cap: implements cache.Cache's Cap(). +func (c *Cache[K, V]) Cap() (l int) { + c.locked(func() { l = c.Cache.Cap() }) + return +} + +// locked performs given function within mutex lock (NOTE: UNLOCK IS NOT DEFERRED). +func (c *Cache[K, V]) locked(fn func()) { + c.Lock() + fn() + c.Unlock() +} + +// truncate will truncate the cache by given size, returning deleted items. +func (c *Cache[K, V]) truncate(sz int, hook func(K, V)) []*Entry { + if hook == nil { + // No hook to execute, simply release all truncated entries. + c.Cache.Truncate(sz, func(_ K, item *Entry) { putEntry(item) }) + return nil + } + + // Allocate a slice for deleted. + deleted := make([]*Entry, 0, sz) + + // Truncate and catch all deleted k-v pairs. + c.Cache.Truncate(sz, func(_ K, item *Entry) { + deleted = append(deleted, item) + }) + + return deleted +} |