1 package cache
2
3 import (
4 "context"
5 "sync"
6 "time"
7
8 "golang.conradwood.net/go-easyops/prometheus"
9 )
10
11 var (
12 asyncLookups = prometheus.NewGaugeVec(
13 prometheus.GaugeOpts{
14 Name: "goeasyops_cache_async_lookups",
15 Help: "V=1 UNIT=ops DESC=number of looks executed asynchronously",
16 },
17 []string{"cachename"},
18 )
19 )
20
21 func init() {
22 prometheus.MustRegister(asyncLookups)
23 }
24
25 type CachingResolver interface {
26 Retrieve(key string, fr func(string) (interface{}, error)) (interface{}, error)
27 RetrieveContext(ctx context.Context, key string, fr func(context.Context, string) (interface{}, error)) (interface{}, error)
28 SetRefreshAfter(time.Duration)
29 SetAsyncRetriever(fr func(string) (interface{}, error))
30 Evict(key string)
31 Clear()
32 Keys() []string
33 }
34
35 type cachingResolver struct {
36
40 CacheErrors bool
41
44 CacheNil bool
45
52 refreshAfter time.Duration
53
56 refreshErrAfter time.Duration
57
58
59 asyncRetriever func(string) (interface{}, error)
60
61
62 gccache *Cache
63 retrieveLock sync.Mutex
64 }
65
66
67 type cacheEntry2 struct {
68 object interface{}
69 err error
70 created time.Time
71 }
72
73 func NewResolvingCache(name string, lifetime time.Duration, maxLimitEntries int) CachingResolver {
74 res := &cachingResolver{
75 CacheErrors: true,
76 CacheNil: true,
77 }
78 res.gccache = New(name, lifetime, maxLimitEntries)
79 res.refreshAfter = lifetime - (lifetime / 3)
80 res.refreshErrAfter = time.Duration(45) * time.Second
81 return res
82 }
83
84
105 func (cr *cachingResolver) Retrieve(key string, fr func(string) (interface{}, error)) (interface{}, error) {
106 ctx := context.Background()
107 return cr.RetrieveContext(ctx, key, func(context.Context, string) (interface{}, error) {
108 return fr(key)
109 })
110 }
111 func (cr *cachingResolver) RetrieveContext(ctx context.Context, key string, fr func(context.Context, string) (interface{}, error)) (interface{}, error) {
112 cname := cr.gccache.name
113 label := prometheus.Labels{"cachename": cname}
114 usage.With(label).Inc()
115 started := time.Now()
116 var ce *cacheEntry2
117 o := cr.gccache.Get(key)
118 if o != nil {
119 ce = o.(*cacheEntry2)
120 if cr.asyncRetriever != nil {
121
122 if cr.refreshAfter != 0 && time.Since(ce.created) > cr.refreshAfter {
123 go cr.refresh(key)
124 } else if cr.refreshErrAfter != 0 && ce.err != nil && time.Since(ce.created) > cr.refreshErrAfter {
125 go cr.refresh(key)
126 }
127 }
128 }
129 if ce == nil {
130
131 cr.retrieveLock.Lock()
132 defer cr.retrieveLock.Unlock()
133 o := cr.gccache.Get(key)
134 if o != nil {
135 ce = o.(*cacheEntry2)
136 } else {
137 efficiency.With(prometheus.Labels{"cachename": cname, "result": "miss"}).Inc()
138 o, err := fr(ctx, key)
139 ce = &cacheEntry2{object: o, err: err, created: time.Now()}
140 cr.gccache.Put(key, ce)
141 }
142 } else {
143 efficiency.With(prometheus.Labels{"cachename": cname, "result": "hit"}).Inc()
144 }
145 if ce.err != nil {
146 return nil, ce.err
147 }
148 performance.With(label).Observe(time.Since(started).Seconds())
149 return ce.object, nil
150 }
151
152 func (cr *cachingResolver) refresh(key string) {
153 cr.retrieveLock.Lock()
154 defer cr.retrieveLock.Unlock()
155 fr := cr.asyncRetriever
156 if fr == nil {
157 return
158 }
159 o, err := fr(key)
160
161
162 if err != nil {
163 o := cr.gccache.Get(key)
164 if o != nil {
165 return
166 }
167 }
168 ce := &cacheEntry2{object: o, err: err, created: time.Now()}
169 cr.gccache.Put(key, ce)
170 cname := cr.gccache.name
171 label := prometheus.Labels{"cachename": cname}
172 asyncLookups.With(label).Inc()
173 }
174 func (cr *cachingResolver) SetRefreshAfter(d time.Duration) {
175 cr.refreshAfter = d
176 }
177 func (cr *cachingResolver) SetAsyncRetriever(fr func(string) (interface{}, error)) {
178 cr.asyncRetriever = fr
179 }
180
181 func (cr *cachingResolver) Evict(key string) {
182 cr.gccache.Evict(key)
183 }
184 func (cr *cachingResolver) Clear() {
185 cr.gccache.Clear()
186 }
187 func (cr *cachingResolver) Keys() []string {
188 return cr.gccache.Keys()
189 }
190
View as plain text