Skip to content

Commit

Permalink
Merge pull request #267 from eko/loadable-options
Browse files Browse the repository at this point in the history
Loadable cache: make options to be re-used in setter (fixes #101)
  • Loading branch information
eko authored Jan 8, 2025
2 parents 70d4931 + a997e57 commit 08ff381
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 37 deletions.
33 changes: 20 additions & 13 deletions lib/cache/loadable.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package cache
import (
"context"
"errors"
"fmt"
"sync"

"github.com/eko/gocache/lib/v4/store"
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
48 changes: 24 additions & 24 deletions lib/cache/loadable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 08ff381

Please sign in to comment.