1package libkb
2
3import (
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.
15type 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.
25type 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.
33func 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
47type 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.
57type 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.
61func (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}
100