lru.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. // Copyright (c) HashiCorp, Inc.
  2. // SPDX-License-Identifier: MPL-2.0
  3. package lru
  4. import (
  5. "sync"
  6. "github.com/hashicorp/golang-lru/v2/simplelru"
  7. )
  8. const (
  9. // DefaultEvictedBufferSize defines the default buffer size to store evicted key/val
  10. DefaultEvictedBufferSize = 16
  11. )
  12. // Cache is a thread-safe fixed size LRU cache.
  13. type Cache[K comparable, V any] struct {
  14. lru *simplelru.LRU[K, V]
  15. evictedKeys []K
  16. evictedVals []V
  17. onEvictedCB func(k K, v V)
  18. lock sync.RWMutex
  19. }
  20. // New creates an LRU of the given size.
  21. func New[K comparable, V any](size int) (*Cache[K, V], error) {
  22. return NewWithEvict[K, V](size, nil)
  23. }
  24. // NewWithEvict constructs a fixed size cache with the given eviction
  25. // callback.
  26. func NewWithEvict[K comparable, V any](size int, onEvicted func(key K, value V)) (c *Cache[K, V], err error) {
  27. // create a cache with default settings
  28. c = &Cache[K, V]{
  29. onEvictedCB: onEvicted,
  30. }
  31. if onEvicted != nil {
  32. c.initEvictBuffers()
  33. onEvicted = c.onEvicted
  34. }
  35. c.lru, err = simplelru.NewLRU(size, onEvicted)
  36. return
  37. }
  38. func (c *Cache[K, V]) initEvictBuffers() {
  39. c.evictedKeys = make([]K, 0, DefaultEvictedBufferSize)
  40. c.evictedVals = make([]V, 0, DefaultEvictedBufferSize)
  41. }
  42. // onEvicted save evicted key/val and sent in externally registered callback
  43. // outside of critical section
  44. func (c *Cache[K, V]) onEvicted(k K, v V) {
  45. c.evictedKeys = append(c.evictedKeys, k)
  46. c.evictedVals = append(c.evictedVals, v)
  47. }
  48. // Purge is used to completely clear the cache.
  49. func (c *Cache[K, V]) Purge() {
  50. var ks []K
  51. var vs []V
  52. c.lock.Lock()
  53. c.lru.Purge()
  54. if c.onEvictedCB != nil && len(c.evictedKeys) > 0 {
  55. ks, vs = c.evictedKeys, c.evictedVals
  56. c.initEvictBuffers()
  57. }
  58. c.lock.Unlock()
  59. // invoke callback outside of critical section
  60. if c.onEvictedCB != nil {
  61. for i := 0; i < len(ks); i++ {
  62. c.onEvictedCB(ks[i], vs[i])
  63. }
  64. }
  65. }
  66. // Add adds a value to the cache. Returns true if an eviction occurred.
  67. func (c *Cache[K, V]) Add(key K, value V) (evicted bool) {
  68. var k K
  69. var v V
  70. c.lock.Lock()
  71. evicted = c.lru.Add(key, value)
  72. if c.onEvictedCB != nil && evicted {
  73. k, v = c.evictedKeys[0], c.evictedVals[0]
  74. c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
  75. }
  76. c.lock.Unlock()
  77. if c.onEvictedCB != nil && evicted {
  78. c.onEvictedCB(k, v)
  79. }
  80. return
  81. }
  82. // Get looks up a key's value from the cache.
  83. func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
  84. c.lock.Lock()
  85. value, ok = c.lru.Get(key)
  86. c.lock.Unlock()
  87. return value, ok
  88. }
  89. // Contains checks if a key is in the cache, without updating the
  90. // recent-ness or deleting it for being stale.
  91. func (c *Cache[K, V]) Contains(key K) bool {
  92. c.lock.RLock()
  93. containKey := c.lru.Contains(key)
  94. c.lock.RUnlock()
  95. return containKey
  96. }
  97. // Peek returns the key value (or undefined if not found) without updating
  98. // the "recently used"-ness of the key.
  99. func (c *Cache[K, V]) Peek(key K) (value V, ok bool) {
  100. c.lock.RLock()
  101. value, ok = c.lru.Peek(key)
  102. c.lock.RUnlock()
  103. return value, ok
  104. }
  105. // ContainsOrAdd checks if a key is in the cache without updating the
  106. // recent-ness or deleting it for being stale, and if not, adds the value.
  107. // Returns whether found and whether an eviction occurred.
  108. func (c *Cache[K, V]) ContainsOrAdd(key K, value V) (ok, evicted bool) {
  109. var k K
  110. var v V
  111. c.lock.Lock()
  112. if c.lru.Contains(key) {
  113. c.lock.Unlock()
  114. return true, false
  115. }
  116. evicted = c.lru.Add(key, value)
  117. if c.onEvictedCB != nil && evicted {
  118. k, v = c.evictedKeys[0], c.evictedVals[0]
  119. c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
  120. }
  121. c.lock.Unlock()
  122. if c.onEvictedCB != nil && evicted {
  123. c.onEvictedCB(k, v)
  124. }
  125. return false, evicted
  126. }
  127. // PeekOrAdd checks if a key is in the cache without updating the
  128. // recent-ness or deleting it for being stale, and if not, adds the value.
  129. // Returns whether found and whether an eviction occurred.
  130. func (c *Cache[K, V]) PeekOrAdd(key K, value V) (previous V, ok, evicted bool) {
  131. var k K
  132. var v V
  133. c.lock.Lock()
  134. previous, ok = c.lru.Peek(key)
  135. if ok {
  136. c.lock.Unlock()
  137. return previous, true, false
  138. }
  139. evicted = c.lru.Add(key, value)
  140. if c.onEvictedCB != nil && evicted {
  141. k, v = c.evictedKeys[0], c.evictedVals[0]
  142. c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
  143. }
  144. c.lock.Unlock()
  145. if c.onEvictedCB != nil && evicted {
  146. c.onEvictedCB(k, v)
  147. }
  148. return
  149. }
  150. // Remove removes the provided key from the cache.
  151. func (c *Cache[K, V]) Remove(key K) (present bool) {
  152. var k K
  153. var v V
  154. c.lock.Lock()
  155. present = c.lru.Remove(key)
  156. if c.onEvictedCB != nil && present {
  157. k, v = c.evictedKeys[0], c.evictedVals[0]
  158. c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
  159. }
  160. c.lock.Unlock()
  161. if c.onEvictedCB != nil && present {
  162. c.onEvictedCB(k, v)
  163. }
  164. return
  165. }
  166. // Resize changes the cache size.
  167. func (c *Cache[K, V]) Resize(size int) (evicted int) {
  168. var ks []K
  169. var vs []V
  170. c.lock.Lock()
  171. evicted = c.lru.Resize(size)
  172. if c.onEvictedCB != nil && evicted > 0 {
  173. ks, vs = c.evictedKeys, c.evictedVals
  174. c.initEvictBuffers()
  175. }
  176. c.lock.Unlock()
  177. if c.onEvictedCB != nil && evicted > 0 {
  178. for i := 0; i < len(ks); i++ {
  179. c.onEvictedCB(ks[i], vs[i])
  180. }
  181. }
  182. return evicted
  183. }
  184. // RemoveOldest removes the oldest item from the cache.
  185. func (c *Cache[K, V]) RemoveOldest() (key K, value V, ok bool) {
  186. var k K
  187. var v V
  188. c.lock.Lock()
  189. key, value, ok = c.lru.RemoveOldest()
  190. if c.onEvictedCB != nil && ok {
  191. k, v = c.evictedKeys[0], c.evictedVals[0]
  192. c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
  193. }
  194. c.lock.Unlock()
  195. if c.onEvictedCB != nil && ok {
  196. c.onEvictedCB(k, v)
  197. }
  198. return
  199. }
  200. // GetOldest returns the oldest entry
  201. func (c *Cache[K, V]) GetOldest() (key K, value V, ok bool) {
  202. c.lock.RLock()
  203. key, value, ok = c.lru.GetOldest()
  204. c.lock.RUnlock()
  205. return
  206. }
  207. // Keys returns a slice of the keys in the cache, from oldest to newest.
  208. func (c *Cache[K, V]) Keys() []K {
  209. c.lock.RLock()
  210. keys := c.lru.Keys()
  211. c.lock.RUnlock()
  212. return keys
  213. }
  214. // Values returns a slice of the values in the cache, from oldest to newest.
  215. func (c *Cache[K, V]) Values() []V {
  216. c.lock.RLock()
  217. values := c.lru.Values()
  218. c.lock.RUnlock()
  219. return values
  220. }
  221. // Len returns the number of items in the cache.
  222. func (c *Cache[K, V]) Len() int {
  223. c.lock.RLock()
  224. length := c.lru.Len()
  225. c.lock.RUnlock()
  226. return length
  227. }