From 0be51f57e8430c7657f99062efd5b92c277b1f3a Mon Sep 17 00:00:00 2001 From: Simon Bos Date: Tue, 26 Nov 2024 12:10:01 +0100 Subject: [PATCH] Enable enqueuing updates for expired items when serving stale (#13) --- cache.go | 4 ++- cache_test.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/cache.go b/cache.go index 45ecb2e..a92a349 100644 --- a/cache.go +++ b/cache.go @@ -258,7 +258,9 @@ func (ch *Cache[K, T]) Get(key K) Value[T] { return v } - if delta.Abs() <= ch.threshold { + inTreshold := delta < 0 && delta.Abs() <= ch.threshold + expired := delta >= 0 + if inTreshold || expired { // key is eligible for update ch.enqueueUpdate(key) } diff --git a/cache_test.go b/cache_test.go index 4872c42..791a58b 100644 --- a/cache_test.go +++ b/cache_test.go @@ -262,6 +262,85 @@ func TestThresholdUpdater(tt *testing.T) { }) } +func TestThresholdUpdaterStale(tt *testing.T) { + var ( + requirer = require.New(tt) + asserter = require.New(tt) + cacheAge = time.Second + threshold = time.Millisecond * 500 + ) + + ranUpdater := atomic.Bool{} + + ch, err := New(Config[string, string]{ + ServeStale: true, + CacheAge: cacheAge, + Threshold: threshold, + Updater: func(ctx context.Context, key string) (string, error) { + ranUpdater.Store(true) + return key, nil + }, + }) + requirer.NoError(err) + tt.Run("before threshold", func(t *testing.T) { + ranUpdater.Store(false) + key := "key_1" + ch.Add(key, key) + ch.BulkAdd([]Tuple[string, string]{{Key: key, Value: key}}) + + v := ch.Get(key) + asserter.True(v.Found) + asserter.False(ranUpdater.Load()) + asserter.EqualValues(key, v.V) + }) + + tt.Run("during threshold", func(t *testing.T) { + ranUpdater.Store(false) + key := "key_2" + + ch.BulkAdd([]Tuple[string, string]{{Key: key, Value: key}}) + time.Sleep((cacheAge - threshold) + time.Millisecond) + v := ch.Get(key) + asserter.True(v.Found) + asserter.EqualValues(key, v.V) + // wait for updater to complete execution + time.Sleep(time.Millisecond * 100) + asserter.True(ranUpdater.Load()) + }) + + tt.Run("after threshold (cache expired)", func(t *testing.T) { + ranUpdater.Store(false) + key := "key_3" + + ch.BulkAdd([]Tuple[string, string]{{Key: key, Value: key}}) + time.Sleep(cacheAge + time.Millisecond) + + v := ch.Get(key) + asserter.True(v.Found) + asserter.EqualValues(key, v.V) + + // wait for updater to complete execution + time.Sleep(time.Millisecond * 100) + asserter.True(ranUpdater.Load()) + }) + + tt.Run("long after threshold (cache expired)", func(t *testing.T) { + ranUpdater.Store(false) + key := "key_4" + + ch.BulkAdd([]Tuple[string, string]{{Key: key, Value: key}}) + time.Sleep(cacheAge + 2*threshold) + + v := ch.Get(key) + asserter.True(v.Found) + asserter.EqualValues(key, v.V) + + // wait for updater to complete execution + time.Sleep(time.Millisecond * 100) + asserter.True(ranUpdater.Load()) + }) +} + func TestValidate(tt *testing.T) { asserter := assert.New(tt) requirer := require.New(tt)