From 73e2fffc225db2e9d7151cc91a233abc2685e381 Mon Sep 17 00:00:00 2001 From: Matt Dainty Date: Mon, 1 Apr 2024 11:10:10 +0100 Subject: [PATCH] feat: Add RemoveWithoutEvict cache method --- expirable/expirable_lru.go | 26 +++++++++++++++++++------- lru.go | 9 +++++++++ lru_test.go | 32 ++++++++++++++++++++++++++++++++ simplelru/lru.go | 18 +++++++++++++----- simplelru/lru_interface.go | 3 +++ 5 files changed, 76 insertions(+), 12 deletions(-) diff --git a/expirable/expirable_lru.go b/expirable/expirable_lru.go index 8e70082..9aa2ff1 100644 --- a/expirable/expirable_lru.go +++ b/expirable/expirable_lru.go @@ -192,7 +192,19 @@ func (c *LRU[K, V]) Remove(key K) bool { c.mu.Lock() defer c.mu.Unlock() if ent, ok := c.items[key]; ok { - c.removeElement(ent) + c.removeElement(ent, true) + return true + } + return false +} + +// RemoveWithoutEvict removes the provided key from the cache without calling +// the eviction callback. +func (c *LRU[K, V]) RemoveWithoutEvict(key K) bool { + c.mu.Lock() + defer c.mu.Unlock() + if ent, ok := c.items[key]; ok { + c.removeElement(ent, false) return true } return false @@ -203,7 +215,7 @@ func (c *LRU[K, V]) RemoveOldest() (key K, value V, ok bool) { c.mu.Lock() defer c.mu.Unlock() if ent := c.evictList.Back(); ent != nil { - c.removeElement(ent) + c.removeElement(ent, true) return ent.Key, ent.Value, true } return @@ -292,16 +304,16 @@ func (c *LRU[K, V]) Resize(size int) (evicted int) { // removeOldest removes the oldest item from the cache. Has to be called with lock! func (c *LRU[K, V]) removeOldest() { if ent := c.evictList.Back(); ent != nil { - c.removeElement(ent) + c.removeElement(ent, true) } } // removeElement is used to remove a given list element from the cache. Has to be called with lock! -func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V]) { +func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V], cb bool) { c.evictList.Remove(e) delete(c.items, e.Key) c.removeFromBucket(e) - if c.onEvict != nil { + if cb && c.onEvict != nil { c.onEvict(e.Key, e.Value) } } @@ -319,7 +331,7 @@ func (c *LRU[K, V]) deleteExpired() { c.mu.Lock() } for _, ent := range c.buckets[bucketIdx].entries { - c.removeElement(ent) + c.removeElement(ent, true) } c.nextCleanupBucket = (c.nextCleanupBucket + 1) % numBuckets c.mu.Unlock() @@ -343,4 +355,4 @@ func (c *LRU[K, V]) removeFromBucket(e *internal.Entry[K, V]) { // Cap returns the capacity of the cache func (c *LRU[K, V]) Cap() int { return c.size -} \ No newline at end of file +} diff --git a/lru.go b/lru.go index 2bb07fd..13ace60 100644 --- a/lru.go +++ b/lru.go @@ -181,6 +181,15 @@ func (c *Cache[K, V]) Remove(key K) (present bool) { return } +// RemoveWithoutEvict removes the provided key from the cache without calling +// the eviction callback. +func (c *Cache[K, V]) RemoveWithoutEvict(key K) (present bool) { + c.lock.Lock() + present = c.lru.RemoveWithoutEvict(key) + c.lock.Unlock() + return +} + // Resize changes the cache size. func (c *Cache[K, V]) Resize(size int) (evicted int) { var ks []K diff --git a/lru_test.go b/lru_test.go index 7ecc7ae..a8532d7 100644 --- a/lru_test.go +++ b/lru_test.go @@ -444,3 +444,35 @@ func TestCache_EvictionSameKey(t *testing.T) { } }) } + +func TestLRURemoveWithoutEvict(t *testing.T) { + var ( + called bool + evictedKeys []int + ) + + cache, _ := NewWithEvict(2, func(key int, _ struct{}) { + called = true + evictedKeys = append(evictedKeys, key) + }) + + cache.Add(1, struct{}{}) + cache.Add(2, struct{}{}) + + cache.Remove(1) + if !called { + t.Error("eviction wasn't called") + } + + called = false + + cache.RemoveWithoutEvict(2) + if called { + t.Error("eviction was called") + } + + want := []int{1} + if !reflect.DeepEqual(evictedKeys, want) { + t.Errorf("evictedKeys got: %v want: %v", evictedKeys, want) + } +} diff --git a/simplelru/lru.go b/simplelru/lru.go index 8f45d2e..43969c8 100644 --- a/simplelru/lru.go +++ b/simplelru/lru.go @@ -97,7 +97,15 @@ func (c *LRU[K, V]) Peek(key K) (value V, ok bool) { // key was contained. func (c *LRU[K, V]) Remove(key K) (present bool) { if ent, ok := c.items[key]; ok { - c.removeElement(ent) + c.removeElement(ent, true) + return true + } + return false +} + +func (c *LRU[K, V]) RemoveWithoutEvict(key K) (present bool) { + if ent, ok := c.items[key]; ok { + c.removeElement(ent, false) return true } return false @@ -106,7 +114,7 @@ func (c *LRU[K, V]) Remove(key K) (present bool) { // RemoveOldest removes the oldest item from the cache. func (c *LRU[K, V]) RemoveOldest() (key K, value V, ok bool) { if ent := c.evictList.Back(); ent != nil { - c.removeElement(ent) + c.removeElement(ent, true) return ent.Key, ent.Value, true } return @@ -168,15 +176,15 @@ func (c *LRU[K, V]) Resize(size int) (evicted int) { // removeOldest removes the oldest item from the cache. func (c *LRU[K, V]) removeOldest() { if ent := c.evictList.Back(); ent != nil { - c.removeElement(ent) + c.removeElement(ent, true) } } // removeElement is used to remove a given list element from the cache -func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V]) { +func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V], cb bool) { c.evictList.Remove(e) delete(c.items, e.Key) - if c.onEvict != nil { + if cb && c.onEvict != nil { c.onEvict(e.Key, e.Value) } } diff --git a/simplelru/lru_interface.go b/simplelru/lru_interface.go index 495652b..9079a07 100644 --- a/simplelru/lru_interface.go +++ b/simplelru/lru_interface.go @@ -23,6 +23,9 @@ type LRUCache[K comparable, V any] interface { // Removes a key from the cache. Remove(key K) bool + // Removes a key from the cache without calling the eviction callback. + RemoveWithoutEvict(key K) bool + // Removes the oldest entry from cache. RemoveOldest() (K, V, bool)