From 5fcff7f8306aca6145825a1ea4bbd2665fda1edb Mon Sep 17 00:00:00 2001 From: Nithin Benny Date: Thu, 26 Sep 2024 11:31:44 +0530 Subject: [PATCH] Add background refresh functionality --- cache.go | 26 +++++++++++++++++++++++--- cache_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/cache.go b/cache.go index b5c96da..8539acd 100644 --- a/cache.go +++ b/cache.go @@ -79,6 +79,12 @@ type Config struct { OnEviction func(key, value interface{}) // Optional callback invoked when an item expired OnExpiration func(key, value interface{}) + // Optional refresh interval after which all items in the cache expires. + // If zero, refreshing cache is disabled. + RefreshInterval time.Duration + // Optional on refresh callback invoked when the cache is refreshed + // Both RefreshInterval and OnRefresh must be provided to enable background cache refresh + OnRefresh func() map[interface{}]interface{} } // Entry pointed to by each list.Element @@ -133,6 +139,10 @@ func New(config Config) *Cache { panic("config.MinAge must be less than or equal to config.MaxAge") } + if config.RefreshInterval < 0 { + panic("Must supply a zero or positive config.RefreshInterval") + } + minAge := config.MinAge if minAge == 0 { minAge = config.MaxAge @@ -166,6 +176,18 @@ func New(config Config) *Cache { }() } + if config.RefreshInterval > 0 && config.OnRefresh != nil { + cache.RefreshCache(config.OnRefresh()) + go func() { + t := time.NewTicker(config.RefreshInterval) + defer t.Stop() + for { + <-t.C + cache.RefreshCache(config.OnRefresh()) + } + }() + } + return cache } @@ -232,9 +254,7 @@ func (cache *Cache) RefreshCache(items map[interface{}]interface{}) { cache.mutex.Lock() defer cache.mutex.Unlock() - for _, val := range cache.items { - cache.deleteElement(val) - } + cache.items = make(map[interface{}]*list.Element) cache.evictionList.Init() for key, value := range items { diff --git a/cache_test.go b/cache_test.go index b6c18d5..2069f5a 100644 --- a/cache_test.go +++ b/cache_test.go @@ -34,6 +34,12 @@ func TestInvalidMinAge(t *testing.T) { }) } +func TestInvalidRefreshInterval(t *testing.T) { + assert.Panics(t, func() { + New(Config{Capacity: 1, RefreshInterval: -1 * time.Hour}) + }) +} + func TestBasicSetGet(t *testing.T) { cache := New(Config{Capacity: 2}) cache.Set("foo", 1) @@ -109,6 +115,34 @@ func TestExpiration(t *testing.T) { assert.False(t, eviction) } +func TestCacheBackgroundRefresh(t *testing.T) { + count := 0 + cache := New(Config{ + Capacity: 1, + RefreshInterval: 3 * time.Second, + OnRefresh: func() map[interface{}]interface{} { + count++ + return map[interface{}]interface{}{"key": count} + }, + }) + + value, ok := cache.Get("key") + assert.Equal(t, true, ok) + assert.Equal(t, 1, value) + + time.Sleep(4 * time.Second) // wait for the refresh loop to run + + value, ok = cache.Get("key") + assert.Equal(t, true, ok) + assert.Equal(t, 2, value) + + time.Sleep(4 * time.Second) + value, ok = cache.Get("key") + assert.Equal(t, true, ok) + assert.Equal(t, 3, value) + +} + type MockRandGenerator struct { startAt int64 incr int64