github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/link_cache.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package libkb 5 6 import ( 7 "container/list" 8 "fmt" 9 "sync" 10 "time" 11 ) 12 13 type linkIDFixed [LinkIDLen]byte 14 15 // LinkCache is a cache of ChainLinks. 16 // It is safe to use concurrently. 17 type LinkCache struct { 18 sync.Mutex 19 20 cache map[linkIDFixed]*list.Element 21 done chan struct{} 22 shutdownOnce sync.Once 23 24 cleanWait time.Duration 25 maxSize int 26 27 accessOrder *list.List 28 } 29 30 // NewLinkCache creates a LinkCache. When finished using this 31 // LinkCache, call Shutdown on it to clean up. 32 func NewLinkCache(maxSize int, cleanDur time.Duration) *LinkCache { 33 c := &LinkCache{ 34 cache: make(map[linkIDFixed]*list.Element), 35 done: make(chan struct{}), 36 maxSize: maxSize, 37 cleanWait: cleanDur, 38 accessOrder: list.New(), 39 } 40 go c.periodic() 41 return c 42 } 43 44 // Get retrieves a ChainLink from the cache. If nothing 45 // exists for this LinkID, it will return false for ok. 46 func (c *LinkCache) Get(id LinkID) (link ChainLink, ok bool) { 47 c.Lock() 48 defer c.Unlock() 49 var linkID linkIDFixed 50 copy(linkID[:], id) 51 elt, ok := c.cache[linkID] 52 if ok { 53 link, ok := elt.Value.(ChainLink) 54 if !ok { 55 panic(fmt.Sprintf("invalid type in cache: %T", elt)) 56 } 57 // move the element to the back (most recently accessed) 58 c.accessOrder.MoveToBack(elt) 59 return link.Copy(), true 60 } 61 return link, false 62 } 63 64 func (c *LinkCache) Put(m MetaContext, id LinkID, link ChainLink) { 65 c.Lock() 66 defer c.Unlock() 67 var linkID linkIDFixed 68 copy(linkID[:], id) 69 70 elt, ok := c.cache[linkID] 71 if ok { 72 // if this link already exists, remove it from 73 // the accessOrder list. 74 c.accessOrder.Remove(elt) 75 } 76 elt = c.accessOrder.PushBack(link) 77 c.cache[linkID] = elt 78 } 79 80 func (c *LinkCache) Mutate(id LinkID, f func(c *ChainLink)) { 81 c.Lock() 82 defer c.Unlock() 83 var linkID linkIDFixed 84 copy(linkID[:], id) 85 if elt, ok := c.cache[linkID]; ok { 86 if link, ok := elt.Value.(ChainLink); ok { 87 f(&link) 88 c.accessOrder.Remove(elt) 89 elt = c.accessOrder.PushBack(link) 90 c.cache[linkID] = elt 91 } 92 } 93 } 94 95 // Remove deletes a ChainLink from the cache. 96 func (c *LinkCache) Remove(id LinkID) { 97 c.Lock() 98 defer c.Unlock() 99 var linkID linkIDFixed 100 copy(linkID[:], id) 101 elt, ok := c.cache[linkID] 102 if ok { 103 c.accessOrder.Remove(elt) 104 } 105 delete(c.cache, linkID) 106 } 107 108 // Len returns the number of ChainLinks cached. 109 func (c *LinkCache) Len() int { 110 c.Lock() 111 defer c.Unlock() 112 return len(c.cache) 113 } 114 115 // Shutdown terminates the use of this cache. 116 func (c *LinkCache) Shutdown() { 117 c.shutdownOnce.Do(func() { close(c.done) }) 118 } 119 120 func (c *LinkCache) Clean() { 121 c.Lock() 122 defer c.Unlock() 123 delta := len(c.cache) - c.maxSize 124 for i := 0; i < delta; i++ { 125 // get the least recently used element 126 oldest := c.accessOrder.Front() 127 128 // get a fixed link id from it 129 link, ok := oldest.Value.(ChainLink) 130 if !ok { 131 panic(fmt.Sprintf("invalid type in cache: %T", oldest)) 132 } 133 var linkID linkIDFixed 134 copy(linkID[:], link.id) 135 136 // remove the oldest element 137 c.accessOrder.Remove(oldest) 138 139 // and remove the ChainLink from the cache 140 delete(c.cache, linkID) 141 } 142 } 143 144 func (c *LinkCache) periodic() { 145 for { 146 select { 147 case <-c.done: 148 return 149 case <-time.After(c.cleanWait): 150 c.Clean() 151 } 152 } 153 }