diff --git a/common/arc/arc.go b/common/arc/arc.go index 8e62c90614..da78b1c1a0 100644 --- a/common/arc/arc.go +++ b/common/arc/arc.go @@ -20,16 +20,15 @@ func WithSize[K comparable, V any](maxSize int) Option[K, V] { } type ARC[K comparable, V any] struct { - p int - c int - t1 *list.List[*entry[K, V]] - b1 *list.List[*entry[K, V]] - t2 *list.List[*entry[K, V]] - b2 *list.List[*entry[K, V]] - mutex sync.Mutex - len int - cache map[K]*entry[K, V] - staleReturn bool + p int + c int + t1 *list.List[*entry[K, V]] + b1 *list.List[*entry[K, V]] + t2 *list.List[*entry[K, V]] + b2 *list.List[*entry[K, V]] + mutex sync.Mutex + len int + cache map[K]*entry[K, V] } // New returns a new Adaptive Replacement Cache (ARC). @@ -74,20 +73,22 @@ func (a *ARC[K, V]) SetWithExpire(key K, value V, expires time.Time) { func (a *ARC[K, V]) setWithExpire(key K, value V, expires time.Time) { ent, ok := a.cache[key] - if ok != true { + if !ok { a.len++ ent := &entry[K, V]{key: key, value: value, ghost: false, expires: expires.Unix()} a.req(ent) a.cache[key] = ent - } else { - if ent.ghost { - a.len++ - } - ent.value = value - ent.ghost = false - ent.expires = expires.Unix() - a.req(ent) + return + } + + if ent.ghost { + a.len++ } + + ent.value = value + ent.ghost = false + ent.expires = expires.Unix() + a.req(ent) } // Get retrieves a previously via Set inserted entry. @@ -97,25 +98,25 @@ func (a *ARC[K, V]) Get(key K) (value V, ok bool) { defer a.mutex.Unlock() ent, ok := a.get(key) - if ok { - return ent.value, true + if !ok { + return lo.Empty[V](), false } - return lo.Empty[V](), false + return ent.value, true } func (a *ARC[K, V]) get(key K) (e *entry[K, V], ok bool) { ent, ok := a.cache[key] - if ok { - a.req(ent) - return ent, !ent.ghost + if !ok { + return ent, false } - return ent, false + a.req(ent) + return ent, !ent.ghost } // GetWithExpire returns any representation of a cached response, // a time.Time Give expected expires, // and a bool set to true if the key was found. -// This method will NOT check the maxAge of element and will NOT update the expires. +// This method will NOT update the expires. func (a *ARC[K, V]) GetWithExpire(key K) (V, time.Time, bool) { a.mutex.Lock() defer a.mutex.Unlock() @@ -138,10 +139,11 @@ func (a *ARC[K, V]) Len() int { } func (a *ARC[K, V]) req(ent *entry[K, V]) { - if ent.ll == a.t1 || ent.ll == a.t2 { + switch { + case ent.ll == a.t1 || ent.ll == a.t2: // Case I ent.setMRU(a.t2) - } else if ent.ll == a.b1 { + case ent.ll == a.b1: // Case II // Cache Miss in t1 and t2 @@ -152,16 +154,11 @@ func (a *ARC[K, V]) req(ent *entry[K, V]) { } else { d = a.b2.Len() / a.b1.Len() } - - // a.p = min(a.p+d, a.c) - a.p = a.p + d - if a.c < a.p { - a.p = a.c - } + a.p = min(a.p+d, a.c) a.replace(ent) ent.setMRU(a.t2) - } else if ent.ll == a.b2 { + case ent.ll == a.b2: // Case III // Cache Miss in t1 and t2 @@ -172,35 +169,30 @@ func (a *ARC[K, V]) req(ent *entry[K, V]) { } else { d = a.b1.Len() / a.b2.Len() } - //a.p = max(a.p-d, 0) - a.p = a.p - d - if a.p < 0 { - a.p = 0 - } + a.p = max(a.p-d, 0) a.replace(ent) ent.setMRU(a.t2) - } else if ent.ll == nil { - // Case IV - - if a.t1.Len()+a.b1.Len() == a.c { - // Case A - if a.t1.Len() < a.c { - a.delLRU(a.b1) - a.replace(ent) - } else { - a.delLRU(a.t1) - } - } else if a.t1.Len()+a.b1.Len() < a.c { - // Case B - if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c { - if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c { - a.delLRU(a.b2) - } - a.replace(ent) + case ent.ll == nil && a.t1.Len()+a.b1.Len() == a.c: + // Case IV A + if a.t1.Len() < a.c { + a.delLRU(a.b1) + a.replace(ent) + } else { + a.delLRU(a.t1) + } + ent.setMRU(a.t1) + case ent.ll == nil && a.t1.Len()+a.b1.Len() < a.c: + // Case IV B + if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c { + if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c { + a.delLRU(a.b2) } + a.replace(ent) } - + ent.setMRU(a.t1) + case ent.ll == nil: + // Case IV, not A nor B ent.setMRU(a.t1) } } @@ -227,3 +219,17 @@ func (a *ARC[K, V]) replace(ent *entry[K, V]) { lru.setMRU(a.b2) } } + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a int, b int) int { + if a < b { + return b + } + return a +} diff --git a/common/arc/arc_test.go b/common/arc/arc_test.go index cb9835a339..a9d8a0c1bc 100644 --- a/common/arc/arc_test.go +++ b/common/arc/arc_test.go @@ -1,59 +1,105 @@ package arc -import "testing" +import ( + "testing" +) -func TestBasic(t *testing.T) { +func TestInsertion(t *testing.T) { cache := New[string, string](WithSize[string, string](3)) - if cache.Len() != 0 { - t.Error("Empty cache should have length 0") + if got, want := cache.Len(), 0; got != want { + t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want) } - cache.Set("Hello", "World") - if cache.Len() != 1 { - t.Error("Cache should have length 1") - } + const ( + k1 = "Hello" + k2 = "Hallo" + k3 = "Ciao" + k4 = "Salut" - var val interface{} - var ok bool + v1 = "World" + v2 = "Worlds" + v3 = "Welt" + ) - if val, ok = cache.Get("Hello"); val != "World" || ok != true { - t.Error("Didn't set \"Hello\" to \"World\"") + // Insert the first value + cache.Set(k1, v1) + if got, want := cache.Len(), 1; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + if got, ok := cache.Get(k1); !ok || got != v1 { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v1) } - cache.Set("Hello", "World1") - if cache.Len() != 1 { - t.Error("Inserting the same entry multiple times shouldn't increase cache size") + // Replace existing value for a given key + cache.Set(k1, v2) + if got, want := cache.Len(), 1; got != want { + t.Errorf("re-insertion: cache.Len(): got %d want %d", cache.Len(), want) + } + if got, ok := cache.Get(k1); !ok || got != v2 { + t.Errorf("re-insertion: cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2) } - if val, ok = cache.Get("Hello"); val != "World1" || ok != true { - t.Error("Didn't update \"Hello\" to \"World1\"") + // Add a second different key + cache.Set(k2, v3) + if got, want := cache.Len(), 2; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + if got, ok := cache.Get(k1); !ok || got != v2 { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2) + } + if got, ok := cache.Get(k2); !ok || got != v3 { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k2, got, ok, v3) } - cache.Set("Hallo", "Welt") - if cache.Len() != 2 { - t.Error("Inserting two different entries should result into lenght=2") + // Fill cache + cache.Set(k3, v1) + if got, want := cache.Len(), 3; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) } - if val, ok = cache.Get("Hallo"); val != "Welt" || ok != true { - t.Error("Didn't set \"Hallo\" to \"Welt\"") + // Exceed size, this should not exceed size: + cache.Set(k4, v1) + if got, want := cache.Len(), 3; got != want { + t.Errorf("insertion of key out of size: cache.Len(): got %d want %d", cache.Len(), want) } } -func TestBasicReplace(t *testing.T) { - cache := New[string, string](WithSize[string, string](3)) +func TestEviction(t *testing.T) { + size := 3 + cache := New[string, string](WithSize[string, string](size)) + if got, want := cache.Len(), 0; got != want { + t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want) + } - cache.Set("Hello", "Hallo") - cache.Set("World", "Welt") - cache.Get("World") - cache.Set("Cache", "Cache") - cache.Set("Replace", "Ersetzen") + tests := []struct { + k, v string + }{ + {"k1", "v1"}, + {"k2", "v2"}, + {"k3", "v3"}, + {"k4", "v4"}, + } + for i, tt := range tests[:size] { + cache.Set(tt.k, tt.v) + if got, want := cache.Len(), i+1; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + } + + // Exceed size and check we don't outgrow it: + cache.Set(tests[size].k, tests[size].v) + if got := cache.Len(); got != size { + t.Errorf("insertion of overflow key #%d: cache.Len(): got %d want %d", 4, cache.Len(), size) + } - value, ok := cache.Get("World") - if !ok || value != "Welt" { - t.Error("ARC should have replaced \"Hello\"") + // Check that LRU got evicted: + if got, ok := cache.Get(tests[0].k); ok || got != "" { + t.Errorf("cache.Get(%v): got (%v,%t) want (,true)", tests[0].k, got, ok) } - if cache.Len() != 3 { - t.Error("ARC should have a maximum size of 3") + for _, tt := range tests[1:] { + if got, ok := cache.Get(tt.k); !ok || got != tt.v { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", tt.k, got, ok, tt.v) + } } } diff --git a/common/lru/lrucache.go b/common/lru/lrucache.go index d23277c2c7..6f32ed18b1 100644 --- a/common/lru/lrucache.go +++ b/common/lru/lrucache.go @@ -80,7 +80,7 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] { return lc } -// Get returns the any representation of a cached response and a bool +// Get returns any representation of a cached response and a bool // set to true if the key was found. func (c *LruCache[K, V]) Get(key K) (V, bool) { c.mu.Lock() @@ -110,7 +110,7 @@ func (c *LruCache[K, V]) GetOrStore(key K, constructor func() V) (V, bool) { return value, true } -// GetWithExpire returns the any representation of a cached response, +// GetWithExpire returns any representation of a cached response, // a time.Time Give expected expires, // and a bool set to true if the key was found. // This method will NOT check the maxAge of element and will NOT update the expires. @@ -135,7 +135,7 @@ func (c *LruCache[K, V]) Exist(key K) bool { return ok } -// Set stores the any representation of a response for a given key. +// Set stores any representation of a response for a given key. func (c *LruCache[K, V]) Set(key K, value V) { c.mu.Lock() defer c.mu.Unlock() @@ -151,7 +151,7 @@ func (c *LruCache[K, V]) set(key K, value V) { c.setWithExpire(key, value, time.Unix(expires, 0)) } -// SetWithExpire stores the any representation of a response for a given key and given expires. +// SetWithExpire stores any representation of a response for a given key and given expires. // The expires time will round to second. func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) { c.mu.Lock()