1
2
3
4 package expirable
5
6 import (
7 "sync"
8 "time"
9
10 "github.com/hashicorp/golang-lru/v2/internal"
11 )
12
13
14 type EvictCallback[K comparable, V any] func(key K, value V)
15
16
17 type LRU[K comparable, V any] struct {
18 size int
19 evictList *internal.LruList[K, V]
20 items map[K]*internal.Entry[K, V]
21 onEvict EvictCallback[K, V]
22
23
24 mu sync.Mutex
25 ttl time.Duration
26 done chan struct{}
27
28
29 buckets []bucket[K, V]
30
31 nextCleanupBucket uint8
32 }
33
34
35 type bucket[K comparable, V any] struct {
36 entries map[K]*internal.Entry[K, V]
37 newestEntry time.Time
38 }
39
40
41 const noEvictionTTL = time.Hour * 24 * 365 * 10
42
43
44
45 const numBuckets = 100
46
47
48
49
50
51
52
53
54 func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V], ttl time.Duration) *LRU[K, V] {
55 if size < 0 {
56 size = 0
57 }
58 if ttl <= 0 {
59 ttl = noEvictionTTL
60 }
61
62 res := LRU[K, V]{
63 ttl: ttl,
64 size: size,
65 evictList: internal.NewList[K, V](),
66 items: make(map[K]*internal.Entry[K, V]),
67 onEvict: onEvict,
68 done: make(chan struct{}),
69 }
70
71
72 res.buckets = make([]bucket[K, V], numBuckets)
73 for i := 0; i < numBuckets; i++ {
74 res.buckets[i] = bucket[K, V]{entries: make(map[K]*internal.Entry[K, V])}
75 }
76
77
78
79
80
81 if res.ttl != noEvictionTTL {
82 go func(done <-chan struct{}) {
83 ticker := time.NewTicker(res.ttl / numBuckets)
84 defer ticker.Stop()
85 for {
86 select {
87 case <-done:
88 return
89 case <-ticker.C:
90 res.deleteExpired()
91 }
92 }
93 }(res.done)
94 }
95 return &res
96 }
97
98
99
100 func (c *LRU[K, V]) Purge() {
101 c.mu.Lock()
102 defer c.mu.Unlock()
103 for k, v := range c.items {
104 if c.onEvict != nil {
105 c.onEvict(k, v.Value)
106 }
107 delete(c.items, k)
108 }
109 for _, b := range c.buckets {
110 for _, ent := range b.entries {
111 delete(b.entries, ent.Key)
112 }
113 }
114 c.evictList.Init()
115 }
116
117
118
119
120 func (c *LRU[K, V]) Add(key K, value V) (evicted bool) {
121 c.mu.Lock()
122 defer c.mu.Unlock()
123 now := time.Now()
124
125
126 if ent, ok := c.items[key]; ok {
127 c.evictList.MoveToFront(ent)
128 c.removeFromBucket(ent)
129 ent.Value = value
130 ent.ExpiresAt = now.Add(c.ttl)
131 c.addToBucket(ent)
132 return false
133 }
134
135
136 ent := c.evictList.PushFrontExpirable(key, value, now.Add(c.ttl))
137 c.items[key] = ent
138 c.addToBucket(ent)
139
140 evict := c.size > 0 && c.evictList.Length() > c.size
141
142 if evict {
143 c.removeOldest()
144 }
145 return evict
146 }
147
148
149 func (c *LRU[K, V]) Get(key K) (value V, ok bool) {
150 c.mu.Lock()
151 defer c.mu.Unlock()
152 var ent *internal.Entry[K, V]
153 if ent, ok = c.items[key]; ok {
154
155 if time.Now().After(ent.ExpiresAt) {
156 return value, false
157 }
158 c.evictList.MoveToFront(ent)
159 return ent.Value, true
160 }
161 return
162 }
163
164
165
166 func (c *LRU[K, V]) Contains(key K) (ok bool) {
167 c.mu.Lock()
168 defer c.mu.Unlock()
169 _, ok = c.items[key]
170 return ok
171 }
172
173
174
175 func (c *LRU[K, V]) Peek(key K) (value V, ok bool) {
176 c.mu.Lock()
177 defer c.mu.Unlock()
178 var ent *internal.Entry[K, V]
179 if ent, ok = c.items[key]; ok {
180
181 if time.Now().After(ent.ExpiresAt) {
182 return value, false
183 }
184 return ent.Value, true
185 }
186 return
187 }
188
189
190
191 func (c *LRU[K, V]) Remove(key K) bool {
192 c.mu.Lock()
193 defer c.mu.Unlock()
194 if ent, ok := c.items[key]; ok {
195 c.removeElement(ent)
196 return true
197 }
198 return false
199 }
200
201
202 func (c *LRU[K, V]) RemoveOldest() (key K, value V, ok bool) {
203 c.mu.Lock()
204 defer c.mu.Unlock()
205 if ent := c.evictList.Back(); ent != nil {
206 c.removeElement(ent)
207 return ent.Key, ent.Value, true
208 }
209 return
210 }
211
212
213 func (c *LRU[K, V]) GetOldest() (key K, value V, ok bool) {
214 c.mu.Lock()
215 defer c.mu.Unlock()
216 if ent := c.evictList.Back(); ent != nil {
217 return ent.Key, ent.Value, true
218 }
219 return
220 }
221
222
223 func (c *LRU[K, V]) Keys() []K {
224 c.mu.Lock()
225 defer c.mu.Unlock()
226 keys := make([]K, 0, len(c.items))
227 for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() {
228 keys = append(keys, ent.Key)
229 }
230 return keys
231 }
232
233
234
235 func (c *LRU[K, V]) Values() []V {
236 c.mu.Lock()
237 defer c.mu.Unlock()
238 values := make([]V, len(c.items))
239 i := 0
240 now := time.Now()
241 for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() {
242 if now.After(ent.ExpiresAt) {
243 continue
244 }
245 values[i] = ent.Value
246 i++
247 }
248 return values
249 }
250
251
252 func (c *LRU[K, V]) Len() int {
253 c.mu.Lock()
254 defer c.mu.Unlock()
255 return c.evictList.Length()
256 }
257
258
259 func (c *LRU[K, V]) Resize(size int) (evicted int) {
260 c.mu.Lock()
261 defer c.mu.Unlock()
262 if size <= 0 {
263 c.size = 0
264 return 0
265 }
266 diff := c.evictList.Length() - size
267 if diff < 0 {
268 diff = 0
269 }
270 for i := 0; i < diff; i++ {
271 c.removeOldest()
272 }
273 c.size = size
274 return diff
275 }
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290 func (c *LRU[K, V]) removeOldest() {
291 if ent := c.evictList.Back(); ent != nil {
292 c.removeElement(ent)
293 }
294 }
295
296
297 func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V]) {
298 c.evictList.Remove(e)
299 delete(c.items, e.Key)
300 c.removeFromBucket(e)
301 if c.onEvict != nil {
302 c.onEvict(e.Key, e.Value)
303 }
304 }
305
306
307
308 func (c *LRU[K, V]) deleteExpired() {
309 c.mu.Lock()
310 bucketIdx := c.nextCleanupBucket
311 timeToExpire := time.Until(c.buckets[bucketIdx].newestEntry)
312
313 if timeToExpire > 0 {
314 c.mu.Unlock()
315 time.Sleep(timeToExpire)
316 c.mu.Lock()
317 }
318 for _, ent := range c.buckets[bucketIdx].entries {
319 c.removeElement(ent)
320 }
321 c.nextCleanupBucket = (c.nextCleanupBucket + 1) % numBuckets
322 c.mu.Unlock()
323 }
324
325
326 func (c *LRU[K, V]) addToBucket(e *internal.Entry[K, V]) {
327 bucketID := (numBuckets + c.nextCleanupBucket - 1) % numBuckets
328 e.ExpireBucket = bucketID
329 c.buckets[bucketID].entries[e.Key] = e
330 if c.buckets[bucketID].newestEntry.Before(e.ExpiresAt) {
331 c.buckets[bucketID].newestEntry = e.ExpiresAt
332 }
333 }
334
335
336 func (c *LRU[K, V]) removeFromBucket(e *internal.Entry[K, V]) {
337 delete(c.buckets[e.ExpireBucket].entries, e.Key)
338 }
339
View as plain text