github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/burst_cache.go (about) 1 package libkb 2 3 import ( 4 "fmt" 5 "time" 6 7 lru "github.com/hashicorp/golang-lru" 8 "golang.org/x/net/context" 9 ) 10 11 // BurstCachce is an LRU+SingleFlighter useful for absorbing short-lived bursts 12 // of lookups. If multiple goroutines are fetching resource A, they all will block on 13 // the first fetch, and can (if the fetch returns without error) share the answer 14 // of the first request. 15 type BurstCache struct { 16 Contextified 17 locktab *LockTable 18 lru *lru.Cache 19 cacheLife time.Duration 20 cacheName string 21 } 22 23 // BurstCacheKey is a key for a burst cache resource. Needs to implement the one 24 // method --- String() --- used for turning the key into an LRU and LockTab key. 25 type BurstCacheKey interface { 26 String() string 27 } 28 29 // NewBurstCache makes a new burst cache with the given size and cacheLife. 30 // The cache will be at most cacheSize items long, and items will live in there for 31 // at most cacheLife duration. For debug logging purposes, this cache will be known 32 // as cacheName. 33 func NewBurstCache(g *GlobalContext, cacheSize int, cacheLife time.Duration, cacheName string) *BurstCache { 34 lru, err := lru.New(cacheSize) 35 if err != nil { 36 g.Log.Fatalf("Bad LRU Constructor: %s", err.Error()) 37 } 38 return &BurstCache{ 39 Contextified: NewContextified(g), 40 lru: lru, 41 locktab: NewLockTable(), 42 cacheLife: cacheLife, 43 cacheName: cacheName, 44 } 45 } 46 47 type burstCacheObj struct { 48 obj interface{} 49 cachedAt time.Time 50 } 51 52 // BurstCacherLoader is a function that loads an item (from network or whatnot). 53 // On success, its result will be cached into the burst cache. Maps a key 54 // to an object as an interface{}. The caller to Load() should wrap into the 55 // closure all parameters needed to actually fetch the object. They called 56 // Load(), so they likely have them handy. 57 type BurstCacheLoader func() (obj interface{}, err error) 58 59 // Load item key from the burst cache. On a cache miss, load with the given loader function. 60 // Return the object as an interface{}, so the caller needs to cast out of this burst cache. 61 func (b *BurstCache) Load(ctx context.Context, key BurstCacheKey, loader BurstCacheLoader) (ret interface{}, err error) { 62 ctx = WithLogTag(ctx, "BC") 63 defer b.G().CVTrace(ctx, VLog0, fmt.Sprintf("BurstCache(%s)#Load(%s)", b.cacheName, key.String()), &err)() 64 65 lock := b.locktab.AcquireOnName(ctx, b.G(), key.String()) 66 defer lock.Release(ctx) 67 68 b.G().VDL.CLogf(ctx, VLog0, "| past single-flight lock") 69 70 found := false 71 if val, ok := b.lru.Get(key.String()); ok { 72 b.G().VDL.CLogf(ctx, VLog0, "| found in LRU cache") 73 if tmp, ok := val.(*burstCacheObj); ok { 74 age := b.G().GetClock().Now().Sub(tmp.cachedAt) 75 if age < b.cacheLife { 76 b.G().VDL.CLogf(ctx, VLog0, "| cached object was fresh (loaded %v ago)", age) 77 ret = tmp.obj 78 found = true 79 } else { 80 b.G().VDL.CLogf(ctx, VLog0, "| cached object expired %v ago", (age - b.cacheLife)) 81 b.lru.Remove(key.String()) 82 } 83 } else { 84 b.G().Log.CErrorf(ctx, "| object in LRU was of wrong type") 85 } 86 } else { 87 b.G().VDL.CLogf(ctx, VLog0, "| object cache miss") 88 } 89 90 if !found { 91 ret, err = loader() 92 if err == nil { 93 b.G().VDL.CLogf(ctx, VLog0, "| caching object after successful fetch") 94 b.lru.Add(key.String(), &burstCacheObj{ret, b.G().GetClock().Now()}) 95 } 96 } 97 98 return ret, err 99 }