Files
mattermost/utils/lru.go
2018-02-28 12:07:11 -08:00

185 lines
4.1 KiB
Go

// This files was copied/modified from https://github.com/hashicorp/golang-lru
// which was (see below)
// This package provides a simple LRU cache. It is based on the
// LRU implementation in groupcache:
// https://github.com/golang/groupcache/tree/master/lru
package utils
import (
"container/list"
"sync"
"time"
)
// Caching Interface
type ObjectCache interface {
AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64)
AddWithDefaultExpires(key, value interface{})
Purge()
Get(key interface{}) (value interface{}, ok bool)
Remove(key interface{})
Len() int
Name() string
GetInvalidateClusterEvent() string
}
// Cache is a thread-safe fixed size LRU cache.
type Cache struct {
size int
evictList *list.List
items map[interface{}]*list.Element
lock sync.RWMutex
name string
defaultExpiry int64
invalidateClusterEvent string
currentGeneration int64
len int
}
// entry is used to hold a value in the evictList
type entry struct {
key interface{}
value interface{}
expireAtSecs int64
generation int64
}
// New creates an LRU of the given size
func NewLru(size int) *Cache {
return &Cache{
size: size,
evictList: list.New(),
items: make(map[interface{}]*list.Element, size),
}
}
func NewLruWithParams(size int, name string, defaultExpiry int64, invalidateClusterEvent string) *Cache {
lru := NewLru(size)
lru.name = name
lru.defaultExpiry = defaultExpiry
lru.invalidateClusterEvent = invalidateClusterEvent
return lru
}
// Purge is used to completely clear the cache
func (c *Cache) Purge() {
c.lock.Lock()
defer c.lock.Unlock()
c.len = 0
c.currentGeneration++
}
func (c *Cache) Add(key, value interface{}) {
c.AddWithExpiresInSecs(key, value, 0)
}
func (c *Cache) AddWithDefaultExpires(key, value interface{}) {
c.AddWithExpiresInSecs(key, value, c.defaultExpiry)
}
func (c *Cache) AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64) {
c.lock.Lock()
defer c.lock.Unlock()
if expireAtSecs > 0 {
expireAtSecs = (time.Now().UnixNano() / int64(time.Second)) + expireAtSecs
}
// Check for existing item
if ent, ok := c.items[key]; ok {
c.evictList.MoveToFront(ent)
e := ent.Value.(*entry)
e.value = value
e.expireAtSecs = expireAtSecs
if e.generation != c.currentGeneration {
e.generation = c.currentGeneration
c.len++
}
return
}
// Add new item
ent := &entry{key, value, expireAtSecs, c.currentGeneration}
entry := c.evictList.PushFront(ent)
c.items[key] = entry
c.len++
if c.evictList.Len() > c.size {
c.removeElement(c.evictList.Back())
}
}
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
c.lock.Lock()
defer c.lock.Unlock()
if ent, ok := c.items[key]; ok {
e := ent.Value.(*entry)
if e.generation != c.currentGeneration || (e.expireAtSecs > 0 && (time.Now().UnixNano()/int64(time.Second)) > e.expireAtSecs) {
c.removeElement(ent)
return nil, false
}
c.evictList.MoveToFront(ent)
return ent.Value.(*entry).value, true
}
return nil, false
}
func (c *Cache) Remove(key interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
if ent, ok := c.items[key]; ok {
c.removeElement(ent)
}
}
// Keys returns a slice of the keys in the cache, from oldest to newest.
func (c *Cache) Keys() []interface{} {
c.lock.RLock()
defer c.lock.RUnlock()
keys := make([]interface{}, c.len)
i := 0
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
e := ent.Value.(*entry)
if e.generation == c.currentGeneration {
keys[i] = e.key
i++
}
}
return keys
}
// Len returns the number of items in the cache.
func (c *Cache) Len() int {
c.lock.RLock()
defer c.lock.RUnlock()
return c.len
}
func (c *Cache) Name() string {
return c.name
}
func (c *Cache) GetInvalidateClusterEvent() string {
return c.invalidateClusterEvent
}
// removeElement is used to remove a given list element from the cache
func (c *Cache) removeElement(e *list.Element) {
c.evictList.Remove(e)
kv := e.Value.(*entry)
if kv.generation == c.currentGeneration {
c.len--
}
delete(c.items, kv.key)
}