github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/services/cache/lru.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package cache
     5  
     6  import (
     7  	"container/list"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/vmihailenco/msgpack/v5"
    12  )
    13  
    14  // LRU is a thread-safe fixed size LRU cache.
    15  type LRU struct {
    16  	name                   string
    17  	size                   int
    18  	evictList              *list.List
    19  	items                  map[string]*list.Element
    20  	lock                   sync.RWMutex
    21  	defaultExpiry          time.Duration
    22  	invalidateClusterEvent string
    23  	currentGeneration      int64
    24  	len                    int
    25  }
    26  
    27  // LRUOptions contains options for initializing LRU cache
    28  type LRUOptions struct {
    29  	Name                   string
    30  	Size                   int
    31  	DefaultExpiry          time.Duration
    32  	InvalidateClusterEvent string
    33  }
    34  
    35  // entry is used to hold a value in the evictList.
    36  type entry struct {
    37  	key        string
    38  	value      []byte
    39  	expires    time.Time
    40  	generation int64
    41  }
    42  
    43  // NewLRU creates an LRU of the given size.
    44  func NewLRU(opts *LRUOptions) Cache {
    45  	return &LRU{
    46  		name:                   opts.Name,
    47  		size:                   opts.Size,
    48  		evictList:              list.New(),
    49  		items:                  make(map[string]*list.Element, opts.Size),
    50  		defaultExpiry:          opts.DefaultExpiry,
    51  		invalidateClusterEvent: opts.InvalidateClusterEvent,
    52  	}
    53  }
    54  
    55  // Purge is used to completely clear the cache.
    56  func (l *LRU) Purge() error {
    57  	l.lock.Lock()
    58  	defer l.lock.Unlock()
    59  
    60  	l.len = 0
    61  	l.currentGeneration++
    62  	return nil
    63  }
    64  
    65  // Set adds the given key and value to the store without an expiry. If the key already exists,
    66  // it will overwrite the previous value.
    67  func (l *LRU) Set(key string, value interface{}) error {
    68  	return l.SetWithExpiry(key, value, 0)
    69  }
    70  
    71  // SetWithDefaultExpiry adds the given key and value to the store with the default expiry. If
    72  // the key already exists, it will overwrite the previoous value
    73  func (l *LRU) SetWithDefaultExpiry(key string, value interface{}) error {
    74  	return l.SetWithExpiry(key, value, l.defaultExpiry)
    75  }
    76  
    77  // SetWithExpiry adds the given key and value to the cache with the given expiry. If the key
    78  // already exists, it will overwrite the previoous value
    79  func (l *LRU) SetWithExpiry(key string, value interface{}, ttl time.Duration) error {
    80  	l.lock.Lock()
    81  	defer l.lock.Unlock()
    82  	return l.set(key, value, ttl)
    83  }
    84  
    85  // Get the content stored in the cache for the given key, and decode it into the value interface.
    86  // return ErrKeyNotFound if the key is missing from the cache
    87  func (l *LRU) Get(key string, value interface{}) error {
    88  	l.lock.Lock()
    89  	defer l.lock.Unlock()
    90  	return l.get(key, value)
    91  }
    92  
    93  // Remove deletes the value for a key.
    94  func (l *LRU) Remove(key string) error {
    95  	l.lock.Lock()
    96  	defer l.lock.Unlock()
    97  
    98  	if ent, ok := l.items[key]; ok {
    99  		l.removeElement(ent)
   100  	}
   101  	return nil
   102  }
   103  
   104  // Keys returns a slice of the keys in the cache.
   105  func (l *LRU) Keys() ([]string, error) {
   106  	l.lock.RLock()
   107  	defer l.lock.RUnlock()
   108  
   109  	keys := make([]string, l.len)
   110  	i := 0
   111  	for ent := l.evictList.Back(); ent != nil; ent = ent.Prev() {
   112  		e := ent.Value.(*entry)
   113  		if e.generation == l.currentGeneration {
   114  			keys[i] = e.key
   115  			i++
   116  		}
   117  	}
   118  	return keys, nil
   119  }
   120  
   121  // Len returns the number of items in the cache.
   122  func (l *LRU) Len() (int, error) {
   123  	l.lock.RLock()
   124  	defer l.lock.RUnlock()
   125  	return l.len, nil
   126  }
   127  
   128  // GetInvalidateClusterEvent returns the cluster event configured when this cache was created.
   129  func (l *LRU) GetInvalidateClusterEvent() string {
   130  	return l.invalidateClusterEvent
   131  }
   132  
   133  // Name returns the name of the cache
   134  func (l *LRU) Name() string {
   135  	return l.name
   136  }
   137  
   138  func (l *LRU) set(key string, value interface{}, ttl time.Duration) error {
   139  	var expires time.Time
   140  	if ttl > 0 {
   141  		expires = time.Now().Add(ttl)
   142  	}
   143  
   144  	buf, err := msgpack.Marshal(value)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	// Check for existing item, ignoring expiry since we'd update anyway.
   150  	if ent, ok := l.items[key]; ok {
   151  		l.evictList.MoveToFront(ent)
   152  		e := ent.Value.(*entry)
   153  		e.value = buf
   154  		e.expires = expires
   155  		if e.generation != l.currentGeneration {
   156  			e.generation = l.currentGeneration
   157  			l.len++
   158  		}
   159  		return nil
   160  	}
   161  
   162  	// Add new item
   163  	ent := &entry{key, buf, expires, l.currentGeneration}
   164  	entry := l.evictList.PushFront(ent)
   165  	l.items[key] = entry
   166  	l.len++
   167  
   168  	if l.evictList.Len() > l.size {
   169  		l.removeElement(l.evictList.Back())
   170  	}
   171  	return nil
   172  }
   173  
   174  func (l *LRU) get(key string, value interface{}) error {
   175  	if ent, ok := l.items[key]; ok {
   176  		e := ent.Value.(*entry)
   177  
   178  		if e.generation != l.currentGeneration || (!e.expires.IsZero() && time.Now().After(e.expires)) {
   179  			l.removeElement(ent)
   180  			return ErrKeyNotFound
   181  		}
   182  
   183  		l.evictList.MoveToFront(ent)
   184  
   185  		return msgpack.Unmarshal(e.value, value)
   186  	}
   187  	return ErrKeyNotFound
   188  }
   189  
   190  func (l *LRU) removeElement(e *list.Element) {
   191  	l.evictList.Remove(e)
   192  	kv := e.Value.(*entry)
   193  	if kv.generation == l.currentGeneration {
   194  		l.len--
   195  	}
   196  	delete(l.items, kv.key)
   197  }