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  }