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 }