github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+incompatible/utils/lru.go (about)

     1  // This files was copied/modified from https://github.com/hashicorp/golang-lru
     2  // which was (see below)
     3  
     4  // This package provides a simple LRU cache. It is based on the
     5  // LRU implementation in groupcache:
     6  // https://github.com/golang/groupcache/tree/master/lru
     7  
     8  package utils
     9  
    10  import (
    11  	"container/list"
    12  	"errors"
    13  	"sync"
    14  	"time"
    15  )
    16  
    17  // Caching Interface
    18  type ObjectCache interface {
    19  	AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64) bool
    20  	AddWithDefaultExpires(key, value interface{}) bool
    21  	Purge()
    22  	Get(key interface{}) (value interface{}, ok bool)
    23  	Remove(key interface{})
    24  	Len() int
    25  	Name() string
    26  	GetInvalidateClusterEvent() string
    27  }
    28  
    29  // Cache is a thread-safe fixed size LRU cache.
    30  type Cache struct {
    31  	size                   int
    32  	evictList              *list.List
    33  	items                  map[interface{}]*list.Element
    34  	lock                   sync.RWMutex
    35  	onEvicted              func(key interface{}, value interface{})
    36  	name                   string
    37  	defaultExpiry          int64
    38  	invalidateClusterEvent string
    39  }
    40  
    41  // entry is used to hold a value in the evictList
    42  type entry struct {
    43  	key          interface{}
    44  	value        interface{}
    45  	expireAtSecs int64
    46  }
    47  
    48  // New creates an LRU of the given size
    49  func NewLru(size int) *Cache {
    50  	cache, _ := NewLruWithEvict(size, nil)
    51  	return cache
    52  }
    53  
    54  func NewLruWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
    55  	if size <= 0 {
    56  		return nil, errors.New(T("utils.iru.with_evict"))
    57  	}
    58  	c := &Cache{
    59  		size:      size,
    60  		evictList: list.New(),
    61  		items:     make(map[interface{}]*list.Element, size),
    62  		onEvicted: onEvicted,
    63  	}
    64  	return c, nil
    65  }
    66  
    67  func NewLruWithParams(size int, name string, defaultExpiry int64, invalidateClusterEvent string) *Cache {
    68  	lru := NewLru(size)
    69  	lru.name = name
    70  	lru.defaultExpiry = defaultExpiry
    71  	lru.invalidateClusterEvent = invalidateClusterEvent
    72  	return lru
    73  }
    74  
    75  // Purge is used to completely clear the cache
    76  func (c *Cache) Purge() {
    77  	c.lock.Lock()
    78  	defer c.lock.Unlock()
    79  
    80  	if c.onEvicted != nil {
    81  		for k, v := range c.items {
    82  			c.onEvicted(k, v.Value)
    83  		}
    84  	}
    85  
    86  	c.evictList = list.New()
    87  	c.items = make(map[interface{}]*list.Element, c.size)
    88  }
    89  
    90  func (c *Cache) Add(key, value interface{}) bool {
    91  	return c.AddWithExpiresInSecs(key, value, 0)
    92  }
    93  
    94  func (c *Cache) AddWithDefaultExpires(key, value interface{}) bool {
    95  	return c.AddWithExpiresInSecs(key, value, c.defaultExpiry)
    96  }
    97  
    98  // Add adds a value to the cache.  Returns true if an eviction occurred.
    99  func (c *Cache) AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64) bool {
   100  	c.lock.Lock()
   101  	defer c.lock.Unlock()
   102  
   103  	if expireAtSecs > 0 {
   104  		expireAtSecs = (time.Now().UnixNano() / int64(time.Second)) + expireAtSecs
   105  	}
   106  
   107  	// Check for existing item
   108  	if ent, ok := c.items[key]; ok {
   109  		c.evictList.MoveToFront(ent)
   110  		ent.Value.(*entry).value = value
   111  		ent.Value.(*entry).expireAtSecs = expireAtSecs
   112  		return false
   113  	}
   114  
   115  	// Add new item
   116  	ent := &entry{key, value, expireAtSecs}
   117  	entry := c.evictList.PushFront(ent)
   118  	c.items[key] = entry
   119  
   120  	evict := c.evictList.Len() > c.size
   121  	// Verify size not exceeded
   122  	if evict {
   123  		c.removeOldest()
   124  	}
   125  	return evict
   126  }
   127  
   128  // Get looks up a key's value from the cache.
   129  func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
   130  	c.lock.Lock()
   131  	defer c.lock.Unlock()
   132  
   133  	if ent, ok := c.items[key]; ok {
   134  
   135  		if ent.Value.(*entry).expireAtSecs > 0 {
   136  			if (time.Now().UnixNano() / int64(time.Second)) > ent.Value.(*entry).expireAtSecs {
   137  				c.removeElement(ent)
   138  				return nil, false
   139  			}
   140  		}
   141  
   142  		c.evictList.MoveToFront(ent)
   143  		return ent.Value.(*entry).value, true
   144  	}
   145  	return
   146  }
   147  
   148  // Remove removes the provided key from the cache.
   149  func (c *Cache) Remove(key interface{}) {
   150  	c.lock.Lock()
   151  	defer c.lock.Unlock()
   152  
   153  	if ent, ok := c.items[key]; ok {
   154  		c.removeElement(ent)
   155  	}
   156  }
   157  
   158  // RemoveOldest removes the oldest item from the cache.
   159  func (c *Cache) RemoveOldest() {
   160  	c.lock.Lock()
   161  	defer c.lock.Unlock()
   162  	c.removeOldest()
   163  }
   164  
   165  // Keys returns a slice of the keys in the cache, from oldest to newest.
   166  func (c *Cache) Keys() []interface{} {
   167  	c.lock.RLock()
   168  	defer c.lock.RUnlock()
   169  
   170  	keys := make([]interface{}, len(c.items))
   171  	ent := c.evictList.Back()
   172  	i := 0
   173  	for ent != nil {
   174  		keys[i] = ent.Value.(*entry).key
   175  		ent = ent.Prev()
   176  		i++
   177  	}
   178  
   179  	return keys
   180  }
   181  
   182  // Len returns the number of items in the cache.
   183  func (c *Cache) Len() int {
   184  	c.lock.RLock()
   185  	defer c.lock.RUnlock()
   186  	return c.evictList.Len()
   187  }
   188  
   189  func (c *Cache) Name() string {
   190  	return c.name
   191  }
   192  
   193  func (c *Cache) GetInvalidateClusterEvent() string {
   194  	return c.invalidateClusterEvent
   195  }
   196  
   197  // removeOldest removes the oldest item from the cache.
   198  func (c *Cache) removeOldest() {
   199  	ent := c.evictList.Back()
   200  	if ent != nil {
   201  		c.removeElement(ent)
   202  	}
   203  }
   204  
   205  // removeElement is used to remove a given list element from the cache
   206  func (c *Cache) removeElement(e *list.Element) {
   207  	c.evictList.Remove(e)
   208  	kv := e.Value.(*entry)
   209  	delete(c.items, kv.key)
   210  	if c.onEvicted != nil {
   211  		c.onEvicted(kv.key, kv.value)
   212  	}
   213  }