-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Ivan Milchev <[email protected]>
- Loading branch information
Showing
5 changed files
with
125 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package memoize | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/patrickmn/go-cache" | ||
Check failure on line 6 in providers-sdk/v1/util/memoize/memoize.go GitHub Actions / go-test
Check failure on line 6 in providers-sdk/v1/util/memoize/memoize.go GitHub Actions / go-test
Check failure on line 6 in providers-sdk/v1/util/memoize/memoize.go GitHub Actions / go-test
|
||
"golang.org/x/sync/singleflight" | ||
) | ||
|
||
// Memoizer allows you to memoize function calls. Memoizer is safe for concurrent use by multiple goroutines. | ||
type Memoizer struct { | ||
// Storage exposes the underlying cache of memoized results to manipulate as desired - for example, to Flush(). | ||
Storage *cache.Cache | ||
|
||
group singleflight.Group | ||
} | ||
|
||
// NewMemoizer creates a new Memoizer with the configured expiry and cleanup policies. | ||
// If desired, use cache.NoExpiration to cache values forever. | ||
func NewMemoizer(defaultExpiration, cleanupInterval time.Duration) *Memoizer { | ||
return &Memoizer{ | ||
Storage: cache.New(defaultExpiration, cleanupInterval), | ||
group: singleflight.Group{}, | ||
} | ||
} | ||
|
||
// Memoize executes and returns the results of the given function, unless there was a cached value of the same key. | ||
// Only one execution is in-flight for a given key at a time. | ||
// The boolean return value indicates whether v was previously stored. | ||
func (m *Memoizer) Memoize(key string, fn func() (interface{}, error)) (interface{}, error, bool) { | ||
// Check cache | ||
value, found := m.Storage.Get(key) | ||
if found { | ||
return value, nil, true | ||
} | ||
|
||
// Combine memoized function with a cache store | ||
value, err, _ := m.group.Do(key, func() (interface{}, error) { | ||
data, innerErr := fn() | ||
|
||
if innerErr == nil { | ||
m.Storage.Set(key, data, cache.DefaultExpiration) | ||
} | ||
|
||
return data, innerErr | ||
}) | ||
return value, err, false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package memoize | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
// TestBasic adopts the code from readme.md into a simple test case | ||
func TestBasic(t *testing.T) { | ||
expensiveCalls := 0 | ||
|
||
// Function tracks how many times its been called | ||
expensive := func() (interface{}, error) { | ||
expensiveCalls++ | ||
return expensiveCalls, nil | ||
} | ||
|
||
cache := NewMemoizer(90*time.Second, 10*time.Minute) | ||
|
||
// First call SHOULD NOT be cached | ||
result, err, cached := cache.Memoize("key1", expensive) | ||
assert.NoError(t, err) | ||
assert.Equal(t, result.(int), 1) | ||
assert.False(t, cached) | ||
|
||
// Second call on same key SHOULD be cached | ||
result, err, cached = cache.Memoize("key1", expensive) | ||
assert.NoError(t, err) | ||
assert.Equal(t, result.(int), 1) | ||
assert.True(t, cached) | ||
|
||
// First call on a new key SHOULD NOT be cached | ||
result, err, cached = cache.Memoize("key2", expensive) | ||
assert.NoError(t, err) | ||
assert.Equal(t, result.(int), 2) | ||
assert.False(t, cached) | ||
} | ||
|
||
// TestFailure checks that failed function values are not cached | ||
func TestFailure(t *testing.T) { | ||
calls := 0 | ||
|
||
// This function will fail IFF it has not been called before. | ||
twoForTheMoney := func() (interface{}, error) { | ||
calls++ | ||
|
||
if calls == 1 { | ||
return calls, errors.New("Try again") | ||
} else { | ||
return calls, nil | ||
} | ||
} | ||
|
||
cache := NewMemoizer(90*time.Second, 10*time.Minute) | ||
|
||
// First call should fail, and not be cached | ||
result, err, cached := cache.Memoize("key1", twoForTheMoney) | ||
assert.Error(t, err) | ||
assert.Equal(t, result.(int), 1) | ||
assert.False(t, cached) | ||
|
||
// Second call should succeed, and not be cached | ||
result, err, cached = cache.Memoize("key1", twoForTheMoney) | ||
assert.NoError(t, err) | ||
assert.Equal(t, result.(int), 2) | ||
assert.False(t, cached) | ||
|
||
// Third call should succeed, and be cached | ||
result, err, cached = cache.Memoize("key1", twoForTheMoney) | ||
assert.NoError(t, err) | ||
assert.Equal(t, result.(int), 2) | ||
assert.True(t, cached) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters