github.com/coyove/common@v0.0.0-20240403014525-f70e643f9de8/lru/lru.go (about) 1 package lru 2 3 import ( 4 "container/list" 5 "fmt" 6 "sync" 7 ) 8 9 type Cache struct { 10 // OnEvicted is called when an entry is going to be purged from the cache. 11 OnEvicted func(key Key, value interface{}) 12 13 maxWeight int64 14 curWeight int64 15 16 ll *list.List 17 cache map[interface{}]*list.Element 18 19 sync.Mutex 20 } 21 22 // A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators 23 type Key interface{} 24 25 type entry struct { 26 key Key 27 value interface{} 28 hits int64 29 weight int64 30 } 31 32 var ErrWeightTooBig = fmt.Errorf("weight can't be held by the cache") 33 34 // NewCache creates a new Cache. 35 func NewCache(maxWeight int64) *Cache { 36 return &Cache{ 37 maxWeight: maxWeight, 38 ll: list.New(), 39 cache: make(map[interface{}]*list.Element), 40 } 41 } 42 43 // Clear clears the cache 44 func (c *Cache) Clear() { 45 c.Lock() 46 c.ll = list.New() 47 c.cache = make(map[interface{}]*list.Element) 48 c.Unlock() 49 } 50 51 // Info iterates the cache 52 func (c *Cache) Info(callback func(Key, interface{}, int64, int64)) { 53 c.Lock() 54 55 for f := c.ll.Front(); f != nil; f = f.Next() { 56 e := f.Value.(*entry) 57 callback(e.key, e.value, e.hits, e.weight) 58 } 59 60 c.Unlock() 61 } 62 63 // Add adds a value to the cache with weight = 1. 64 func (c *Cache) Add(key Key, value interface{}) error { 65 return c.AddWeight(key, value, 1) 66 } 67 68 // AddWeight adds a value to the cache with weight. 69 func (c *Cache) AddWeight(key Key, value interface{}, weight int64) error { 70 if weight > c.maxWeight || weight < 1 { 71 return ErrWeightTooBig 72 } 73 74 c.Lock() 75 defer c.Unlock() 76 77 controlWeight := func() { 78 if c.maxWeight == 0 { 79 return 80 } 81 82 for c.curWeight > c.maxWeight { 83 if ele := c.ll.Back(); ele != nil { 84 c.removeElement(ele, true) 85 } else { 86 panic("shouldn't happen") 87 } 88 } 89 // Since weight <= c.maxWeight, we will always reach here without problems 90 } 91 92 if ee, ok := c.cache[key]; ok { 93 e := ee.Value.(*entry) 94 c.ll.MoveToFront(ee) 95 diff := weight - e.weight 96 e.weight = weight 97 e.value = value 98 e.hits++ 99 100 c.curWeight += diff 101 controlWeight() 102 return nil 103 } 104 105 c.curWeight += weight 106 ele := c.ll.PushFront(&entry{key, value, 1, weight}) 107 c.cache[key] = ele 108 controlWeight() 109 110 if c.curWeight < 0 { 111 panic("too many entries, really?") 112 } 113 114 return nil 115 } 116 117 // Get gets a key 118 func (c *Cache) Get(key Key) (value interface{}, ok bool) { 119 c.Lock() 120 defer c.Unlock() 121 122 if ele, hit := c.cache[key]; hit { 123 e := ele.Value.(*entry) 124 e.hits++ 125 c.ll.MoveToFront(ele) 126 return e.value, true 127 } 128 129 return 130 } 131 132 // GetEx returns the extra info of the given key 133 func (c *Cache) GetEx(key Key) (hits int64, weight int64, ok bool) { 134 c.Lock() 135 defer c.Unlock() 136 137 if ele, hit := c.cache[key]; hit { 138 return ele.Value.(*entry).hits, ele.Value.(*entry).weight, true 139 } 140 141 return 142 } 143 144 // Remove removes the given key from the cache. 145 func (c *Cache) Remove(key Key) { 146 c.Lock() 147 c.remove(key, true) 148 c.Unlock() 149 } 150 151 // RemoveSlient removes the given key without triggering OnEvicted 152 func (c *Cache) RemoveSlient(key Key) { 153 c.Lock() 154 c.remove(key, false) 155 c.Unlock() 156 } 157 158 // Len returns the number of items in the cache. 159 func (c *Cache) Len() (len int) { 160 c.Lock() 161 len = c.ll.Len() 162 c.Unlock() 163 return 164 } 165 166 // MaxWeight returns max weight 167 func (c *Cache) MaxWeight() int64 { 168 return c.maxWeight 169 } 170 171 // Weight returns current weight 172 func (c *Cache) Weight() int64 { 173 return c.curWeight 174 } 175 176 func (c *Cache) remove(key Key, doCallback bool) { 177 if ele, hit := c.cache[key]; hit { 178 c.removeElement(ele, doCallback) 179 } 180 } 181 182 func (c *Cache) removeElement(e *list.Element, doCallback bool) { 183 kv := e.Value.(*entry) 184 185 if c.OnEvicted != nil && doCallback { 186 c.OnEvicted(kv.key, kv.value) 187 } 188 189 c.ll.Remove(e) 190 c.curWeight -= e.Value.(*entry).weight 191 delete(c.cache, kv.key) 192 }