From a997e57c2d3d1d424801d2a048628c41064b09eb Mon Sep 17 00:00:00 2001 From: Vincent Composieux Date: Wed, 8 Jan 2025 07:13:51 +0100 Subject: [PATCH] Loadable cache: make options to be re-used in setter (fixes #101) --- lib/cache/loadable.go | 33 +++++++++++++++----------- lib/cache/loadable_test.go | 48 +++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/lib/cache/loadable.go b/lib/cache/loadable.go index efd81e3..cbf7abc 100644 --- a/lib/cache/loadable.go +++ b/lib/cache/loadable.go @@ -3,7 +3,6 @@ package cache import ( "context" "errors" - "fmt" "sync" "github.com/eko/gocache/lib/v4/store" @@ -16,11 +15,12 @@ const ( ) type loadableKeyValue[T any] struct { - key any - value T + key any + value T + options []store.Option } -type LoadFunction[T any] func(ctx context.Context, key any) (T, error) +type LoadFunction[T any] func(ctx context.Context, key any) (T, []store.Option, error) // LoadableCache represents a cache that uses a function to load data type LoadableCache[T any] struct { @@ -52,7 +52,7 @@ func (c *LoadableCache[T]) setter() { defer c.setterWg.Done() for item := range c.setChannel { - c.Set(context.Background(), item.key, item.value) + c.Set(context.Background(), item.key, item.value, item.options...) cacheKey := c.getCacheKey(item.key) c.singleFlight.Forget(cacheKey) @@ -74,30 +74,37 @@ func (c *LoadableCache[T]) Get(ctx context.Context, key any) (T, error) { if v, ok := c.setCache.Load(cacheKey); ok { return v.(T), nil } + zero := *new(T) - loadedResult, err, _ := c.singleFlight.Do( + rawLoadedResult, err, _ := c.singleFlight.Do( cacheKey, func() (any, error) { - return c.loadFunc(ctx, key) + value, options, innerErr := c.loadFunc(ctx, key) + + return &loadableKeyValue[T]{ + key: key, + value: value, + options: options, + }, innerErr }, ) if err != nil { return zero, err } - var ok bool - if object, ok = loadedResult.(T); !ok { + loadedKeyValue, ok := rawLoadedResult.(*loadableKeyValue[T]) + if !ok { return zero, errors.New( - fmt.Sprintf("returned value can't be cast to %T", zero), + "returned value can't be cast to *loadableKeyValue[T]", ) } // Then, put it back in cache - c.setCache.Store(cacheKey, object) - c.setChannel <- &loadableKeyValue[T]{key, object} + c.setCache.Store(cacheKey, loadedKeyValue.value) + c.setChannel <- loadedKeyValue - return object, err + return loadedKeyValue.value, err } // Set sets a value in available caches diff --git a/lib/cache/loadable_test.go b/lib/cache/loadable_test.go index a909e10..80ceb07 100644 --- a/lib/cache/loadable_test.go +++ b/lib/cache/loadable_test.go @@ -19,8 +19,8 @@ func TestNewLoadable(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) - loadFunc := func(_ context.Context, key any) (any, error) { - return "test data loaded", nil + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return "test data loaded", []store.Option{}, nil } // When @@ -48,8 +48,8 @@ func TestLoadableGetWhenAlreadyInCache(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) cache1.EXPECT().Get(ctx, "my-key").Return(cacheValue, nil) - loadFunc := func(_ context.Context, key any) (any, error) { - return nil, errors.New("should not be called") + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return nil, []store.Option{}, errors.New("should not be called") } cache := NewLoadable[any](loadFunc, cache1) @@ -72,8 +72,8 @@ func TestLoadableGetWhenNotAvailableInLoadFunc(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) cache1.EXPECT().Get(ctx, "my-key").Return(nil, errors.New("unable to find in cache 1")) - loadFunc := func(_ context.Context, key any) (any, error) { - return nil, errors.New("an error has occurred while loading data from custom source") + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return nil, []store.Option{}, errors.New("an error has occurred while loading data from custom source") } cache := NewLoadable[any](loadFunc, cache1) @@ -108,11 +108,11 @@ func TestLoadableGetWhenAvailableInLoadFunc(t *testing.T) { var loadCallCount int32 pauseLoadFn := make(chan struct{}) - loadFunc := func(_ context.Context, key any) (any, error) { + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { atomic.AddInt32(&loadCallCount, 1) <-pauseLoadFn time.Sleep(time.Millisecond * 10) - return cacheValue, nil + return cacheValue, []store.Option{}, nil } cache := NewLoadable[any](loadFunc, cache1) @@ -156,8 +156,8 @@ func TestLoadableDelete(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) cache1.EXPECT().Delete(ctx, "my-key").Return(nil) - loadFunc := func(_ context.Context, key any) (any, error) { - return "a value", nil + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return "a value", []store.Option{}, nil } cache := NewLoadable[any](loadFunc, cache1) @@ -180,8 +180,8 @@ func TestLoadableDeleteWhenError(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) cache1.EXPECT().Delete(ctx, "my-key").Return(expectedErr) - loadFunc := func(_ context.Context, key any) (any, error) { - return "a value", nil + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return "a value", []store.Option{}, nil } cache := NewLoadable[any](loadFunc, cache1) @@ -202,8 +202,8 @@ func TestLoadableInvalidate(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) cache1.EXPECT().Invalidate(ctx).Return(nil) - loadFunc := func(_ context.Context, key any) (any, error) { - return "a value", nil + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return "a value", []store.Option{}, nil } cache := NewLoadable[any](loadFunc, cache1) @@ -226,8 +226,8 @@ func TestLoadableInvalidateWhenError(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) cache1.EXPECT().Invalidate(ctx).Return(expectedErr) - loadFunc := func(_ context.Context, key any) (any, error) { - return "a value", nil + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return "a value", []store.Option{}, nil } cache := NewLoadable[any](loadFunc, cache1) @@ -248,8 +248,8 @@ func TestLoadableClear(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) cache1.EXPECT().Clear(ctx).Return(nil) - loadFunc := func(_ context.Context, key any) (any, error) { - return "a value", nil + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return "a value", []store.Option{}, nil } cache := NewLoadable[any](loadFunc, cache1) @@ -272,8 +272,8 @@ func TestLoadableClearWhenError(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) cache1.EXPECT().Clear(ctx).Return(expectedErr) - loadFunc := func(_ context.Context, key any) (any, error) { - return "a value", nil + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return "a value", []store.Option{}, nil } cache := NewLoadable[any](loadFunc, cache1) @@ -291,8 +291,8 @@ func TestLoadableGetType(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) - loadFunc := func(_ context.Context, key any) (any, error) { - return "test data loaded", nil + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return "test data loaded", []store.Option{}, nil } cache := NewLoadable[any](loadFunc, cache1) @@ -308,8 +308,8 @@ func TestLoadableGetTwice(t *testing.T) { cache1 := NewMockSetterCacheInterface[any](ctrl) var counter atomic.Uint64 - loadFunc := func(_ context.Context, key any) (any, error) { - return counter.Add(1), nil + loadFunc := func(_ context.Context, key any) (any, []store.Option, error) { + return counter.Add(1), []store.Option{}, nil } cache := NewLoadable[any](loadFunc, cache1)