2016-06-20 14:47:10 +00:00
|
|
|
package lru
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/hashicorp/golang-lru/simplelru"
|
|
|
|
)
|
|
|
|
|
2021-06-28 06:53:50 +00:00
|
|
|
const (
|
|
|
|
// DefaultEvictedBufferSize defines the default buffer size to store evicted key/val
|
|
|
|
DefaultEvictedBufferSize = 16
|
|
|
|
)
|
|
|
|
|
2016-06-20 14:47:10 +00:00
|
|
|
// Cache is a thread-safe fixed size LRU cache.
|
|
|
|
type Cache struct {
|
2021-06-28 06:53:50 +00:00
|
|
|
lru *simplelru.LRU
|
|
|
|
evictedKeys, evictedVals []interface{}
|
|
|
|
onEvictedCB func(k, v interface{})
|
|
|
|
lock sync.RWMutex
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
2019-06-09 07:24:20 +00:00
|
|
|
// New creates an LRU of the given size.
|
2016-06-20 14:47:10 +00:00
|
|
|
func New(size int) (*Cache, error) {
|
|
|
|
return NewWithEvict(size, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewWithEvict constructs a fixed size cache with the given eviction
|
|
|
|
// callback.
|
2021-06-28 06:53:50 +00:00
|
|
|
func NewWithEvict(size int, onEvicted func(key, value interface{})) (c *Cache, err error) {
|
|
|
|
// create a cache with default settings
|
|
|
|
c = &Cache{
|
|
|
|
onEvictedCB: onEvicted,
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
2021-06-28 06:53:50 +00:00
|
|
|
if onEvicted != nil {
|
|
|
|
c.initEvictBuffers()
|
|
|
|
onEvicted = c.onEvicted
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
2021-06-28 06:53:50 +00:00
|
|
|
c.lru, err = simplelru.NewLRU(size, onEvicted)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Cache) initEvictBuffers() {
|
|
|
|
c.evictedKeys = make([]interface{}, 0, DefaultEvictedBufferSize)
|
|
|
|
c.evictedVals = make([]interface{}, 0, DefaultEvictedBufferSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
// onEvicted save evicted key/val and sent in externally registered callback
|
|
|
|
// outside of critical section
|
|
|
|
func (c *Cache) onEvicted(k, v interface{}) {
|
|
|
|
c.evictedKeys = append(c.evictedKeys, k)
|
|
|
|
c.evictedVals = append(c.evictedVals, v)
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
2019-06-09 07:24:20 +00:00
|
|
|
// Purge is used to completely clear the cache.
|
2016-06-20 14:47:10 +00:00
|
|
|
func (c *Cache) Purge() {
|
2021-06-28 06:53:50 +00:00
|
|
|
var ks, vs []interface{}
|
2016-06-20 14:47:10 +00:00
|
|
|
c.lock.Lock()
|
|
|
|
c.lru.Purge()
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && len(c.evictedKeys) > 0 {
|
|
|
|
ks, vs = c.evictedKeys, c.evictedVals
|
|
|
|
c.initEvictBuffers()
|
|
|
|
}
|
2016-06-20 14:47:10 +00:00
|
|
|
c.lock.Unlock()
|
2021-06-28 06:53:50 +00:00
|
|
|
// invoke callback outside of critical section
|
|
|
|
if c.onEvictedCB != nil {
|
|
|
|
for i := 0; i < len(ks); i++ {
|
|
|
|
c.onEvictedCB(ks[i], vs[i])
|
|
|
|
}
|
|
|
|
}
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
2021-06-16 20:19:45 +00:00
|
|
|
// Add adds a value to the cache. Returns true if an eviction occurred.
|
2019-06-09 07:24:20 +00:00
|
|
|
func (c *Cache) Add(key, value interface{}) (evicted bool) {
|
2021-06-28 06:53:50 +00:00
|
|
|
var k, v interface{}
|
2016-06-20 14:47:10 +00:00
|
|
|
c.lock.Lock()
|
2019-06-09 07:24:20 +00:00
|
|
|
evicted = c.lru.Add(key, value)
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && evicted {
|
|
|
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
|
|
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
|
|
|
}
|
2019-06-09 07:24:20 +00:00
|
|
|
c.lock.Unlock()
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && evicted {
|
|
|
|
c.onEvictedCB(k, v)
|
|
|
|
}
|
|
|
|
return
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get looks up a key's value from the cache.
|
2019-06-09 07:24:20 +00:00
|
|
|
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
|
2016-06-20 14:47:10 +00:00
|
|
|
c.lock.Lock()
|
2019-06-09 07:24:20 +00:00
|
|
|
value, ok = c.lru.Get(key)
|
|
|
|
c.lock.Unlock()
|
|
|
|
return value, ok
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
2019-06-09 07:24:20 +00:00
|
|
|
// Contains checks if a key is in the cache, without updating the
|
|
|
|
// recent-ness or deleting it for being stale.
|
2016-06-20 14:47:10 +00:00
|
|
|
func (c *Cache) Contains(key interface{}) bool {
|
|
|
|
c.lock.RLock()
|
2019-06-09 07:24:20 +00:00
|
|
|
containKey := c.lru.Contains(key)
|
|
|
|
c.lock.RUnlock()
|
|
|
|
return containKey
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
2019-06-09 07:24:20 +00:00
|
|
|
// Peek returns the key value (or undefined if not found) without updating
|
2016-06-20 14:47:10 +00:00
|
|
|
// the "recently used"-ness of the key.
|
2019-06-09 07:24:20 +00:00
|
|
|
func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
|
2016-06-20 14:47:10 +00:00
|
|
|
c.lock.RLock()
|
2019-06-09 07:24:20 +00:00
|
|
|
value, ok = c.lru.Peek(key)
|
|
|
|
c.lock.RUnlock()
|
|
|
|
return value, ok
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
2021-06-16 20:19:45 +00:00
|
|
|
// 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.
|
2016-06-20 14:47:10 +00:00
|
|
|
// Returns whether found and whether an eviction occurred.
|
2019-06-09 07:24:20 +00:00
|
|
|
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) {
|
2021-06-28 06:53:50 +00:00
|
|
|
var k, v interface{}
|
2016-06-20 14:47:10 +00:00
|
|
|
c.lock.Lock()
|
|
|
|
if c.lru.Contains(key) {
|
2021-06-28 06:53:50 +00:00
|
|
|
c.lock.Unlock()
|
2016-06-20 14:47:10 +00:00
|
|
|
return true, false
|
|
|
|
}
|
2019-06-09 07:24:20 +00:00
|
|
|
evicted = c.lru.Add(key, value)
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && evicted {
|
|
|
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
|
|
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
|
|
|
}
|
|
|
|
c.lock.Unlock()
|
|
|
|
if c.onEvictedCB != nil && evicted {
|
|
|
|
c.onEvictedCB(k, v)
|
|
|
|
}
|
2019-06-09 07:24:20 +00:00
|
|
|
return false, evicted
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
2021-06-16 20:19:45 +00:00
|
|
|
// 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 *Cache) PeekOrAdd(key, value interface{}) (previous interface{}, ok, evicted bool) {
|
2021-06-28 06:53:50 +00:00
|
|
|
var k, v interface{}
|
2021-06-16 20:19:45 +00:00
|
|
|
c.lock.Lock()
|
|
|
|
previous, ok = c.lru.Peek(key)
|
|
|
|
if ok {
|
2021-06-28 06:53:50 +00:00
|
|
|
c.lock.Unlock()
|
2021-06-16 20:19:45 +00:00
|
|
|
return previous, true, false
|
|
|
|
}
|
|
|
|
evicted = c.lru.Add(key, value)
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && evicted {
|
|
|
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
|
|
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
|
|
|
}
|
|
|
|
c.lock.Unlock()
|
|
|
|
if c.onEvictedCB != nil && evicted {
|
|
|
|
c.onEvictedCB(k, v)
|
|
|
|
}
|
2021-06-16 20:19:45 +00:00
|
|
|
return nil, false, evicted
|
|
|
|
}
|
|
|
|
|
2016-06-20 14:47:10 +00:00
|
|
|
// Remove removes the provided key from the cache.
|
2021-06-16 20:19:45 +00:00
|
|
|
func (c *Cache) Remove(key interface{}) (present bool) {
|
2021-06-28 06:53:50 +00:00
|
|
|
var k, v interface{}
|
2016-06-20 14:47:10 +00:00
|
|
|
c.lock.Lock()
|
2021-06-16 20:19:45 +00:00
|
|
|
present = c.lru.Remove(key)
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && present {
|
|
|
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
|
|
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
|
|
|
}
|
2016-06-20 14:47:10 +00:00
|
|
|
c.lock.Unlock()
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && present {
|
|
|
|
c.onEvicted(k, v)
|
|
|
|
}
|
2021-06-16 20:19:45 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resize changes the cache size.
|
|
|
|
func (c *Cache) Resize(size int) (evicted int) {
|
2021-06-28 06:53:50 +00:00
|
|
|
var ks, vs []interface{}
|
2021-06-16 20:19:45 +00:00
|
|
|
c.lock.Lock()
|
|
|
|
evicted = c.lru.Resize(size)
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && evicted > 0 {
|
|
|
|
ks, vs = c.evictedKeys, c.evictedVals
|
|
|
|
c.initEvictBuffers()
|
|
|
|
}
|
2021-06-16 20:19:45 +00:00
|
|
|
c.lock.Unlock()
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && evicted > 0 {
|
|
|
|
for i := 0; i < len(ks); i++ {
|
|
|
|
c.onEvictedCB(ks[i], vs[i])
|
|
|
|
}
|
|
|
|
}
|
2021-06-16 20:19:45 +00:00
|
|
|
return evicted
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveOldest removes the oldest item from the cache.
|
2021-06-28 06:53:50 +00:00
|
|
|
func (c *Cache) RemoveOldest() (key, value interface{}, ok bool) {
|
|
|
|
var k, v interface{}
|
2021-06-16 20:19:45 +00:00
|
|
|
c.lock.Lock()
|
|
|
|
key, value, ok = c.lru.RemoveOldest()
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && ok {
|
|
|
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
|
|
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
|
|
|
}
|
2021-06-16 20:19:45 +00:00
|
|
|
c.lock.Unlock()
|
2021-06-28 06:53:50 +00:00
|
|
|
if c.onEvictedCB != nil && ok {
|
|
|
|
c.onEvictedCB(k, v)
|
|
|
|
}
|
2021-06-16 20:19:45 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetOldest returns the oldest entry
|
2021-06-28 06:53:50 +00:00
|
|
|
func (c *Cache) GetOldest() (key, value interface{}, ok bool) {
|
|
|
|
c.lock.RLock()
|
2021-06-16 20:19:45 +00:00
|
|
|
key, value, ok = c.lru.GetOldest()
|
2021-06-28 06:53:50 +00:00
|
|
|
c.lock.RUnlock()
|
2021-06-16 20:19:45 +00:00
|
|
|
return
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
|
|
|
func (c *Cache) Keys() []interface{} {
|
|
|
|
c.lock.RLock()
|
2019-06-09 07:24:20 +00:00
|
|
|
keys := c.lru.Keys()
|
|
|
|
c.lock.RUnlock()
|
|
|
|
return keys
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Len returns the number of items in the cache.
|
|
|
|
func (c *Cache) Len() int {
|
|
|
|
c.lock.RLock()
|
2019-06-09 07:24:20 +00:00
|
|
|
length := c.lru.Len()
|
|
|
|
c.lock.RUnlock()
|
|
|
|
return length
|
2016-06-20 14:47:10 +00:00
|
|
|
}
|