From 8bd18665ccfb0ee44449b756d04d2a23fdfff1f3 Mon Sep 17 00:00:00 2001 From: kris-watts-gravwell Date: Mon, 22 Dec 2025 12:26:53 -0700 Subject: [PATCH] fixing infinite loop with large cache attempts on initial cache Sets --- adapter/memory/memory.go | 14 ++++++++++---- adapter/memory/memory_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/adapter/memory/memory.go b/adapter/memory/memory.go index 3458235..28fff38 100644 --- a/adapter/memory/memory.go +++ b/adapter/memory/memory.go @@ -80,6 +80,10 @@ func (a *Adapter) Set(key uint64, response []byte, expiration time.Time) { a.mutex.Lock() defer a.mutex.Unlock() + if len(response) > a.storage.max && a.storage.active() { + // response too large to cache + return + } if _, ok := a.store[key]; ok { // Known key, overwrite previous item. a.store[key] = response @@ -93,7 +97,9 @@ func (a *Adapter) Set(key uint64, response []byte, expiration time.Time) { // now evict based on storage for a.storage.shouldEvict(len(response)) { - a.evict() + if !a.evict() { + break // nothing was evicted, break to avoid infinite loop + } } a.store[key] = response @@ -120,7 +126,7 @@ func (a *Adapter) Release(key uint64) { // evict removes a single entry from the store. It assumes that the caller holds // the write lock. -func (a *Adapter) evict() { +func (a *Adapter) evict() (hit bool) { selectedKey := uint64(0) lastAccess := time.Now() frequency := 2147483647 @@ -132,7 +138,6 @@ func (a *Adapter) evict() { } var sz int - var hit bool for k, v := range a.store { r := cache.BytesToResponse(v) switch a.algorithm { @@ -166,8 +171,9 @@ func (a *Adapter) evict() { if hit { a.storage.del(sz) + delete(a.store, selectedKey) } - delete(a.store, selectedKey) + return } // NewAdapter initializes memory adapter. diff --git a/adapter/memory/memory_test.go b/adapter/memory/memory_test.go index 0cf628a..0d3d7d0 100644 --- a/adapter/memory/memory_test.go +++ b/adapter/memory/memory_test.go @@ -353,3 +353,32 @@ func TestStorageEvict(t *testing.T) { } } } + +func TestSetResponseLargerThanMaxCacheSize(t *testing.T) { + maxStorageSize := 100 + a := &Adapter{ + mutex: sync.RWMutex{}, + capacity: 10, + algorithm: LRU, + store: make(map[uint64][]byte), + storage: storageControl{ + max: maxStorageSize, + }, + } + + largeResponse := make([]byte, maxStorageSize+50) + for i := range largeResponse { + largeResponse[i] = 'X' + } + + key := uint64(12345) + a.Set(key, largeResponse, time.Now().Add(1*time.Minute)) + + if len(a.store) != 0 { + t.Errorf("expected cache to be empty after attempting to set response larger than max cache size, but got %d items", len(a.store)) + } + + if a.storage.cur != 0 { + t.Errorf("expected storage current size to be 0, but got %d", a.storage.cur) + } +}