github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/go/util/sizecache/size_cache.go (about) 1 // Copyright 2016 Attic Labs, Inc. All rights reserved. 2 // Licensed under the Apache License, version 2.0: 3 // http://www.apache.org/licenses/LICENSE-2.0 4 5 package sizecache 6 7 // SizeCache implements a simple LRU cache of interface{}-typed key-value pairs. 8 // When items are added, the "size" of the item must be provided. LRU items will 9 // be expired until the total of all items is below the specified size for the 10 // SizeCache 11 import ( 12 "container/list" 13 "sync" 14 15 "github.com/attic-labs/noms/go/d" 16 ) 17 18 type sizeCacheEntry struct { 19 size uint64 20 lruEntry *list.Element 21 value interface{} 22 } 23 24 type SizeCache struct { 25 totalSize uint64 26 maxSize uint64 27 mu sync.Mutex 28 lru list.List 29 cache map[interface{}]sizeCacheEntry 30 expireCb func(elm interface{}) 31 } 32 33 type ExpireCallback func(key interface{}) 34 35 // New creates a SizeCache that will hold up to |maxSize| item data. 36 func New(maxSize uint64) *SizeCache { 37 return NewWithExpireCallback(maxSize, nil) 38 } 39 40 // NewWithExpireCallback creates a SizeCache that will hold up to |maxSize| 41 // item data, and will call cb(key) when the item corresponding with that key 42 // expires. 43 func NewWithExpireCallback(maxSize uint64, cb ExpireCallback) *SizeCache { 44 return &SizeCache{ 45 maxSize: maxSize, 46 cache: map[interface{}]sizeCacheEntry{}, 47 expireCb: cb, 48 } 49 } 50 51 // entry() checks if the value is in the cache. If not in the cache, it returns an 52 // empty sizeCacheEntry and false. It it is in the cache, it moves it to 53 // to the back of lru and returns the entry and true. 54 // Callers should have locked down the |c| with a call to c.mu.Lock() before 55 // calling this entry(). 56 func (c *SizeCache) entry(key interface{}) (sizeCacheEntry, bool) { 57 entry, ok := c.cache[key] 58 if !ok { 59 return sizeCacheEntry{}, false 60 } 61 c.lru.MoveToBack(entry.lruEntry) 62 return entry, true 63 } 64 65 // Get checks the searches the cache for an entry. If it exists, it moves it's 66 // lru entry to the back of the queue and returns (value, true). Otherwise, it 67 // returns (nil, false). 68 func (c *SizeCache) Get(key interface{}) (interface{}, bool) { 69 c.mu.Lock() 70 defer c.mu.Unlock() 71 72 if entry, ok := c.entry(key); ok { 73 return entry.value, true 74 } 75 return nil, false 76 } 77 78 // Add will add this element to the cache at the back of the queue as long it's 79 // size does not exceed maxSize. If the addition of this entry causes the size of 80 // the cache to exceed maxSize, the necessary entries at the front of the queue 81 // will be deleted in order to keep the total cache size below maxSize. 82 func (c *SizeCache) Add(key interface{}, size uint64, value interface{}) { 83 if size <= c.maxSize { 84 c.mu.Lock() 85 defer c.mu.Unlock() 86 87 if _, ok := c.entry(key); ok { 88 // this value is already in the cache; just return 89 return 90 } 91 92 newEl := c.lru.PushBack(key) 93 ce := sizeCacheEntry{size: size, lruEntry: newEl, value: value} 94 c.cache[key] = ce 95 c.totalSize += ce.size 96 for el := c.lru.Front(); el != nil && c.totalSize > c.maxSize; { 97 key1 := el.Value 98 ce, ok := c.cache[key1] 99 if !ok { 100 d.Panic("SizeCache is missing expected value") 101 } 102 next := el.Next() 103 delete(c.cache, key1) 104 c.totalSize -= ce.size 105 c.lru.Remove(el) 106 if c.expireCb != nil { 107 c.expireCb(key1) 108 } 109 el = next 110 } 111 } 112 } 113 114 // Drop will remove the element associated with the given key from the cache. 115 func (c *SizeCache) Drop(key interface{}) { 116 c.mu.Lock() 117 defer c.mu.Unlock() 118 119 if entry, ok := c.entry(key); ok { 120 c.totalSize -= entry.size 121 c.lru.Remove(entry.lruEntry) 122 delete(c.cache, key) 123 } 124 }