| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- package lru
- import (
- "sync"
- )
- const (
- // DefaultEvictedBufferSize defines the default buffer size to store evicted key/val
- DefaultEvictedBufferSize = 16
- )
- // SafeLRU is a thread-safe fixed size LRU cache.
- type SafeLRU struct {
- lru LRU
- evictedKeys, evictedValues []any
- onEvictedCB func(k, v any)
- lock sync.RWMutex
- }
- // NewSafe creates an LRU of the given size.
- func NewSafe(size int) LRU {
- return NewWithEvict(size, nil)
- }
- // NewWithEvict constructs a fixed size cache with the given eviction
- // callback.
- func NewWithEvict(size int, onEvicted func(key, value any)) LRU {
- // create a cache with default settings
- c := &SafeLRU{
- onEvictedCB: onEvicted,
- }
- if onEvicted != nil {
- c.initEvictBuffers()
- onEvicted = c.onEvicted
- }
- c.lru = NewSimple(size, onEvicted)
- return c
- }
- func (c *SafeLRU) initEvictBuffers() {
- c.evictedKeys = make([]any, 0, DefaultEvictedBufferSize)
- c.evictedValues = make([]any, 0, DefaultEvictedBufferSize)
- }
- // onEvicted save evicted key/val and sent in externally registered callback
- // outside of critical section
- func (c *SafeLRU) onEvicted(k, v any) {
- c.evictedKeys = append(c.evictedKeys, k)
- c.evictedValues = append(c.evictedValues, v)
- }
- // Purge is used to completely clear the cache.
- func (c *SafeLRU) Purge() {
- var ks, vs []any
- c.lock.Lock()
- c.lru.Purge()
- if c.onEvictedCB != nil && len(c.evictedKeys) > 0 {
- ks, vs = c.evictedKeys, c.evictedValues
- c.initEvictBuffers()
- }
- c.lock.Unlock()
- // invoke callback outside of critical section
- if c.onEvictedCB != nil {
- for i := 0; i < len(ks); i++ {
- c.onEvictedCB(ks[i], vs[i])
- }
- }
- }
- // Add adds a value to the cache. Returns true if an eviction occurred.
- func (c *SafeLRU) Add(key, value any) (evicted bool) {
- var k, v any
- c.lock.Lock()
- evicted = c.lru.Add(key, value)
- if c.onEvictedCB != nil && evicted {
- k, v = c.evictedKeys[0], c.evictedValues[0]
- c.evictedKeys, c.evictedValues = c.evictedKeys[:0], c.evictedValues[:0]
- }
- c.lock.Unlock()
- if c.onEvictedCB != nil && evicted {
- c.onEvictedCB(k, v)
- }
- return
- }
- // Get looks up a key's value from the cache.
- func (c *SafeLRU) Get(key any) (value any, ok bool) {
- c.lock.Lock()
- value, ok = c.lru.Get(key)
- c.lock.Unlock()
- return value, ok
- }
- // Contains checks if a key is in the cache, without updating the
- // recent-ness or deleting it for being stale.
- func (c *SafeLRU) Contains(key any) bool {
- c.lock.RLock()
- containKey := c.lru.Contains(key)
- c.lock.RUnlock()
- return containKey
- }
- // Peek returns the key value (or undefined if not found) without updating
- // the "recently used"-ness of the key.
- func (c *SafeLRU) Peek(key any) (value any, ok bool) {
- c.lock.RLock()
- value, ok = c.lru.Peek(key)
- c.lock.RUnlock()
- return value, ok
- }
- // ContainsOrAdd checks if a key is in the cache without updating the
- // recent-ness or deleting it for being stale, and if not, adds the value.
- // Returns whether found and whether an eviction occurred.
- func (c *SafeLRU) ContainsOrAdd(key, value any) (ok, evicted bool) {
- var k, v any
- c.lock.Lock()
- if c.lru.Contains(key) {
- c.lock.Unlock()
- return true, false
- }
- evicted = c.lru.Add(key, value)
- if c.onEvictedCB != nil && evicted {
- k, v = c.evictedKeys[0], c.evictedValues[0]
- c.evictedKeys, c.evictedValues = c.evictedKeys[:0], c.evictedValues[:0]
- }
- c.lock.Unlock()
- if c.onEvictedCB != nil && evicted {
- c.onEvictedCB(k, v)
- }
- return false, evicted
- }
- // PeekOrAdd checks if a key is in the cache without updating the
- // recent-ness or deleting it for being stale, and if not, adds the value.
- // Returns whether found and whether an eviction occurred.
- func (c *SafeLRU) PeekOrAdd(key, value any) (previous any, ok, evicted bool) {
- var k, v any
- c.lock.Lock()
- previous, ok = c.lru.Peek(key)
- if ok {
- c.lock.Unlock()
- return previous, true, false
- }
- evicted = c.lru.Add(key, value)
- if c.onEvictedCB != nil && evicted {
- k, v = c.evictedKeys[0], c.evictedValues[0]
- c.evictedKeys, c.evictedValues = c.evictedKeys[:0], c.evictedValues[:0]
- }
- c.lock.Unlock()
- if c.onEvictedCB != nil && evicted {
- c.onEvictedCB(k, v)
- }
- return nil, false, evicted
- }
- // Remove removes the provided key from the cache.
- func (c *SafeLRU) Remove(key any) (present bool) {
- var k, v any
- c.lock.Lock()
- present = c.lru.Remove(key)
- if c.onEvictedCB != nil && present {
- k, v = c.evictedKeys[0], c.evictedValues[0]
- c.evictedKeys, c.evictedValues = c.evictedKeys[:0], c.evictedValues[:0]
- }
- c.lock.Unlock()
- if c.onEvictedCB != nil && present {
- c.onEvicted(k, v)
- }
- return
- }
- // Resize changes the cache size.
- func (c *SafeLRU) Resize(size int) (evicted int) {
- var ks, vs []any
- c.lock.Lock()
- evicted = c.lru.Resize(size)
- if c.onEvictedCB != nil && evicted > 0 {
- ks, vs = c.evictedKeys, c.evictedValues
- c.initEvictBuffers()
- }
- c.lock.Unlock()
- if c.onEvictedCB != nil && evicted > 0 {
- for i := 0; i < len(ks); i++ {
- c.onEvictedCB(ks[i], vs[i])
- }
- }
- return evicted
- }
- // RemoveOldest removes the oldest item from the cache.
- func (c *SafeLRU) RemoveOldest() (key, value any, ok bool) {
- var k, v any
- c.lock.Lock()
- key, value, ok = c.lru.RemoveOldest()
- if c.onEvictedCB != nil && ok {
- k, v = c.evictedKeys[0], c.evictedValues[0]
- c.evictedKeys, c.evictedValues = c.evictedKeys[:0], c.evictedValues[:0]
- }
- c.lock.Unlock()
- if c.onEvictedCB != nil && ok {
- c.onEvictedCB(k, v)
- }
- return
- }
- // GetOldest returns the oldest entry
- func (c *SafeLRU) GetOldest() (key, value any, ok bool) {
- c.lock.RLock()
- key, value, ok = c.lru.GetOldest()
- c.lock.RUnlock()
- return
- }
- // Keys returns a slice of the keys in the cache, from oldest to newest.
- func (c *SafeLRU) Keys() []any {
- c.lock.RLock()
- keys := c.lru.Keys()
- c.lock.RUnlock()
- return keys
- }
- // Values returns a slice of the values in the cache, from oldest to newest.
- func (c *SafeLRU) Values() []any {
- c.lock.RLock()
- keys := c.lru.Values()
- c.lock.RUnlock()
- return keys
- }
- // Len returns the number of items in the cache.
- func (c *SafeLRU) Len() int {
- c.lock.RLock()
- length := c.lru.Len()
- c.lock.RUnlock()
- return length
- }
|