diff options
Diffstat (limited to 'vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go')
-rw-r--r-- | vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go | 592 |
1 files changed, 390 insertions, 202 deletions
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go b/vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go index f0988fe6c..a5d9d6fc3 100644 --- a/vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go +++ b/vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go @@ -20,10 +20,10 @@ type Cache[Key comparable, Value any] struct { TTL time.Duration // Evict is the hook that is called when an item is evicted from the cache. - Evict func(*Entry[Key, Value]) + 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(*Entry[Key, Value]) + Invalid func(Key, Value) // Cache is the underlying hashmap used for this cache. Cache maps.LRUMap[Key, *Entry[Key, Value]] @@ -97,66 +97,66 @@ func (c *Cache[K, V]) Stop() (ok bool) { // Sweep attempts to evict expired items (with callback!) from cache. func (c *Cache[K, V]) Sweep(now time.Time) { - var after int + var ( + // evicted key-values. + kvs []kv[K, V] - // Sweep within lock - c.Lock() - defer c.Unlock() + // hook func ptrs. + evict func(K, V) + ) + + c.locked(func() { + // Sentinel value + after := -1 + + // The cache will be ordered by expiry date, we iterate until we reach the index of + // the youngest item that hsa expired, as all succeeding items will also be expired. + c.Cache.RangeIf(0, c.Cache.Len(), func(i int, _ K, item *Entry[K, V]) bool { + if now.After(item.Expiry) { + after = i - // Sentinel value - after = -1 + // evict all older items + // than this (inclusive) + return false + } - // The cache will be ordered by expiry date, we iterate until we reach the index of - // the youngest item that hsa expired, as all succeeding items will also be expired. - c.Cache.RangeIf(0, c.Cache.Len(), func(i int, _ K, item *Entry[K, V]) bool { - if now.After(item.Expiry) { - after = i + // cont. loop. + return true + }) - // All older than this (including) can be dropped - return false + if after == -1 { + // No Truncation needed + return } - // Continue looping - return true + // Set hook func ptr. + evict = c.Evict + + // Truncate determined size. + sz := c.Cache.Len() - after + kvs = c.truncate(sz, evict) }) - if after == -1 { - // No Truncation needed - return + if evict != nil { + for x := range kvs { + // Pass to eviction hook. + evict(kvs[x].K, kvs[x].V) + } } - - // Truncate items, calling eviction hook - c.truncate(c.Cache.Len()-after, c.Evict) } // SetEvictionCallback: implements cache.Cache's SetEvictionCallback(). -func (c *Cache[K, V]) SetEvictionCallback(hook func(*Entry[K, V])) { - // Ensure non-nil hook - if hook == nil { - hook = func(*Entry[K, V]) {} - } - - // Update within lock - c.Lock() - defer c.Unlock() - - // Update hook - c.Evict = hook +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(*Entry[K, V])) { - // Ensure non-nil hook - if hook == nil { - hook = func(*Entry[K, V]) {} - } - - // Update within lock - c.Lock() - defer c.Unlock() - - // Update hook - c.Invalid = hook +func (c *Cache[K, V]) SetInvalidateCallback(hook func(K, V)) { + c.locked(func() { + c.Invalid = hook + }) } // SetTTL: implements cache.Cache's SetTTL(). @@ -165,244 +165,417 @@ func (c *Cache[K, V]) SetTTL(ttl time.Duration, update bool) { panic("ttl must be greater than zero") } - // Update within lock - c.Lock() - defer c.Unlock() + c.locked(func() { + // Set updated TTL + diff := ttl - c.TTL + c.TTL = ttl - // Set updated TTL - diff := ttl - c.TTL - c.TTL = ttl - - if update { - // Update existing cache entries with new expiry time - c.Cache.Range(0, c.Cache.Len(), func(i int, _ K, item *Entry[K, V]) { - item.Expiry = item.Expiry.Add(diff) - }) - } + if update { + // Update existing cache entries with new expiry time + c.Cache.Range(0, c.Cache.Len(), func(i int, _ K, item *Entry[K, V]) { + item.Expiry = item.Expiry.Add(diff) + }) + } + }) } // Get: implements cache.Cache's Get(). func (c *Cache[K, V]) Get(key K) (V, bool) { - // Read within lock - c.Lock() - defer c.Unlock() + var ( + // did exist in cache? + ok bool - // Check for item in cache - item, ok := c.Cache.Get(key) - if !ok { - var value V - return value, false - } + // cached value. + v V + ) + + c.locked(func() { + var item *Entry[K, V] + + // Check for item in cache + item, ok = c.Cache.Get(key) + if !ok { + return + } + + // Update fetched item's expiry + item.Expiry = time.Now().Add(c.TTL) - // Update item expiry and return - item.Expiry = time.Now().Add(c.TTL) - return item.Value, true + // Set value. + v = item.Value + }) + + return v, ok } // Add: implements cache.Cache's Add(). func (c *Cache[K, V]) Add(key K, value V) bool { - // Write within lock - c.Lock() - defer c.Unlock() + var ( + // did exist in cache? + ok bool - // Check if already exists - item, ok := c.Cache.Get(key) - if ok { - return false - } + // was entry evicted? + ev bool - // Alloc new item - item = c.alloc() - item.Key = key - item.Value = value - item.Expiry = time.Now().Add(c.TTL) + // evicted key values. + evcK K + evcV V - var hook func(K, *Entry[K, V]) + // hook func ptrs. + evict func(K, V) + ) - if c.Evict != nil { - // Pass evicted entry to user hook - hook = func(_ K, item *Entry[K, V]) { - c.Evict(item) + c.locked(func() { + // Check if in cache. + ok = c.Cache.Has(key) + if ok { + return } - } - // Place new item in the map with hook - c.Cache.SetWithHook(key, item, hook) + // Alloc new entry. + new := c.alloc() + new.Expiry = time.Now().Add(c.TTL) + 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[K, V]) { + evcK = item.Key + evcV = item.Value + ev = true + c.free(item) + }) - if c.Invalid != nil { - // invalidate old - c.Invalid(item) + // Set hook func ptr. + evict = c.Evict + }) + + if ev && evict != nil { + // Pass to eviction hook. + evict(evcK, evcV) } - return true + return !ok } // Set: implements cache.Cache's Set(). func (c *Cache[K, V]) Set(key K, value V) { - // Write within lock - c.Lock() - defer c.Unlock() + var ( + // did exist in cache? + ok bool - // Check if already exists - item, ok := c.Cache.Get(key) + // was entry evicted? + ev bool - if !ok { - var hook func(K, *Entry[K, V]) + // old value. + oldV V - // Allocate new item - item = c.alloc() - item.Key = key + // evicted key values. + evcK K + evcV V - if c.Evict != nil { - // Pass evicted entry to user hook - hook = func(_ K, item *Entry[K, V]) { - c.Evict(item) - } + // hook func ptrs. + invalid func(K, V) + evict func(K, V) + ) + + c.locked(func() { + var item *Entry[K, V] + + // Check for item in cache + item, ok = c.Cache.Get(key) + + if ok { + // Set old value. + oldV = item.Value + + // Update the existing item. + item.Expiry = time.Now().Add(c.TTL) + item.Value = value + } else { + // Alloc new entry. + new := c.alloc() + new.Expiry = time.Now().Add(c.TTL) + 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[K, V]) { + evcK = item.Key + evcV = item.Value + ev = true + c.free(item) + }) } - // Place new item in the map with hook - c.Cache.SetWithHook(key, item, hook) - } + // Set hook func ptrs. + invalid = c.Invalid + evict = c.Evict + }) - if c.Invalid != nil { - // invalidate old - c.Invalid(item) + if ok && invalid != nil { + // Pass to invalidate hook. + invalid(key, oldV) } - // Update the item value + expiry - item.Expiry = time.Now().Add(c.TTL) - item.Value = value + 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 { - // CAS within lock - c.Lock() - defer c.Unlock() + var ( + // did exist in cache? + ok bool - // Check for item in cache - item, ok := c.Cache.Get(key) - if !ok || !cmp(item.Value, old) { - return false - } + // swapped value. + oldV V - if c.Invalid != nil { - // invalidate old - c.Invalid(item) - } + // hook func ptrs. + invalid func(K, V) + ) + + c.locked(func() { + var item *Entry[K, V] + + // Check for item in cache + item, ok = c.Cache.Get(key) + if !ok { + return + } - // Update item + Expiry - item.Value = new - item.Expiry = time.Now().Add(c.TTL) + // Perform the comparison + if !cmp(old, item.Value) { + return + } + + // Set old value. + oldV = item.Value + + // Update value + expiry. + item.Expiry = time.Now().Add(c.TTL) + 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 { - // Swap within lock - c.Lock() - defer c.Unlock() + var ( + // did exist in cache? + ok bool - // Check for item in cache - item, ok := c.Cache.Get(key) - if !ok { - var value V - return value - } + // swapped value. + oldV V - if c.Invalid != nil { - // invalidate old - c.Invalid(item) - } + // hook func ptrs. + invalid func(K, V) + ) + + c.locked(func() { + var item *Entry[K, V] + + // Check for item in cache + item, ok = c.Cache.Get(key) + if !ok { + return + } + + // Set old value. + oldV = item.Value - old := item.Value + // Update value + expiry. + item.Expiry = time.Now().Add(c.TTL) + item.Value = swp - // update item + Expiry - item.Value = swp - item.Expiry = time.Now().Add(c.TTL) + // Set hook func ptr. + invalid = c.Invalid + }) + + if ok && invalid != nil { + // Pass to invalidate hook. + invalid(key, oldV) + } - return old + return oldV } // Has: implements cache.Cache's Has(). -func (c *Cache[K, V]) Has(key K) bool { - c.Lock() - ok := c.Cache.Has(key) - c.Unlock() - return ok +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) bool { - // Delete within lock - c.Lock() - defer c.Unlock() +func (c *Cache[K, V]) Invalidate(key K) (ok bool) { + var ( + // old value. + oldV V - // Check if we have item with key - item, ok := c.Cache.Get(key) - if !ok { - return false - } + // hook func ptrs. + invalid func(K, V) + ) + + c.locked(func() { + var item *Entry[K, V] + + // Check for item in cache + item, ok = c.Cache.Get(key) + if !ok { + return + } + + // Set old value. + oldV = item.Value - // Remove from cache map - _ = c.Cache.Delete(key) + // Remove from cache map + _ = c.Cache.Delete(key) + + // Free entry + c.free(item) + + // Set hook func ptrs. + invalid = c.Invalid + }) - if c.Invalid != nil { - // Invalidate item - c.Invalid(item) + if ok && invalid != nil { + // Pass to invalidate hook. + invalid(key, oldV) } - // Return item to pool - c.free(item) + return +} - return true +// InvalidateAll: implements cache.Cache's InvalidateAll(). +func (c *Cache[K, V]) InvalidateAll(keys ...K) (ok bool) { + var ( + // invalidated kvs. + kvs []kv[K, V] + + // hook func ptrs. + invalid func(K, V) + ) + + // Allocate a slice for invalidated. + kvs = make([]kv[K, V], 0, len(keys)) + + c.locked(func() { + for _, key := range keys { + var item *Entry[K, V] + + // Check for item in cache + item, ok = c.Cache.Get(key) + if !ok { + return + } + + // Append this old value to slice + kvs = append(kvs, kv[K, V]{ + K: key, + V: item.Value, + }) + + // Remove from cache map + _ = c.Cache.Delete(key) + + // Free entry + c.free(item) + } + + // Set hook func ptrs. + invalid = c.Invalid + }) + + if invalid != nil { + for x := range kvs { + // Pass to invalidate hook. + invalid(kvs[x].K, kvs[x].V) + } + } + + return } // Clear: implements cache.Cache's Clear(). func (c *Cache[K, V]) Clear() { - c.Lock() - defer c.Unlock() - c.truncate(c.Cache.Len(), c.Invalid) + var ( + // deleted key-values. + kvs []kv[K, V] + + // hook func ptrs. + invalid func(K, V) + ) + + c.locked(func() { + // Set hook func ptr. + invalid = c.Invalid + + // Truncate the entire cache length. + kvs = c.truncate(c.Cache.Len(), invalid) + }) + + if invalid != nil { + for x := range kvs { + // Pass to invalidate hook. + invalid(kvs[x].K, kvs[x].V) + } + } } // Len: implements cache.Cache's Len(). -func (c *Cache[K, V]) Len() int { - c.Lock() - l := c.Cache.Len() - c.Unlock() - return l +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() int { +func (c *Cache[K, V]) Cap() (l int) { + c.locked(func() { l = c.Cache.Cap() }) + return +} + +func (c *Cache[K, V]) locked(fn func()) { c.Lock() - l := c.Cache.Cap() + fn() c.Unlock() - return l } -// truncate will call Cache.Truncate(sz), and if provided a hook will temporarily store deleted items before passing them to the hook. This is required in order to prevent cache writes during .Truncate(). -func (c *Cache[K, V]) truncate(sz int, hook func(*Entry[K, V])) { +// truncate will truncate the cache by given size, returning deleted items. +func (c *Cache[K, V]) truncate(sz int, hook func(K, V)) []kv[K, V] { if hook == nil { - // No hook was provided, we can simply truncate and free items immediately. - c.Cache.Truncate(sz, func(_ K, item *Entry[K, V]) { c.free(item) }) - return + // No hook to execute, simply free all truncated entries. + c.Cache.Truncate(sz, func(_ K, e *Entry[K, V]) { c.free(e) }) + return nil } - // Store list of deleted items for later callbacks - deleted := make([]*Entry[K, V], 0, sz) + // Allocate a slice for deleted k-v pairs. + deleted := make([]kv[K, V], 0, sz) - // Truncate and store list of deleted items c.Cache.Truncate(sz, func(_ K, item *Entry[K, V]) { - deleted = append(deleted, item) - }) + // Store key-value pair for later access. + deleted = append(deleted, kv[K, V]{ + K: item.Key, + V: item.Value, + }) - // Pass each deleted to hook, then free - for _, item := range deleted { - hook(item) + // Free entry. c.free(item) - } + }) + + return deleted } // alloc will acquire cache entry from pool, or allocate new. @@ -416,14 +589,29 @@ func (c *Cache[K, V]) alloc() *Entry[K, V] { return e } +// clone allocates a new Entry and copies all info from passed Entry. +func (c *Cache[K, V]) clone(e *Entry[K, V]) *Entry[K, V] { + e2 := c.alloc() + e2.Key = e.Key + e2.Value = e.Value + e2.Expiry = e.Expiry + return e2 +} + // free will reset entry fields and place back in pool. func (c *Cache[K, V]) free(e *Entry[K, V]) { var ( zk K zv V + zt time.Time ) + e.Expiry = zt e.Key = zk e.Value = zv - e.Expiry = time.Time{} c.pool = append(c.pool, e) } + +type kv[K comparable, V any] struct { + K K + V V +} |