github.com/bhojpur/cache@v0.0.4/pkg/engine/lru_cache.go (about) 1 package engine 2 3 // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved. 4 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 // THE SOFTWARE. 22 23 // The implementation borrows heavily from SmallLRUCache 24 // (originally by Nathan Schrenk). The object maintains a doubly-linked list of 25 // elements. When an element is accessed, it is promoted to the head of the 26 // list. When space is needed, the element at the tail of the list 27 // (the least recently used element) is evicted. 28 29 import ( 30 "container/list" 31 "sync" 32 "time" 33 ) 34 35 var _ Cache = &LRUCache{} 36 37 // LRUCache is a typical LRU cache implementation. If the cache 38 // reaches the capacity, the least recently used item is deleted from 39 // the cache. Note the capacity is not the number of items, but the 40 // total sum of the CachedSize() of each item. 41 type LRUCache struct { 42 mu sync.Mutex 43 44 // list & table contain *entry objects. 45 list *list.List 46 table map[string]*list.Element 47 cost func(interface{}) int64 48 49 size int64 50 capacity int64 51 evictions int64 52 } 53 54 // Item is what is stored in the cache 55 type Item struct { 56 Key string 57 Value interface{} 58 } 59 60 type entry struct { 61 key string 62 value interface{} 63 size int64 64 timeAccessed time.Time 65 } 66 67 // NewLRUCache creates a new empty cache with the given capacity. 68 func NewLRUCache(capacity int64, cost func(interface{}) int64) *LRUCache { 69 return &LRUCache{ 70 list: list.New(), 71 table: make(map[string]*list.Element), 72 capacity: capacity, 73 cost: cost, 74 } 75 } 76 77 // Get returns a value from the cache, and marks the entry as most 78 // recently used. 79 func (lru *LRUCache) Get(key string) (v interface{}, ok bool) { 80 lru.mu.Lock() 81 defer lru.mu.Unlock() 82 83 element := lru.table[key] 84 if element == nil { 85 return nil, false 86 } 87 lru.moveToFront(element) 88 return element.Value.(*entry).value, true 89 } 90 91 // Set sets a value in the cache. 92 func (lru *LRUCache) Set(key string, value interface{}) bool { 93 lru.mu.Lock() 94 defer lru.mu.Unlock() 95 96 if element := lru.table[key]; element != nil { 97 lru.updateInplace(element, value) 98 } else { 99 lru.addNew(key, value) 100 } 101 // the LRU cache cannot fail to insert items; it always returns true 102 return true 103 } 104 105 // Delete removes an entry from the cache, and returns if the entry existed. 106 func (lru *LRUCache) delete(key string) bool { 107 lru.mu.Lock() 108 defer lru.mu.Unlock() 109 110 element := lru.table[key] 111 if element == nil { 112 return false 113 } 114 115 lru.list.Remove(element) 116 delete(lru.table, key) 117 lru.size -= element.Value.(*entry).size 118 return true 119 } 120 121 // Delete removes an entry from the cache 122 func (lru *LRUCache) Delete(key string) { 123 lru.delete(key) 124 } 125 126 // Clear will clear the entire cache. 127 func (lru *LRUCache) Clear() { 128 lru.mu.Lock() 129 defer lru.mu.Unlock() 130 131 lru.list.Init() 132 lru.table = make(map[string]*list.Element) 133 lru.size = 0 134 } 135 136 // Len returns the size of the cache (in entries) 137 func (lru *LRUCache) Len() int { 138 lru.mu.Lock() 139 defer lru.mu.Unlock() 140 return lru.list.Len() 141 } 142 143 // SetCapacity will set the capacity of the cache. If the capacity is 144 // smaller, and the current cache size exceed that capacity, the cache 145 // will be shrank. 146 func (lru *LRUCache) SetCapacity(capacity int64) { 147 lru.mu.Lock() 148 defer lru.mu.Unlock() 149 150 lru.capacity = capacity 151 lru.checkCapacity() 152 } 153 154 // Wait is a no-op in the LRU cache 155 func (lru *LRUCache) Wait() {} 156 157 // UsedCapacity returns the size of the cache (in bytes) 158 func (lru *LRUCache) UsedCapacity() int64 { 159 return lru.size 160 } 161 162 // MaxCapacity returns the cache maximum capacity. 163 func (lru *LRUCache) MaxCapacity() int64 { 164 lru.mu.Lock() 165 defer lru.mu.Unlock() 166 return lru.capacity 167 } 168 169 // Evictions returns the number of evictions 170 func (lru *LRUCache) Evictions() int64 { 171 lru.mu.Lock() 172 defer lru.mu.Unlock() 173 return lru.evictions 174 } 175 176 // ForEach yields all the values for the cache, ordered from most recently 177 // used to least recently used. 178 func (lru *LRUCache) ForEach(callback func(value interface{}) bool) { 179 lru.mu.Lock() 180 defer lru.mu.Unlock() 181 182 for e := lru.list.Front(); e != nil; e = e.Next() { 183 v := e.Value.(*entry) 184 if !callback(v.value) { 185 break 186 } 187 } 188 } 189 190 // Items returns all the values for the cache, ordered from most recently 191 // used to least recently used. 192 func (lru *LRUCache) Items() []Item { 193 lru.mu.Lock() 194 defer lru.mu.Unlock() 195 196 items := make([]Item, 0, lru.list.Len()) 197 for e := lru.list.Front(); e != nil; e = e.Next() { 198 v := e.Value.(*entry) 199 items = append(items, Item{Key: v.key, Value: v.value}) 200 } 201 return items 202 } 203 204 func (lru *LRUCache) updateInplace(element *list.Element, value interface{}) { 205 valueSize := lru.cost(value) 206 sizeDiff := valueSize - element.Value.(*entry).size 207 element.Value.(*entry).value = value 208 element.Value.(*entry).size = valueSize 209 lru.size += sizeDiff 210 lru.moveToFront(element) 211 lru.checkCapacity() 212 } 213 214 func (lru *LRUCache) moveToFront(element *list.Element) { 215 lru.list.MoveToFront(element) 216 element.Value.(*entry).timeAccessed = time.Now() 217 } 218 219 func (lru *LRUCache) addNew(key string, value interface{}) { 220 newEntry := &entry{key, value, lru.cost(value), time.Now()} 221 element := lru.list.PushFront(newEntry) 222 lru.table[key] = element 223 lru.size += newEntry.size 224 lru.checkCapacity() 225 } 226 227 func (lru *LRUCache) checkCapacity() { 228 // Partially duplicated from Delete 229 for lru.size > lru.capacity { 230 delElem := lru.list.Back() 231 delValue := delElem.Value.(*entry) 232 lru.list.Remove(delElem) 233 delete(lru.table, delValue.key) 234 lru.size -= delValue.size 235 lru.evictions++ 236 } 237 }