lru.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. package lru
  2. // golang -lru
  3. // https://github.com/hashicorp/golang-lru
  4. import (
  5. "sync"
  6. "time"
  7. )
  8. // EvictCallback is used to get a callback when a cache entry is evicted
  9. type EvictCallback[K comparable, V any] func(key K, value V)
  10. // LRU implements a thread-safe LRU with expirable entries.
  11. type LRU[K comparable, V any] struct {
  12. size int
  13. evictList *LruList[K, V]
  14. items map[K]*Entry[K, V]
  15. onEvict EvictCallback[K, V]
  16. // expirable options
  17. mu sync.Mutex
  18. ttl time.Duration
  19. done chan struct{}
  20. // buckets for expiration
  21. buckets []bucket[K, V]
  22. // uint8 because it's number between 0 and numBuckets
  23. nextCleanupBucket uint8
  24. }
  25. // bucket is a container for holding entries to be expired
  26. type bucket[K comparable, V any] struct {
  27. entries map[K]*Entry[K, V]
  28. newestEntry time.Time
  29. }
  30. // noEvictionTTL - very long ttl to prevent eviction
  31. const noEvictionTTL = time.Hour * 24 * 365 * 10
  32. // because of uint8 usage for nextCleanupBucket, should not exceed 256.
  33. // casting it as uint8 explicitly requires type conversions in multiple places
  34. const numBuckets = 100
  35. // NewLRU returns a new thread-safe cache with expirable entries.
  36. //
  37. // Size parameter set to 0 makes cache of unlimited size, e.g. turns LRU mechanism off.
  38. //
  39. // Providing 0 TTL turns expiring off.
  40. //
  41. // Delete expired entries every 1/100th of ttl value. Goroutine which deletes expired entries runs indefinitely.
  42. func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V], ttl time.Duration) *LRU[K, V] {
  43. if size < 0 {
  44. size = 0
  45. }
  46. if ttl <= 0 {
  47. ttl = noEvictionTTL
  48. }
  49. res := LRU[K, V]{
  50. ttl: ttl,
  51. size: size,
  52. evictList: NewList[K, V](),
  53. items: make(map[K]*Entry[K, V]),
  54. onEvict: onEvict,
  55. done: make(chan struct{}),
  56. }
  57. // initialize the buckets
  58. res.buckets = make([]bucket[K, V], numBuckets)
  59. for i := 0; i < numBuckets; i++ {
  60. res.buckets[i] = bucket[K, V]{entries: make(map[K]*Entry[K, V])}
  61. }
  62. // enable deleteExpired() running in separate goroutine for cache with non-zero TTL
  63. //
  64. // Important: done channel is never closed, so deleteExpired() goroutine will never exit,
  65. // it's decided to add functionality to close it in the version later than v2.
  66. if res.ttl != noEvictionTTL {
  67. go func(done <-chan struct{}) {
  68. ticker := time.NewTicker(res.ttl / numBuckets)
  69. defer ticker.Stop()
  70. for {
  71. select {
  72. case <-done:
  73. return
  74. case <-ticker.C:
  75. res.deleteExpired()
  76. }
  77. }
  78. }(res.done)
  79. }
  80. return &res
  81. }
  82. // Purge clears the cache completely.
  83. // onEvict is called for each evicted key.
  84. func (c *LRU[K, V]) Purge() {
  85. c.mu.Lock()
  86. defer c.mu.Unlock()
  87. for k, v := range c.items {
  88. if c.onEvict != nil {
  89. c.onEvict(k, v.Value)
  90. }
  91. delete(c.items, k)
  92. }
  93. for _, b := range c.buckets {
  94. for _, ent := range b.entries {
  95. delete(b.entries, ent.Key)
  96. }
  97. }
  98. c.evictList.Init()
  99. }
  100. // Add adds a value to the cache. Returns true if an eviction occurred.
  101. // Returns false if there was no eviction: the item was already in the cache,
  102. // or the size was not exceeded.
  103. func (c *LRU[K, V]) Add(key K, value V) (evicted bool) {
  104. c.mu.Lock()
  105. defer c.mu.Unlock()
  106. now := time.Now()
  107. // Check for existing item
  108. if ent, ok := c.items[key]; ok {
  109. c.evictList.MoveToFront(ent)
  110. c.removeFromBucket(ent) // remove the entry from its current bucket as expiresAt is renewed
  111. ent.Value = value
  112. ent.ExpiresAt = now.Add(c.ttl)
  113. c.addToBucket(ent)
  114. return false
  115. }
  116. // Add new item
  117. ent := c.evictList.PushFrontExpirable(key, value, now.Add(c.ttl))
  118. c.items[key] = ent
  119. c.addToBucket(ent) // adds the entry to the appropriate bucket and sets entry.expireBucket
  120. evict := c.size > 0 && c.evictList.Length() > c.size
  121. // Verify size not exceeded
  122. if evict {
  123. c.removeOldest()
  124. }
  125. return evict
  126. }
  127. // Get looks up a key's value from the cache.
  128. func (c *LRU[K, V]) Get(key K) (value V, ok bool) {
  129. c.mu.Lock()
  130. defer c.mu.Unlock()
  131. var ent *Entry[K, V]
  132. if ent, ok = c.items[key]; ok {
  133. // Expired item check
  134. if time.Now().After(ent.ExpiresAt) {
  135. return value, false
  136. }
  137. c.evictList.MoveToFront(ent)
  138. return ent.Value, true
  139. }
  140. return
  141. }
  142. // Contains checks if a key is in the cache, without updating the recent-ness
  143. // or deleting it for being stale.
  144. func (c *LRU[K, V]) Contains(key K) (ok bool) {
  145. c.mu.Lock()
  146. defer c.mu.Unlock()
  147. _, ok = c.items[key]
  148. return ok
  149. }
  150. // Peek returns the key value (or undefined if not found) without updating
  151. // the "recently used"-ness of the key.
  152. func (c *LRU[K, V]) Peek(key K) (value V, ok bool) {
  153. c.mu.Lock()
  154. defer c.mu.Unlock()
  155. var ent *Entry[K, V]
  156. if ent, ok = c.items[key]; ok {
  157. // Expired item check
  158. if time.Now().After(ent.ExpiresAt) {
  159. return value, false
  160. }
  161. return ent.Value, true
  162. }
  163. return
  164. }
  165. // Remove removes the provided key from the cache, returning if the
  166. // key was contained.
  167. func (c *LRU[K, V]) Remove(key K) bool {
  168. c.mu.Lock()
  169. defer c.mu.Unlock()
  170. if ent, ok := c.items[key]; ok {
  171. c.removeElement(ent)
  172. return true
  173. }
  174. return false
  175. }
  176. // RemoveOldest removes the oldest item from the cache.
  177. func (c *LRU[K, V]) RemoveOldest() (key K, value V, ok bool) {
  178. c.mu.Lock()
  179. defer c.mu.Unlock()
  180. if ent := c.evictList.Back(); ent != nil {
  181. c.removeElement(ent)
  182. return ent.Key, ent.Value, true
  183. }
  184. return
  185. }
  186. // GetOldest returns the oldest entry
  187. func (c *LRU[K, V]) GetOldest() (key K, value V, ok bool) {
  188. c.mu.Lock()
  189. defer c.mu.Unlock()
  190. if ent := c.evictList.Back(); ent != nil {
  191. return ent.Key, ent.Value, true
  192. }
  193. return
  194. }
  195. func (c *LRU[K, V]) KeyValues() map[K]V {
  196. c.mu.Lock()
  197. defer c.mu.Unlock()
  198. maps := make(map[K]V)
  199. now := time.Now()
  200. for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() {
  201. if now.After(ent.ExpiresAt) {
  202. continue
  203. }
  204. maps[ent.Key] = ent.Value
  205. // keys = append(keys, ent.Key)
  206. }
  207. return maps
  208. }
  209. // Keys returns a slice of the keys in the cache, from oldest to newest.
  210. // Expired entries are filtered out.
  211. func (c *LRU[K, V]) Keys() []K {
  212. c.mu.Lock()
  213. defer c.mu.Unlock()
  214. keys := make([]K, 0, len(c.items))
  215. now := time.Now()
  216. for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() {
  217. if now.After(ent.ExpiresAt) {
  218. continue
  219. }
  220. keys = append(keys, ent.Key)
  221. }
  222. return keys
  223. }
  224. // Values returns a slice of the values in the cache, from oldest to newest.
  225. // Expired entries are filtered out.
  226. func (c *LRU[K, V]) Values() []V {
  227. c.mu.Lock()
  228. defer c.mu.Unlock()
  229. values := make([]V, 0, len(c.items))
  230. now := time.Now()
  231. for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() {
  232. if now.After(ent.ExpiresAt) {
  233. continue
  234. }
  235. values = append(values, ent.Value)
  236. }
  237. return values
  238. }
  239. // Len returns the number of items in the cache.
  240. func (c *LRU[K, V]) Len() int {
  241. c.mu.Lock()
  242. defer c.mu.Unlock()
  243. return c.evictList.Length()
  244. }
  245. // Resize changes the cache size. Size of 0 means unlimited.
  246. func (c *LRU[K, V]) Resize(size int) (evicted int) {
  247. c.mu.Lock()
  248. defer c.mu.Unlock()
  249. if size <= 0 {
  250. c.size = 0
  251. return 0
  252. }
  253. diff := c.evictList.Length() - size
  254. if diff < 0 {
  255. diff = 0
  256. }
  257. for i := 0; i < diff; i++ {
  258. c.removeOldest()
  259. }
  260. c.size = size
  261. return diff
  262. }
  263. // Close destroys cleanup goroutine. To clean up the cache, run Purge() before Close().
  264. // func (c *LRU[K, V]) Close() {
  265. // c.mu.Lock()
  266. // defer c.mu.Unlock()
  267. // select {
  268. // case <-c.done:
  269. // return
  270. // default:
  271. // }
  272. // close(c.done)
  273. // }
  274. // removeOldest removes the oldest item from the cache. Has to be called with lock!
  275. func (c *LRU[K, V]) removeOldest() {
  276. if ent := c.evictList.Back(); ent != nil {
  277. c.removeElement(ent)
  278. }
  279. }
  280. // removeElement is used to remove a given list element from the cache. Has to be called with lock!
  281. func (c *LRU[K, V]) removeElement(e *Entry[K, V]) {
  282. c.evictList.Remove(e)
  283. delete(c.items, e.Key)
  284. c.removeFromBucket(e)
  285. if c.onEvict != nil {
  286. c.onEvict(e.Key, e.Value)
  287. }
  288. }
  289. // deleteExpired deletes expired records from the oldest bucket, waiting for the newest entry
  290. // in it to expire first.
  291. func (c *LRU[K, V]) deleteExpired() {
  292. c.mu.Lock()
  293. bucketIdx := c.nextCleanupBucket
  294. timeToExpire := time.Until(c.buckets[bucketIdx].newestEntry)
  295. // wait for newest entry to expire before cleanup without holding lock
  296. if timeToExpire > 0 {
  297. c.mu.Unlock()
  298. time.Sleep(timeToExpire)
  299. c.mu.Lock()
  300. }
  301. for _, ent := range c.buckets[bucketIdx].entries {
  302. c.removeElement(ent)
  303. }
  304. c.nextCleanupBucket = (c.nextCleanupBucket + 1) % numBuckets
  305. c.mu.Unlock()
  306. }
  307. // addToBucket adds entry to expire bucket so that it will be cleaned up when the time comes. Has to be called with lock!
  308. func (c *LRU[K, V]) addToBucket(e *Entry[K, V]) {
  309. bucketID := (numBuckets + c.nextCleanupBucket - 1) % numBuckets
  310. e.ExpireBucket = bucketID
  311. c.buckets[bucketID].entries[e.Key] = e
  312. if c.buckets[bucketID].newestEntry.Before(e.ExpiresAt) {
  313. c.buckets[bucketID].newestEntry = e.ExpiresAt
  314. }
  315. }
  316. // removeFromBucket removes the entry from its corresponding bucket. Has to be called with lock!
  317. func (c *LRU[K, V]) removeFromBucket(e *Entry[K, V]) {
  318. delete(c.buckets[e.ExpireBucket].entries, e.Key)
  319. }
  320. // Cap returns the capacity of the cache
  321. func (c *LRU[K, V]) Cap() int {
  322. return c.size
  323. }
  324. // Entry is an LRU Entry
  325. type Entry[K comparable, V any] struct {
  326. // Next and previous pointers in the doubly-linked list of elements.
  327. // To simplify the implementation, internally a list l is implemented
  328. // as a ring, such that &l.root is both the next element of the last
  329. // list element (l.Back()) and the previous element of the first list
  330. // element (l.Front()).
  331. next, prev *Entry[K, V]
  332. // The list to which this element belongs.
  333. list *LruList[K, V]
  334. // The LRU Key of this element.
  335. Key K
  336. // The Value stored with this element.
  337. Value V
  338. // The time this element would be cleaned up, optional
  339. ExpiresAt time.Time
  340. // The expiry bucket item was put in, optional
  341. ExpireBucket uint8
  342. }
  343. // PrevEntry returns the previous list element or nil.
  344. func (e *Entry[K, V]) PrevEntry() *Entry[K, V] {
  345. if p := e.prev; e.list != nil && p != &e.list.root {
  346. return p
  347. }
  348. return nil
  349. }
  350. // LruList represents a doubly linked list.
  351. // The zero Value for LruList is an empty list ready to use.
  352. type LruList[K comparable, V any] struct {
  353. root Entry[K, V] // sentinel list element, only &root, root.prev, and root.next are used
  354. len int // current list Length excluding (this) sentinel element
  355. }
  356. // Init initializes or clears list l.
  357. func (l *LruList[K, V]) Init() *LruList[K, V] {
  358. l.root.next = &l.root
  359. l.root.prev = &l.root
  360. l.len = 0
  361. return l
  362. }
  363. // NewList returns an initialized list.
  364. func NewList[K comparable, V any]() *LruList[K, V] { return new(LruList[K, V]).Init() }
  365. // Length returns the number of elements of list l.
  366. // The complexity is O(1).
  367. func (l *LruList[K, V]) Length() int { return l.len }
  368. // Back returns the last element of list l or nil if the list is empty.
  369. func (l *LruList[K, V]) Back() *Entry[K, V] {
  370. if l.len == 0 {
  371. return nil
  372. }
  373. return l.root.prev
  374. }
  375. // lazyInit lazily initializes a zero List Value.
  376. func (l *LruList[K, V]) lazyInit() {
  377. if l.root.next == nil {
  378. l.Init()
  379. }
  380. }
  381. // insert inserts e after at, increments l.len, and returns e.
  382. func (l *LruList[K, V]) insert(e, at *Entry[K, V]) *Entry[K, V] {
  383. e.prev = at
  384. e.next = at.next
  385. e.prev.next = e
  386. e.next.prev = e
  387. e.list = l
  388. l.len++
  389. return e
  390. }
  391. // insertValue is a convenience wrapper for insert(&Entry{Value: v, ExpiresAt: ExpiresAt}, at).
  392. func (l *LruList[K, V]) insertValue(k K, v V, expiresAt time.Time, at *Entry[K, V]) *Entry[K, V] {
  393. return l.insert(&Entry[K, V]{Value: v, Key: k, ExpiresAt: expiresAt}, at)
  394. }
  395. // Remove removes e from its list, decrements l.len
  396. func (l *LruList[K, V]) Remove(e *Entry[K, V]) V {
  397. e.prev.next = e.next
  398. e.next.prev = e.prev
  399. e.next = nil // avoid memory leaks
  400. e.prev = nil // avoid memory leaks
  401. e.list = nil
  402. l.len--
  403. return e.Value
  404. }
  405. // move moves e to next to at.
  406. func (l *LruList[K, V]) move(e, at *Entry[K, V]) {
  407. if e == at {
  408. return
  409. }
  410. e.prev.next = e.next
  411. e.next.prev = e.prev
  412. e.prev = at
  413. e.next = at.next
  414. e.prev.next = e
  415. e.next.prev = e
  416. }
  417. // PushFront inserts a new element e with value v at the front of list l and returns e.
  418. func (l *LruList[K, V]) PushFront(k K, v V) *Entry[K, V] {
  419. l.lazyInit()
  420. return l.insertValue(k, v, time.Time{}, &l.root)
  421. }
  422. // PushFrontExpirable inserts a new expirable element e with Value v at the front of list l and returns e.
  423. func (l *LruList[K, V]) PushFrontExpirable(k K, v V, expiresAt time.Time) *Entry[K, V] {
  424. l.lazyInit()
  425. return l.insertValue(k, v, expiresAt, &l.root)
  426. }
  427. // MoveToFront moves element e to the front of list l.
  428. // If e is not an element of l, the list is not modified.
  429. // The element must not be nil.
  430. func (l *LruList[K, V]) MoveToFront(e *Entry[K, V]) {
  431. if e.list != l || l.root.next == e {
  432. return
  433. }
  434. // see comment in List.Remove about initialization of l
  435. l.move(e, &l.root)
  436. }