4
4
package merkledb
5
5
6
6
import (
7
+ "errors"
7
8
"sync"
8
9
9
10
"github.com/ava-labs/avalanchego/utils/linkedhashmap"
10
11
"github.com/ava-labs/avalanchego/utils/wrappers"
11
12
)
12
13
14
+ var errEmptyCacheTooLarge = errors .New ("cache is empty yet still too large" )
15
+
13
16
// A cache that calls [onEviction] on the evicted element.
14
17
type onEvictCache [K comparable , V any ] struct {
15
- lock sync.RWMutex
16
- maxSize int
17
- fifo linkedhashmap.LinkedHashmap [K , V ]
18
+ lock sync.RWMutex
19
+ maxSize int
20
+ currentSize int
21
+ fifo linkedhashmap.LinkedHashmap [K , V ]
22
+ size func (K , V ) int
18
23
// Must not call any method that grabs [c.lock]
19
24
// because this would cause a deadlock.
20
25
onEviction func (K , V ) error
21
26
}
22
27
23
- func newOnEvictCache [K comparable , V any ](maxSize int , onEviction func (K , V ) error ) onEvictCache [K , V ] {
28
+ func newOnEvictCache [K comparable , V any ](
29
+ maxSize int ,
30
+ size func (K , V ) int ,
31
+ onEviction func (K , V ) error ,
32
+ ) onEvictCache [K , V ] {
24
33
return onEvictCache [K , V ]{
25
34
maxSize : maxSize ,
26
35
fifo : linkedhashmap .New [K , V ](),
36
+ size : size ,
27
37
onEviction : onEviction ,
28
38
}
29
39
}
30
40
31
- // removeOldest returns and removes the oldest element from this cache.
32
- // Assumes [c.lock] is held.
33
- func (c * onEvictCache [K , V ]) removeOldest () (K , V , bool ) {
34
- k , v , exists := c .fifo .Oldest ()
35
- if exists {
36
- c .fifo .Delete (k )
37
- }
38
- return k , v , exists
39
- }
40
-
41
41
// Get an element from this cache.
42
42
func (c * onEvictCache [K , V ]) Get (key K ) (V , bool ) {
43
43
c .lock .RLock ()
@@ -53,14 +53,14 @@ func (c *onEvictCache[K, V]) Put(key K, value V) error {
53
53
c .lock .Lock ()
54
54
defer c .lock .Unlock ()
55
55
56
+ if oldValue , replaced := c .fifo .Get (key ); replaced {
57
+ c .currentSize -= c .size (key , oldValue )
58
+ }
59
+
60
+ c .currentSize += c .size (key , value )
56
61
c .fifo .Put (key , value ) // Mark as MRU
57
62
58
- if c .fifo .Len () > c .maxSize {
59
- oldestKey , oldestVal , _ := c .fifo .Oldest ()
60
- c .fifo .Delete (oldestKey )
61
- return c .onEviction (oldestKey , oldestVal )
62
- }
63
- return nil
63
+ return c .resize (c .maxSize )
64
64
}
65
65
66
66
// Flush removes all elements from the cache.
@@ -74,16 +74,37 @@ func (c *onEvictCache[K, V]) Flush() error {
74
74
c .lock .Unlock ()
75
75
}()
76
76
77
+ return c .resize (0 )
78
+ }
79
+
80
+ // removeOldest returns and removes the oldest element from this cache.
81
+ //
82
+ // Assumes [c.lock] is held.
83
+ func (c * onEvictCache [K , V ]) removeOldest () (K , V , bool ) {
84
+ k , v , exists := c .fifo .Oldest ()
85
+ if exists {
86
+ c .currentSize -= c .size (k , v )
87
+ c .fifo .Delete (k )
88
+ }
89
+ return k , v , exists
90
+ }
91
+
92
+ // resize removes the oldest elements from the cache until the cache is not
93
+ // larger than the provided target.
94
+ //
95
+ // Assumes [c.lock] is held.
96
+ func (c * onEvictCache [K , V ]) resize (target int ) error {
77
97
// Note that we can't use [c.fifo]'s iterator because [c.onEviction]
78
98
// modifies [c.fifo], which violates the iterator's invariant.
79
99
var errs wrappers.Errs
80
- for {
81
- key , value , exists := c .removeOldest ()
100
+ for c . currentSize > target {
101
+ k , v , exists := c .removeOldest ()
82
102
if ! exists {
83
- // The cache is empty.
84
- return errs .Err
103
+ // This should really never happen unless the size of an entry
104
+ // changed or the target size is negative.
105
+ return errEmptyCacheTooLarge
85
106
}
86
-
87
- errs .Add (c .onEviction (key , value ))
107
+ errs .Add (c .onEviction (k , v ))
88
108
}
109
+ return errs .Err
89
110
}
0 commit comments