-
Notifications
You must be signed in to change notification settings - Fork 0
/
sfcache.go
92 lines (77 loc) · 2.39 KB
/
sfcache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package sfcache
import (
"context"
"sync"
"time"
"github.com/hashicorp/golang-lru/v2/expirable"
"github.com/n-r-w/singleflight/v2"
)
// Group singleflight group with LRU cache
type Group[K comparable, V any] struct {
muClear sync.RWMutex
cache *expirable.LRU[K, V]
size int
ttl time.Duration
onEvict expirable.EvictCallback[K, V]
sfgroup singleflight.Group[K, V]
}
// New creates a new singleflight Group with the specified LRU cache size and TTL.
// Size parameter set to 0 makes cache of unlimited size, e.g. turns LRU mechanism off.
// Providing 0 TTL turns expiring off.
func New[K comparable, V any](size int, onEvict expirable.EvictCallback[K, V], ttl time.Duration) *Group[K, V] {
return &Group[K, V]{
size: size,
ttl: ttl,
onEvict: onEvict,
cache: expirable.NewLRU[K, V](size, onEvict, ttl),
}
}
// Do executes and returns the results of the given function, making sure that
// only one execution is in-flight for a given key at a time. If a duplicate
// comes in, the duplicate caller waits for the original to complete and
// receives the same results.
//
// The context passed to the fn function is a context that preserves all values
// from the passed context but is cancelled by the singleflight only when all
// awaiting caller's contexts are cancelled (no caller is awaiting the result).
// If there are multiple callers, context passed to one caller does not affect
// the execution and returned values of others except if the function result is
// dependent on the context values.
//
// If useCache is false, it will work as a simple singleflight.
//
// The return value shared indicates whether v was given to multiple callers.
func (g *Group[K, V]) Do(ctx context.Context,
key K,
useCache bool,
fn func(ctx context.Context) (V, error),
) (val V, shared bool, err error) {
g.muClear.RLock()
defer g.muClear.RUnlock()
if useCache {
if v, ok := g.cache.Get(key); ok {
return v, true, nil
}
}
val, shared, err = g.sfgroup.Do(ctx, key, func(ctxFunc context.Context) (V, error) {
v, err1 := fn(ctxFunc)
if err1 != nil {
var zeroV V
return zeroV, err1
}
return v, nil
})
if err != nil {
return val, shared, err
}
if useCache {
_ = g.cache.Add(key, val)
}
return val, shared, err
}
// Clear clears the cache
func (g *Group[K, V]) Clear() {
g.muClear.Lock()
defer g.muClear.Unlock()
g.cache = expirable.NewLRU[K, V](g.size, g.onEvict, g.ttl)
}