@@ -3,66 +3,56 @@ package lazycache
33import (
44 "sync"
55
6- "github.com/hashicorp /golang-lru/simplelru"
6+ "github.com/bep /golang-lru/v2 /simplelru"
77)
88
9- var _ = Entry (& delayedEntry {})
10-
119// New creates a new Cache.
12- func New (options CacheOptions ) * Cache {
13- lru , err := simplelru .NewLRU (int (options .MaxEntries ), nil )
10+ func New [ K comparable , V any ] (options Options ) * Cache [ K , V ] {
11+ lru , err := simplelru .NewLRU [ K , * valueWrapper [ V ]] (int (options .MaxEntries ), nil )
1412 if err != nil {
1513 panic (err )
1614 }
17- c := & Cache {
15+ c := & Cache [ K , V ] {
1816 lru : lru ,
1917 }
2018 return c
2119}
2220
23- type CacheOptions struct {
21+ // Options holds the cache options.
22+ type Options struct {
2423 // MaxEntries is the maximum number of entries that the cache should hold.
2524 // Note that this can also be adjusted after the cache is created with Resize.
2625 MaxEntries int
2726}
2827
29- // Entry is the result of a cache lookup.
30- // Any Err value is the error that was returned by the cache prime function. This error value is cached. TODO(bep) consider this.
31- type Entry interface {
32- Value () any
33- Err () error
34- }
35-
36- type Cache struct {
37- lru * simplelru.LRU
28+ // Cache is a thread-safe resizable LRU cache.
29+ type Cache [K comparable , V any ] struct {
30+ lru * simplelru.LRU [K , * valueWrapper [V ]]
3831 mu sync.RWMutex
39- }
4032
41- // Contains returns true if the given key is in the cache.
42- func (c * Cache ) Contains (key any ) bool {
43- c .mu .RLock ()
44- b := c .lru .Contains (key )
45- c .mu .RUnlock ()
46- return b
33+ zerov V
4734}
4835
4936// Delete deletes the item with given key from the cache, returning if the
5037// key was contained.
51- func (c * Cache ) Delete (key any ) bool {
38+ func (c * Cache [ K , V ] ) Delete (key K ) bool {
5239 c .mu .Lock ()
5340 defer c .mu .Unlock ()
5441 return c .lru .Remove (key )
5542}
5643
5744// DeleteFunc deletes all entries for which the given function returns true.
58- func (c * Cache ) DeleteFunc (matches func (key any , item Entry ) bool ) int {
45+ func (c * Cache [ K , V ] ) DeleteFunc (matches func (key K , item V ) bool ) int {
5946 c .mu .RLock ()
6047 keys := c .lru .Keys ()
6148
62- var keysToDelete []any
49+ var keysToDelete []K
6350 for _ , key := range keys {
64- v , _ := c .lru .Peek (key )
65- if matches (key , v .(Entry )) {
51+ w , _ := c .lru .Peek (key )
52+ if ! w .wait ().found {
53+ continue
54+ }
55+ if matches (key , w .value ) {
6656 keysToDelete = append (keysToDelete , key )
6757 }
6858 }
@@ -80,109 +70,118 @@ func (c *Cache) DeleteFunc(matches func(key any, item Entry) bool) int {
8070 return deleteCount
8171}
8272
83- // Keys returns a slice of the keys in the cache, oldest first.
84- func (c * Cache ) Keys () []any {
85- c .mu .RLock ()
86- defer c .mu .RUnlock ()
87- return c .lru .Keys ()
88- }
89-
90- // Len returns the number of items in the cache.
91- func (c * Cache ) Len () int {
92- c .mu .RLock ()
93- defer c .mu .RUnlock ()
94- return c .lru .Len ()
95- }
96-
9773// Get returns the value associated with key.
98- func (c * Cache ) Get (key any ) Entry {
74+ func (c * Cache [ K , V ] ) Get (key K ) ( V , bool ) {
9975 c .mu .Lock ()
100- v , ok := c .lru . Get (key )
76+ w := c .get (key )
10177 c .mu .Unlock ()
102- if ! ok {
103- return entry {}
78+ if w == nil {
79+ return c . zerov , false
10480 }
105- return v .(Entry )
81+ w .wait ()
82+ return w .value , w .found
10683}
10784
10885// GetOrCreate returns the value associated with key, or creates it if it doesn't.
10986// Note that create, the cache prime function, is called once and then not called again for a given key
11087// unless the cache entry is evicted; it does not block other goroutines from calling GetOrCreate,
11188// it is not called with the cache lock held.
112- func (c * Cache ) GetOrCreate (key any , create func (key any ) (any , error )) Entry {
89+ func (c * Cache [ K , V ] ) GetOrCreate (key K , create func (key K ) (V , error )) ( V , bool , error ) {
11390 c .mu .Lock ()
114- v , ok := c .lru .Get (key )
115- if ok {
116- c .mu .Unlock ()
117- return v .(Entry )
91+ w := c .get (key )
92+ if w != nil {
93+ w .wait ()
94+ // if w.ready is set then w comes from a concurrent GetOrCreate call.
95+ if w .found || w .ready != nil {
96+ c .mu .Unlock ()
97+ return w .value , w .found , nil
98+ }
11899 }
119100
120- var e = & delayedEntry {
121- done : make (chan struct {}),
101+ w = & valueWrapper [ V ] {
102+ ready : make (chan struct {}),
122103 }
123- // Add the *delayedEntry early and release the lock.
124- // Calllers coming in getting the same cache entry will block on the done channel.
125- c .lru .Add (key , e )
104+
105+ // Concurrent access to the same key will see w, but needs to wait for w.ready
106+ // to get the value.
107+ c .lru .Add (key , w )
126108 c .mu .Unlock ()
127109
128110 // Create the value with the lock released.
129111 v , err := create (key )
112+ w .err = err
113+ w .value = v
114+ w .found = err == nil
130115
131- // e is a pointer, and these values will be available to other callers getting this cache entry,
132- // once the done channel is closed.
133- e .err = err
134- e .value = v
135- close (e .done )
116+ close (w .ready )
136117
137- return e
118+ if err != nil {
119+ c .Delete (key )
120+ return c .zerov , false , err
121+ }
122+ return v , true , nil
138123}
139124
140125// Resize changes the cache size and returns the number of entries evicted.
141- func (c * Cache ) Resize (size int ) (evicted int ) {
126+ func (c * Cache [ K , V ] ) Resize (size int ) (evicted int ) {
142127 c .mu .Lock ()
143128 evicted = c .lru .Resize (size )
144129 c .mu .Unlock ()
145130 return evicted
146131}
147132
148133// Set associates value with key.
149- func (c * Cache ) Set (key , value any ) {
134+ func (c * Cache [ K , V ] ) Set (key K , value V ) {
150135 c .mu .Lock ()
151- if _ , ok := value .(Entry ); ! ok {
152- value = entry {
153- value : value ,
154- }
155- }
156- c .lru .Add (key , value )
136+ c .lru .Add (key , & valueWrapper [V ]{value : value , found : true })
157137 c .mu .Unlock ()
158138}
159139
160- // delayedEntry holds a cache value or error that is not available until the done channel is closed.
161- type delayedEntry struct {
162- done chan struct {}
163- value any
164- err error
140+ func (c * Cache [K , V ]) get (key K ) * valueWrapper [V ] {
141+ w , ok := c .lru .Get (key )
142+ if ! ok {
143+ return nil
144+ }
145+ return w
165146}
166147
167- func (r * delayedEntry ) Value () any {
168- <- r .done
169- return r .value
148+ // contains returns true if the given key is in the cache.
149+ // note that this wil also return true if the key is in the cache but the value is not yet ready.
150+ func (c * Cache [K , V ]) contains (key K ) bool {
151+ c .mu .RLock ()
152+ b := c .lru .Contains (key )
153+ c .mu .RUnlock ()
154+ return b
170155}
171156
172- func (r * delayedEntry ) Err () error {
173- <- r .done
174- return r .err
157+ // keys returns a slice of the keys in the cache, oldest first.
158+ // note that this wil also include keys that are not yet ready.
159+ func (c * Cache [K , V ]) keys () []K {
160+ c .mu .RLock ()
161+ defer c .mu .RUnlock ()
162+ return c .lru .Keys ()
175163}
176164
177- type entry struct {
178- value any
179- err error
165+ // len returns the number of items in the cache.
166+ // note that this wil also include values that are not yet ready.
167+ func (c * Cache [K , V ]) len () int {
168+ c .mu .RLock ()
169+ defer c .mu .RUnlock ()
170+ return c .lru .Len ()
180171}
181172
182- func (r entry ) Value () any {
183- return r .value
173+ // valueWrapper holds a cache value that is not available unless the done channel is nil or closed.
174+ // This construct makes more sense if you look at the code in GetOrCreate.
175+ type valueWrapper [V any ] struct {
176+ value V
177+ found bool
178+ err error
179+ ready chan struct {}
184180}
185181
186- func (r entry ) Err () error {
187- return r .err
182+ func (w * valueWrapper [V ]) wait () * valueWrapper [V ] {
183+ if w .ready != nil {
184+ <- w .ready
185+ }
186+ return w
188187}
0 commit comments