Codebase list golang-github-karrick-goswarm / 2ed42b1
Import upstream version 1.10.0 Debian Janitor 1 year, 6 months ago
6 changed file(s) with 781 addition(s) and 93 deletion(s). Raw diff Collapse all Expand all
+0
-24
.gitignore less more
0 # Compiled Object files, Static and Dynamic libs (Shared Objects)
1 *.o
2 *.a
3 *.so
4
5 # Folders
6 _obj
7 _test
8
9 # Architecture specific extensions/prefixes
10 *.[568vq]
11 [568vq].out
12
13 *.cgo1.go
14 *.cgo2.c
15 _cgo_defun.c
16 _cgo_gotypes.go
17 _cgo_export.*
18
19 _testmain.go
20
21 *.exe
22 *.test
23 *.prof
0 module github.com/karrick/goswarm
(New empty file)
2222 halt chan struct{}
2323 closeError chan error
2424 gcFlag int32
25 stats Stats
26 }
27
28 // Stats contains various cache statistics.
29 type Stats struct {
30 Count int64 // Count represents the number of items in the cache.
31 Creates int64 // Creates represents how many new cache items were created since the previous Stats call.
32 Deletes int64 // Deletes represents how many Delete calls were made since the previous Stats call. Note this counts when Delete is called with a key that is not in the cache.
33 Evictions int64 // Evictions represents how many key-value pairs were evicted since the previous Stats call.
34 Hits int64 // Hits represents how many Load and Query calls found and returned a Fresh item.
35 LookupErrors int64 // LookupErrors represents the number of Lookup invocations that returned an error since the previous Stats call.
36 Misses int64 // Misses represents how many Load and Query calls either did not find the item, or found an expired item.
37 Queries int64 // Queries represents how many Load and Query calls were made since the previous Stats call.
38 Stales int64 // Stales represents how many Load and Query calls found and returned a Stale item.
39 Stores int64 // Stores represents how many Store calls were made since the previous Stats call.
40 Updates int64 // Updates represents how many Update calls were made since the previous Stats call.
2541 }
2642
2743 // NewSimple returns Swarm that attempts to respond to Query methods by
107123
108124 // Delete removes the key and associated value from the data map.
109125 func (s *Simple) Delete(key string) {
126 atomic.AddInt64(&s.stats.Deletes, 1)
127
110128 s.lock.RLock()
111129 _, ok := s.data[key]
112130 s.lock.RUnlock()
176194 if av := atv.av.Load(); av != nil {
177195 allPairs <- gcPair{
178196 key: key,
179 doomed: av.(*TimedValue).isExpired(now),
197 doomed: av.(*TimedValue).IsExpiredAt(now),
180198 }
181199 }
182200 }(key, atv, allPairs)
216234 for _, key := range doomed {
217235 delete(s.data, key)
218236 }
237 atomic.AddInt64(&s.stats.Evictions, int64(len(doomed)))
219238 s.lock.Unlock()
220239 }
221240
222241 // Load returns the value associated with the specified key, and a boolean value
223242 // indicating whether or not the key was found in the map.
224243 func (s *Simple) Load(key string) (interface{}, bool) {
244 atomic.AddInt64(&s.stats.Queries, 1)
245
225246 // Do not want to use getOrCreateLockingTimeValue, because there's no reason
226247 // to create ATV if key is not present in data map.
227248 s.lock.RLock()
228249 atv, ok := s.data[key]
229250 s.lock.RUnlock()
230251 if !ok {
252 atomic.AddInt64(&s.stats.Misses, 1)
231253 return nil, false
232254 }
233255
236258 // Element either recently erased by another routine while this method
237259 // was waiting for element lock above, or has not been populated by
238260 // fetch, in which case the value is not really there yet.
261 atomic.AddInt64(&s.stats.Misses, 1)
239262 return nil, false
240263 }
241 return av.(*TimedValue).Value, true
264
265 now := time.Now()
266 tv := av.(*TimedValue)
267
268 if tv.IsExpiredAt(now) {
269 atomic.AddInt64(&s.stats.Misses, 1)
270 return nil, false
271 }
272 if tv.IsStaleAt(now) {
273 atomic.AddInt64(&s.stats.Stales, 1)
274 return tv.Value, true
275 }
276 atomic.AddInt64(&s.stats.Hits, 1)
277 return tv.Value, true
278 }
279
280 // LoadTimedValue returns the TimedValue associated with the specified key, or
281 // false if the key is not found in the map.
282 func (s *Simple) LoadTimedValue(key string) *TimedValue {
283 atomic.AddInt64(&s.stats.Queries, 1)
284
285 // Do not want to use getOrCreateLockingTimeValue, because there's no reason
286 // to create ATV if key is not present in data map.
287 s.lock.RLock()
288 atv, ok := s.data[key]
289 s.lock.RUnlock()
290 if !ok {
291 atomic.AddInt64(&s.stats.Misses, 1)
292 return nil
293 }
294
295 av := atv.av.Load()
296 if av == nil {
297 // Element either recently erased by another routine while this method
298 // was waiting for element lock above, or has not been populated by
299 // fetch, in which case the value is not really there yet.
300 atomic.AddInt64(&s.stats.Misses, 1)
301 return nil
302 }
303
304 now := time.Now()
305 tv := av.(*TimedValue)
306
307 if tv.IsExpiredAt(now) {
308 atomic.AddInt64(&s.stats.Misses, 1)
309 } else if tv.IsStaleAt(now) {
310 atomic.AddInt64(&s.stats.Stales, 1)
311 } else {
312 atomic.AddInt64(&s.stats.Hits, 1)
313 }
314 return tv
242315 }
243316
244317 // Query loads the value associated with the specified key from the data
248321 // lookup of a new value is triggered, then the new value is stored and
249322 // returned.
250323 func (s *Simple) Query(key string) (interface{}, error) {
324 atomic.AddInt64(&s.stats.Queries, 1)
325
251326 atv := s.getOrCreateAtomicTimedValue(key)
252327 av := atv.av.Load()
253328 if av == nil {
329 atomic.AddInt64(&s.stats.Misses, 1)
254330 tv := s.update(key, atv)
255331 return tv.Value, tv.Err
256 } else {
257 now := time.Now()
258 tv := av.(*TimedValue)
259 if tv.isExpired(now) {
260 tv = s.update(key, atv)
261 } else if tv.isStale(now) {
262 // If no other goroutine is looking up this value, spin one off
263 if atomic.CompareAndSwapInt32(&atv.pending, 0, 1) {
264 go func() {
265 defer atomic.StoreInt32(&atv.pending, 0)
266 _ = s.update(key, atv)
267 }()
268 }
332 }
333
334 now := time.Now()
335 tv := av.(*TimedValue)
336
337 if tv.IsExpiredAt(now) {
338 // Expired is considered a blocking miss.
339 atomic.AddInt64(&s.stats.Misses, 1)
340 tv := s.update(key, atv)
341 return tv.Value, tv.Err
342 }
343
344 if tv.IsStaleAt(now) {
345 // If no other goroutine is looking up this value, spin one off.
346 if atomic.CompareAndSwapInt32(&atv.pending, 0, 1) {
347 go func() {
348 defer atomic.StoreInt32(&atv.pending, 0)
349 _ = s.update(key, atv)
350 }()
269351 }
352 atomic.AddInt64(&s.stats.Stales, 1)
270353 return tv.Value, tv.Err
271354 }
355
356 atomic.AddInt64(&s.stats.Hits, 1)
357 return tv.Value, tv.Err
272358 }
273359
274360 // Range invokes specified callback function for each non-expired key in the
299385 s.lock.RUnlock()
300386 }
301387
388 // RangeBreak invokes specified callback function for each non-expired key in
389 // the data map. Each key-value pair is independently locked until the callback
390 // function invoked with the specified key returns. This method does not block
391 // access to the Simple instance, allowing keys to be added and removed like
392 // normal even while the callbacks are running. When the callback returns true,
393 // this function performs an early termination of enumerating the cache,
394 // returning true it its caller.
395 func (s *Simple) RangeBreak(callback func(key string, value *TimedValue) bool) bool {
396 // Need to have read lock while enumerating key-value pairs from map
397 s.lock.RLock()
398 for key, atv := range s.data {
399 // Now that we have a key-value pair from the map, we can release the
400 // map's lock to prevent blocking other routines that need it.
401 s.lock.RUnlock()
402
403 if av := atv.av.Load(); av != nil {
404 // We have an element. If it's not yet expired, invoke the user's
405 // callback with the key and value.
406 if tv := av.(*TimedValue); !tv.IsExpired() {
407 if callback(key, tv) {
408 return true
409 }
410 }
411 }
412
413 // After callback is done with element, re-acquire map-level lock before
414 // we grab the next key-value pair from the map.
415 s.lock.RLock()
416 }
417 s.lock.RUnlock()
418 return false
419 }
420
421 // Stats returns a snapshot of the cache's statistics. Note all statistics will
422 // be reset when this method is invoked, allowing the client to determine the
423 // number of each respective events that have taken place since the previous
424 // time this method was invoked.
425 func (s *Simple) Stats() Stats {
426 s.lock.RLock()
427 count := int64(len(s.data))
428 s.lock.RUnlock()
429
430 return Stats{
431 Count: count,
432 Creates: atomic.SwapInt64(&s.stats.Creates, 0),
433 Deletes: atomic.SwapInt64(&s.stats.Deletes, 0),
434 Evictions: atomic.SwapInt64(&s.stats.Evictions, 0),
435 Hits: atomic.SwapInt64(&s.stats.Hits, 0),
436 LookupErrors: atomic.SwapInt64(&s.stats.LookupErrors, 0),
437 Misses: atomic.SwapInt64(&s.stats.Misses, 0),
438 Queries: atomic.SwapInt64(&s.stats.Queries, 0),
439 Stales: atomic.SwapInt64(&s.stats.Stales, 0),
440 Stores: atomic.SwapInt64(&s.stats.Stores, 0),
441 Updates: atomic.SwapInt64(&s.stats.Updates, 0),
442 }
443 }
444
302445 // Store saves the key-value pair to the cache, overwriting whatever was
303446 // previously stored.
304447 func (s *Simple) Store(key string, value interface{}) {
448 atomic.AddInt64(&s.stats.Stores, 1)
305449 atv := s.getOrCreateAtomicTimedValue(key)
450
451 // NOTE: Below invocation ignores the provided durations when value is
452 // already a TimedValue.
306453 tv := newTimedValue(value, nil, s.config.GoodStaleDuration, s.config.GoodExpiryDuration)
454
307455 atv.av.Store(tv)
308456 }
309457
310458 // Update forces an update of the value associated with the specified key.
311459 func (s *Simple) Update(key string) {
460 atomic.AddInt64(&s.stats.Updates, 1)
312461 atv := s.getOrCreateAtomicTimedValue(key)
313462 s.update(key, atv)
314463 }
340489 if !ok {
341490 atv = new(atomicTimedValue)
342491 s.data[key] = atv
492 atomic.AddInt64(&s.stats.Creates, 1)
343493 }
344494 s.lock.Unlock()
345495 }
350500 // the update is successful, it stores the value in the TimedValue associated
351501 // with the key.
352502 func (s *Simple) update(key string, atv *atomicTimedValue) *TimedValue {
353 staleDuration := s.config.GoodStaleDuration
354 expiryDuration := s.config.GoodExpiryDuration
355
356503 value, err := s.config.Lookup(key)
357504 if err == nil {
358 tv := newTimedValue(value, err, staleDuration, expiryDuration)
505 tv := newTimedValue(value, nil, s.config.GoodStaleDuration, s.config.GoodExpiryDuration)
359506 atv.av.Store(tv)
360507 return tv
361508 }
362509
363510 // lookup gave us an error
364 staleDuration = s.config.BadStaleDuration
365 expiryDuration = s.config.BadExpiryDuration
511 atomic.AddInt64(&s.stats.LookupErrors, 1)
512 staleDuration := s.config.BadStaleDuration
513 expiryDuration := s.config.BadExpiryDuration
366514
367515 // new error overwrites previous error, and also used when initial value
368516 av := atv.av.Load()
1111 "time"
1212 )
1313
14 func ensureErrorL(t *testing.T, swr *Simple, key, expectedError string) {
15 value, err := swr.Query(key)
14 func ensureErrorL(t *testing.T, querier Querier, key, expectedError string) {
15 value, err := querier.Query(key)
1616 if value != nil {
1717 t.Errorf("Actual: %v; Expected: %v", value, nil)
1818 }
2121 }
2222 }
2323
24 func ensureValueL(t *testing.T, swr *Simple, key string, expectedValue uint64) {
25 value, err := swr.Query(key)
24 func ensureValueL(t *testing.T, querier Querier, key string, expectedValue uint64) {
25 value, err := querier.Query(key)
2626 if value.(uint64) != expectedValue {
2727 t.Errorf("Actual: %d; Expected: %d", value, expectedValue)
2828 }
3636 func TestSimpleSynchronousLookupWhenMiss(t *testing.T) {
3737 var invoked uint64
3838 swr, err := NewSimple(&Config{Lookup: func(_ string) (interface{}, error) {
39 atomic.AddUint64(&invoked, 1)
39 invoked++
4040 return uint64(42), nil
4141 }})
4242 if err != nil {
4646
4747 ensureValueL(t, swr, "miss", 42)
4848
49 if actual, expected := atomic.AddUint64(&invoked, 0), uint64(1); actual != expected {
49 if actual, expected := invoked, uint64(1); actual != expected {
5050 t.Errorf("Actual: %d; Expected: %d", actual, expected)
5151 }
5252 }
7878 defer func() { _ = swr.Close() }()
7979
8080 // NOTE: storing a value that expires one minute in the future
81 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Expiry: time.Now().Add(time.Minute)})
82
83 ensureValueL(t, swr, "hit", 13)
81 now := time.Now()
82 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Created: now, Expiry: now.Add(time.Minute)})
83
84 ensureValueL(t, swr, "hit", 13)
85 }
86
87 func TestSimpleStaleExpireLoadReturnsFalse(t *testing.T) {
88 swr, err := NewSimple(nil)
89 if err != nil {
90 t.Fatal(err)
91 }
92 defer func() { _ = swr.Close() }()
93
94 now := time.Now()
95 swr.Store("expired", &TimedValue{Value: uint64(42), Created: now, Expiry: now.Add(-time.Minute)})
96
97 value, ok := swr.Load("expired")
98
99 if got, want := ok, false; got != want {
100 t.Errorf("GOT: %v; WANT: %v", got, want)
101 }
102
103 if value != nil {
104 t.Errorf("GOT: %v; WANT: %v", value, nil)
105 }
106 }
107
108 func TestSimpleStaleExpireLoadTimedValueReturnsExpiredValue(t *testing.T) {
109 swr, err := NewSimple(nil)
110 if err != nil {
111 t.Fatal(err)
112 }
113 defer func() { _ = swr.Close() }()
114
115 now := time.Now()
116 swr.Store("expired", &TimedValue{Value: uint64(42), Created: now, Expiry: now.Add(-time.Minute)})
117
118 tv := swr.LoadTimedValue("expired")
119
120 if got, want := tv.IsExpired(), true; got != want {
121 t.Errorf("GOT: %v; WANT: %v", got, want)
122 }
123
124 if got, want := tv.Value, uint64(42); got != want {
125 t.Errorf("GOT: %v; WANT: %v", got, want)
126 }
84127 }
85128
86129 func TestSimpleNoStaleExpireSynchronousLookupWhenAfterExpire(t *testing.T) {
95138 defer func() { _ = swr.Close() }()
96139
97140 // NOTE: storing a value that expired one minute ago
98 swr.Store("hit", &TimedValue{Value: uint64(42), Err: nil, Expiry: time.Now().Add(-time.Minute)})
141 now := time.Now()
142 swr.Store("hit", &TimedValue{Value: uint64(42), Err: nil, Created: now, Expiry: now.Add(-time.Minute)})
99143
100144 ensureValueL(t, swr, "hit", 42)
101145
116160 defer func() { _ = swr.Close() }()
117161
118162 // NOTE: storing a value that goes stale one minute in the future
119 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Stale: time.Now().Add(time.Minute)})
163 now := time.Now()
164 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Created: now, Stale: now.Add(time.Minute)})
120165
121166 ensureValueL(t, swr, "hit", 13)
122167 }
136181 defer func() { _ = swr.Close() }()
137182
138183 // NOTE: storing a value that went stale one minute ago
139 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Stale: time.Now().Add(-time.Minute)})
184 now := time.Now()
185 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Created: now, Stale: now.Add(-time.Minute)})
140186
141187 wg.Add(1)
142188 ensureValueL(t, swr, "hit", 13)
164210 defer func() { _ = swr.Close() }()
165211
166212 // NOTE: storing a value that goes stale one minute in the future and expires one hour in the future
167 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Stale: time.Now().Add(time.Minute), Expiry: time.Now().Add(time.Hour)})
213 now := time.Now()
214 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Created: now, Stale: now.Add(time.Minute), Expiry: now.Add(time.Hour)})
168215
169216 ensureValueL(t, swr, "hit", 13)
170217 }
183230 defer func() { _ = swr.Close() }()
184231
185232 // NOTE: storing a value that went stale one minute ago and expires one minute in the future
186 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Stale: time.Now().Add(-time.Minute), Expiry: time.Now().Add(time.Minute)})
233 now := time.Now()
234 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Created: now, Stale: now.Add(-time.Minute), Expiry: now.Add(time.Minute)})
187235
188236 // expect to receive the old value back immediately, then expect lookup to be asynchronously invoked
189237 wg.Add(1)
211259 defer func() { _ = swr.Close() }()
212260
213261 // NOTE: storing a value that went stale one hour ago and expired one minute ago
214 swr.Store("hit", &TimedValue{Value: uint64(42), Err: nil, Stale: time.Now().Add(-time.Hour), Expiry: time.Now().Add(-time.Minute)})
262 now := time.Now()
263 swr.Store("hit", &TimedValue{Value: uint64(42), Err: nil, Created: now, Stale: now.Add(-time.Hour), Expiry: now.Add(-time.Minute)})
215264
216265 ensureValueL(t, swr, "hit", 42)
217266
234283 defer func() { _ = swr.Close() }()
235284
236285 // NOTE: storing a value that went stale one minute ago
237 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Stale: time.Now().Add(-time.Minute)})
286 now := time.Now()
287 swr.Store("hit", &TimedValue{Value: uint64(13), Err: nil, Created: now, Stale: now.Add(-time.Minute)})
238288
239289 wg.Add(1)
240290 ensureValueL(t, swr, "hit", 13)
265315 defer func() { _ = swr.Close() }()
266316
267317 // NOTE: storing a value that went stale one minute ago
268 swr.Store("hit", &TimedValue{Value: nil, Err: errors.New("original error"), Stale: time.Now().Add(-time.Minute)})
318 now := time.Now()
319 swr.Store("hit", &TimedValue{Value: nil, Err: errors.New("original error"), Created: now, Stale: now.Add(-time.Minute)})
269320
270321 wg.Add(1)
271322 ensureErrorL(t, swr, "hit", "new error")
291342 defer func() { _ = swr.Close() }()
292343
293344 // NOTE: storing a value is already stale, but will expire during the fetch
294 swr.Store("hit", &TimedValue{Value: nil, Err: errors.New("original error"), Stale: time.Now().Add(-time.Hour), Expiry: time.Now().Add(5 * time.Millisecond)})
345 now := time.Now()
346 swr.Store("hit", &TimedValue{Value: nil, Err: errors.New("original error"), Created: now, Stale: now.Add(-time.Hour), Expiry: now.Add(5 * time.Millisecond)})
295347
296348 wg.Add(1)
297349 ensureErrorL(t, swr, "hit", "original error")
316368 defer func() { _ = swr.Close() }()
317369
318370 swr.Store("no expiry", "shall not expire")
319 swr.Store("stale value", TimedValue{Value: "stale value", Stale: time.Now().Add(-time.Minute)})
320 swr.Store("expired value", TimedValue{Value: "expired value", Expiry: time.Now().Add(-time.Minute)})
371 swr.Store("expired value", TimedValue{Value: "expired value", Created: time.Now(), Expiry: time.Now().Add(-time.Minute)})
372 swr.Store("stale value", TimedValue{Value: "stale value", Created: time.Now(), Stale: time.Now().Add(-time.Minute)})
373 swr.Store("will update expiry", "soon to be expired")
374
375 swr.Store("will update stale", TimedValue{Value: "stale value", Created: time.Now(), Stale: time.Now().Add(-time.Minute)})
376 // make sure already stale
377 if got, want := swr.LoadTimedValue("will update stale").IsStale(), true; got != want {
378 t.Errorf("GOT: %v; WANT: %v", got, want)
379 }
321380
322381 called := make(map[string]struct{})
323382 swr.Range(func(key string, value *TimedValue) {
324383 called[key] = struct{}{}
325384 swr.Store(strconv.Itoa(rand.Intn(50)), "make sure we can invoke methods that require locking")
385 switch key {
386 case "will update stale":
387 value.Stale = time.Now().Add(time.Minute)
388 case "will update expiry":
389 value.Expiry = time.Now().Add(-time.Minute)
390 }
326391 })
327392
328393 if _, ok := called["no expiry"]; !ok {
333398 }
334399 if _, ok := called["expired value"]; ok {
335400 t.Errorf("Actual: %#v; Expected: %#v", ok, false)
401 }
402 if _, ok := called["will update stale"]; !ok {
403 t.Errorf("Actual: %#v; Expected: %#v", ok, true)
404 }
405 if got, want := swr.LoadTimedValue("will update stale").IsStale(), false; got != want {
406 t.Errorf("GOT: %v; WANT: %v", got, want)
407 }
408 if got, want := swr.LoadTimedValue("will update expiry").IsExpired(), true; got != want {
409 t.Errorf("GOT: %v; WANT: %v", got, want)
410 }
411
412 swr.Store("ensure range released top level lock", struct{}{})
413 }
414
415 func TestSimpleRangeBreak(t *testing.T) {
416 swr, err := NewSimple(nil)
417 if err != nil {
418 t.Fatal(err)
419 }
420 defer func() { _ = swr.Close() }()
421
422 swr.Store("alpha", 1)
423 swr.Store("bravo", 2)
424 swr.Store("charlie", 3)
425 swr.Store("delta", 4)
426
427 called := make(map[string]struct{})
428 terminated := swr.RangeBreak(func(key string, value *TimedValue) bool {
429 called[key] = struct{}{}
430 if key == "charlie" {
431 return true
432 }
433 return false
434 })
435
436 if got, want := terminated, true; got != want {
437 t.Errorf("GOT: %v; WANT: %v", got, want)
438 }
439 if _, ok := called["charlie"]; !ok {
440 t.Errorf("Actual: %#v; Expected: %#v", ok, true)
336441 }
337442
338443 swr.Store("ensure range released top level lock", struct{}{})
383488 t.Errorf("Actual: %s; Expected: %s", actual, expected)
384489 }
385490 }
491
492 func TestStats(t *testing.T) {
493 t.Run("query", func(t *testing.T) {
494 var haveLookupFail bool
495
496 swr, err := NewSimple(&Config{
497 Lookup: func(key string) (interface{}, error) {
498 if haveLookupFail {
499 return nil, errors.New("lookup failure")
500 }
501 time.Sleep(10 * time.Millisecond)
502 return key, nil
503 },
504 })
505 if err != nil {
506 t.Fatal(err)
507 }
508
509 // Get stats before cache methods invoked.
510 stats := swr.Stats()
511 if got, want := stats.Count, int64(0); got != want {
512 t.Errorf("GOT: %v; WANT: %v", got, want)
513 }
514 if got, want := stats.Creates, int64(0); got != want {
515 t.Errorf("GOT: %v; WANT: %v", got, want)
516 }
517 if got, want := stats.Deletes, int64(0); got != want {
518 t.Errorf("GOT: %v; WANT: %v", got, want)
519 }
520 if got, want := stats.Evictions, int64(0); got != want {
521 t.Errorf("GOT: %v; WANT: %v", got, want)
522 }
523 if got, want := stats.LookupErrors, int64(0); got != want {
524 t.Errorf("GOT: %v; WANT: %v", got, want)
525 }
526 if got, want := stats.Queries, int64(0); got != want {
527 t.Errorf("GOT: %v; WANT: %v", got, want)
528 }
529 if got, want := stats.Hits, int64(0); got != want {
530 t.Errorf("GOT: %v; WANT: %v", got, want)
531 }
532 if got, want := stats.Misses, int64(0); got != want {
533 t.Errorf("GOT: %v; WANT: %v", got, want)
534 }
535 if got, want := stats.Stales, int64(0); got != want {
536 t.Errorf("GOT: %v; WANT: %v", got, want)
537 }
538 if got, want := stats.Stores, int64(0); got != want {
539 t.Errorf("GOT: %v; WANT: %v", got, want)
540 }
541 if got, want := stats.Updates, int64(0); got != want {
542 t.Errorf("GOT: %v; WANT: %v", got, want)
543 }
544
545 // Invoke Query with key not yet in cache.
546 _, err = swr.Query("foo")
547 if got, want := err, error(nil); got != want {
548 t.Errorf("GOT: %v; WANT: %v", got, want)
549 }
550
551 // Get stats after new key-value pair added.
552 stats = swr.Stats()
553 if got, want := stats.Count, int64(1); got != want {
554 t.Errorf("GOT: %v; WANT: %v", got, want)
555 }
556 if got, want := stats.Creates, int64(1); got != want {
557 t.Errorf("GOT: %v; WANT: %v", got, want)
558 }
559 if got, want := stats.Deletes, int64(0); got != want {
560 t.Errorf("GOT: %v; WANT: %v", got, want)
561 }
562 if got, want := stats.Evictions, int64(0); got != want {
563 t.Errorf("GOT: %v; WANT: %v", got, want)
564 }
565 if got, want := stats.LookupErrors, int64(0); got != want {
566 t.Errorf("GOT: %v; WANT: %v", got, want)
567 }
568 if got, want := stats.Queries, int64(1); got != want {
569 t.Errorf("GOT: %v; WANT: %v", got, want)
570 }
571 if got, want := stats.Hits, int64(0); got != want {
572 t.Errorf("GOT: %v; WANT: %v", got, want)
573 }
574 if got, want := stats.Misses, int64(1); got != want {
575 t.Errorf("GOT: %v; WANT: %v", got, want)
576 }
577 if got, want := stats.Stales, int64(0); got != want {
578 t.Errorf("GOT: %v; WANT: %v", got, want)
579 }
580 if got, want := stats.Stores, int64(0); got != want {
581 t.Errorf("GOT: %v; WANT: %v", got, want)
582 }
583 if got, want := stats.Updates, int64(0); got != want {
584 t.Errorf("GOT: %v; WANT: %v", got, want)
585 }
586
587 // Invoke Query with key already in cache.
588 _, err = swr.Query("foo")
589 if got, want := err, error(nil); got != want {
590 t.Errorf("GOT: %v; WANT: %v", got, want)
591 }
592
593 // Get stats after new key-value pair added.
594 stats = swr.Stats()
595 if got, want := stats.Count, int64(1); got != want {
596 t.Errorf("GOT: %v; WANT: %v", got, want)
597 }
598 if got, want := stats.Creates, int64(0); got != want {
599 t.Errorf("GOT: %v; WANT: %v", got, want)
600 }
601 if got, want := stats.Deletes, int64(0); got != want {
602 t.Errorf("GOT: %v; WANT: %v", got, want)
603 }
604 if got, want := stats.Evictions, int64(0); got != want {
605 t.Errorf("GOT: %v; WANT: %v", got, want)
606 }
607 if got, want := stats.LookupErrors, int64(0); got != want {
608 t.Errorf("GOT: %v; WANT: %v", got, want)
609 }
610 if got, want := stats.Queries, int64(1); got != want {
611 t.Errorf("GOT: %v; WANT: %v", got, want)
612 }
613 if got, want := stats.Hits, int64(1); got != want {
614 t.Errorf("GOT: %v; WANT: %v", got, want)
615 }
616 if got, want := stats.Misses, int64(0); got != want {
617 t.Errorf("GOT: %v; WANT: %v", got, want)
618 }
619 if got, want := stats.Stales, int64(0); got != want {
620 t.Errorf("GOT: %v; WANT: %v", got, want)
621 }
622 if got, want := stats.Stores, int64(0); got != want {
623 t.Errorf("GOT: %v; WANT: %v", got, want)
624 }
625 if got, want := stats.Updates, int64(0); got != want {
626 t.Errorf("GOT: %v; WANT: %v", got, want)
627 }
628
629 // Invoke Query with key already in cache.
630 haveLookupFail = true
631 _, err = swr.Query("bar")
632 if err == nil || !strings.Contains(err.Error(), "lookup failure") {
633 t.Errorf("GOT: %v; WANT: %v", err, "lookup failure")
634 }
635
636 // Get stats after new key-value pair added.
637 stats = swr.Stats()
638 if got, want := stats.Count, int64(2); got != want {
639 t.Errorf("GOT: %v; WANT: %v", got, want)
640 }
641 if got, want := stats.Creates, int64(1); got != want {
642 t.Errorf("GOT: %v; WANT: %v", got, want)
643 }
644 if got, want := stats.Deletes, int64(0); got != want {
645 t.Errorf("GOT: %v; WANT: %v", got, want)
646 }
647 if got, want := stats.Evictions, int64(0); got != want {
648 t.Errorf("GOT: %v; WANT: %v", got, want)
649 }
650 if got, want := stats.LookupErrors, int64(1); got != want {
651 t.Errorf("GOT: %v; WANT: %v", got, want)
652 }
653 if got, want := stats.Queries, int64(1); got != want {
654 t.Errorf("GOT: %v; WANT: %v", got, want)
655 }
656 if got, want := stats.Hits, int64(0); got != want {
657 t.Errorf("GOT: %v; WANT: %v", got, want)
658 }
659 if got, want := stats.Misses, int64(1); got != want {
660 t.Errorf("GOT: %v; WANT: %v", got, want)
661 }
662 if got, want := stats.Stales, int64(0); got != want {
663 t.Errorf("GOT: %v; WANT: %v", got, want)
664 }
665 if got, want := stats.Stores, int64(0); got != want {
666 t.Errorf("GOT: %v; WANT: %v", got, want)
667 }
668 if got, want := stats.Updates, int64(0); got != want {
669 t.Errorf("GOT: %v; WANT: %v", got, want)
670 }
671 })
672
673 t.Run("load", func(t *testing.T) {
674 t.Run("stale", func(t *testing.T) {
675 swr, err := NewSimple(nil)
676 if err != nil {
677 t.Fatal(err)
678 }
679 swr.Store("foo", TimedValue{
680 Value: "foo",
681 Stale: time.Now().Add(-time.Second),
682 })
683 _, ok := swr.Load("foo")
684 if got, want := ok, true; got != want {
685 t.Errorf("GOT: %v; WANT: %v", got, want)
686 }
687
688 stats := swr.Stats()
689
690 if got, want := stats.Count, int64(1); got != want {
691 t.Errorf("GOT: %v; WANT: %v", got, want)
692 }
693 if got, want := stats.Creates, int64(1); got != want {
694 t.Errorf("GOT: %v; WANT: %v", got, want)
695 }
696 if got, want := stats.Deletes, int64(0); got != want {
697 t.Errorf("GOT: %v; WANT: %v", got, want)
698 }
699 if got, want := stats.Evictions, int64(0); got != want {
700 t.Errorf("GOT: %v; WANT: %v", got, want)
701 }
702 if got, want := stats.LookupErrors, int64(0); got != want {
703 t.Errorf("GOT: %v; WANT: %v", got, want)
704 }
705 if got, want := stats.Queries, int64(1); got != want {
706 t.Errorf("GOT: %v; WANT: %v", got, want)
707 }
708 if got, want := stats.Hits, int64(0); got != want {
709 t.Errorf("GOT: %v; WANT: %v", got, want)
710 }
711 if got, want := stats.Misses, int64(0); got != want {
712 t.Errorf("GOT: %v; WANT: %v", got, want)
713 }
714 if got, want := stats.Stales, int64(1); got != want {
715 t.Errorf("GOT: %v; WANT: %v", got, want)
716 }
717 if got, want := stats.Stores, int64(1); got != want {
718 t.Errorf("GOT: %v; WANT: %v", got, want)
719 }
720 if got, want := stats.Updates, int64(0); got != want {
721 t.Errorf("GOT: %v; WANT: %v", got, want)
722 }
723 })
724
725 t.Run("expired", func(t *testing.T) {
726 swr, err := NewSimple(nil)
727 if err != nil {
728 t.Fatal(err)
729 }
730 swr.Store("foo", TimedValue{
731 Value: "foo",
732 Expiry: time.Now().Add(-time.Second),
733 })
734 _, ok := swr.Load("foo")
735 if got, want := ok, false; got != want {
736 t.Errorf("GOT: %v; WANT: %v", got, want)
737 }
738
739 stats := swr.Stats()
740
741 if got, want := stats.Count, int64(1); got != want {
742 t.Errorf("GOT: %v; WANT: %v", got, want)
743 }
744 if got, want := stats.Creates, int64(1); got != want {
745 t.Errorf("GOT: %v; WANT: %v", got, want)
746 }
747 if got, want := stats.Deletes, int64(0); got != want {
748 t.Errorf("GOT: %v; WANT: %v", got, want)
749 }
750 if got, want := stats.Evictions, int64(0); got != want {
751 t.Errorf("GOT: %v; WANT: %v", got, want)
752 }
753 if got, want := stats.LookupErrors, int64(0); got != want {
754 t.Errorf("GOT: %v; WANT: %v", got, want)
755 }
756 if got, want := stats.Queries, int64(1); got != want {
757 t.Errorf("GOT: %v; WANT: %v", got, want)
758 }
759 if got, want := stats.Hits, int64(0); got != want {
760 t.Errorf("GOT: %v; WANT: %v", got, want)
761 }
762 if got, want := stats.Misses, int64(1); got != want {
763 t.Errorf("GOT: %v; WANT: %v", got, want)
764 }
765 if got, want := stats.Stales, int64(0); got != want {
766 t.Errorf("GOT: %v; WANT: %v", got, want)
767 }
768 if got, want := stats.Stores, int64(1); got != want {
769 t.Errorf("GOT: %v; WANT: %v", got, want)
770 }
771 if got, want := stats.Updates, int64(0); got != want {
772 t.Errorf("GOT: %v; WANT: %v", got, want)
773 }
774 })
775 })
776
777 t.Run("load-timed-value", func(t *testing.T) {
778 t.Run("stale", func(t *testing.T) {
779 swr, err := NewSimple(nil)
780 if err != nil {
781 t.Fatal(err)
782 }
783 swr.Store("foo", TimedValue{
784 Value: "foo",
785 Stale: time.Now().Add(-time.Second),
786 Expiry: time.Now().Add(time.Second),
787 })
788 tv := swr.LoadTimedValue("foo")
789 if got, want := tv.IsStale(), true; got != want {
790 t.Errorf("GOT: %v; WANT: %v", got, want)
791 }
792 if got, want := tv.IsExpired(), false; got != want {
793 t.Errorf("GOT: %v; WANT: %v", got, want)
794 }
795
796 stats := swr.Stats()
797
798 if got, want := stats.Count, int64(1); got != want {
799 t.Errorf("GOT: %v; WANT: %v", got, want)
800 }
801 if got, want := stats.Creates, int64(1); got != want {
802 t.Errorf("GOT: %v; WANT: %v", got, want)
803 }
804 if got, want := stats.Deletes, int64(0); got != want {
805 t.Errorf("GOT: %v; WANT: %v", got, want)
806 }
807 if got, want := stats.Evictions, int64(0); got != want {
808 t.Errorf("GOT: %v; WANT: %v", got, want)
809 }
810 if got, want := stats.LookupErrors, int64(0); got != want {
811 t.Errorf("GOT: %v; WANT: %v", got, want)
812 }
813 if got, want := stats.Queries, int64(1); got != want {
814 t.Errorf("GOT: %v; WANT: %v", got, want)
815 }
816 if got, want := stats.Hits, int64(0); got != want {
817 t.Errorf("GOT: %v; WANT: %v", got, want)
818 }
819 if got, want := stats.Misses, int64(0); got != want {
820 t.Errorf("GOT: %v; WANT: %v", got, want)
821 }
822 if got, want := stats.Stales, int64(1); got != want {
823 t.Errorf("GOT: %v; WANT: %v", got, want)
824 }
825 if got, want := stats.Stores, int64(1); got != want {
826 t.Errorf("GOT: %v; WANT: %v", got, want)
827 }
828 if got, want := stats.Updates, int64(0); got != want {
829 t.Errorf("GOT: %v; WANT: %v", got, want)
830 }
831 })
832
833 t.Run("expired", func(t *testing.T) {
834 swr, err := NewSimple(nil)
835 if err != nil {
836 t.Fatal(err)
837 }
838 swr.Store("foo", TimedValue{
839 Value: "foo",
840 Stale: time.Now().Add(-time.Second),
841 Expiry: time.Now().Add(-time.Second),
842 })
843 tv := swr.LoadTimedValue("foo")
844 if got, want := tv.IsStale(), true; got != want {
845 t.Errorf("GOT: %v; WANT: %v", got, want)
846 }
847 if got, want := tv.IsExpired(), true; got != want {
848 t.Errorf("GOT: %v; WANT: %v", got, want)
849 }
850
851 stats := swr.Stats()
852
853 if got, want := stats.Count, int64(1); got != want {
854 t.Errorf("GOT: %v; WANT: %v", got, want)
855 }
856 if got, want := stats.Creates, int64(1); got != want {
857 t.Errorf("GOT: %v; WANT: %v", got, want)
858 }
859 if got, want := stats.Deletes, int64(0); got != want {
860 t.Errorf("GOT: %v; WANT: %v", got, want)
861 }
862 if got, want := stats.Evictions, int64(0); got != want {
863 t.Errorf("GOT: %v; WANT: %v", got, want)
864 }
865 if got, want := stats.LookupErrors, int64(0); got != want {
866 t.Errorf("GOT: %v; WANT: %v", got, want)
867 }
868 if got, want := stats.Queries, int64(1); got != want {
869 t.Errorf("GOT: %v; WANT: %v", got, want)
870 }
871 if got, want := stats.Hits, int64(0); got != want {
872 t.Errorf("GOT: %v; WANT: %v", got, want)
873 }
874 if got, want := stats.Misses, int64(1); got != want {
875 t.Errorf("GOT: %v; WANT: %v", got, want)
876 }
877 if got, want := stats.Stales, int64(0); got != want {
878 t.Errorf("GOT: %v; WANT: %v", got, want)
879 }
880 if got, want := stats.Stores, int64(1); got != want {
881 t.Errorf("GOT: %v; WANT: %v", got, want)
882 }
883 if got, want := stats.Updates, int64(0); got != want {
884 t.Errorf("GOT: %v; WANT: %v", got, want)
885 }
886 })
887 })
888 }
44 "time"
55 )
66
7 // TimedValue couples a value or the error with both a stale and expiry time for the value and
8 // error.
7 // TimedValueStatus is an enumeration of the states of a TimedValue: Fresh,
8 // Stale, or Expired.
9 type TimedValueStatus int
10
11 const (
12 // Fresh items are not yet Stale, and will be returned immediately on Query
13 // without scheduling an asynchronous Lookup.
14 Fresh TimedValueStatus = iota
15
16 // Stale items have exceeded their Fresh status, and will be returned
17 // immediately on Query, but will also schedule an asynchronous Lookup if a
18 // Lookup for this key is not yet in flight.
19 Stale
20
21 // Expired items have exceeded their Fresh and Stale status, and will be
22 // evicted during the next background cache eviction loop, controlled by
23 // GCPeriodicity parameter, or upon Query, which will cause the Query to
24 // block until a new value can be obtained from the Lookup function.
25 Expired
26 )
27
28 // TimedValue couples a value or the error with both a stale and expiry time for
29 // the value and error.
930 type TimedValue struct {
1031 // Value stores the datum returned by the lookup function.
1132 Value interface{}
1334 // Err stores the error returned by the lookup function.
1435 Err error
1536
16 // Stale stores the time at which the value becomes stale. On Query, a stale value will
17 // trigger an asynchronous lookup of a replacement value, and the original value is
18 // returned. A zero-value for Stale implies the value never goes stale, and querying the key
19 // associated for this value will never trigger an asynchronous lookup of a replacement
20 // value.
37 // Stale stores the time at which the value becomes stale. On Query, a stale
38 // value will trigger an asynchronous lookup of a replacement value, and the
39 // original value is returned. A zero-value for Stale implies the value
40 // never goes stale, and querying the key associated for this value will
41 // never trigger an asynchronous lookup of a replacement value.
2142 Stale time.Time
2243
23 // Expiry stores the time at which the value expires. On Query, an expired value will block
24 // until a synchronous lookup of a replacement value is attempted. Once the lookup returns,
25 // the Query method will return with the new value or the error returned by the lookup
26 // function.
44 // Expiry stores the time at which the value expires. On Query, an expired
45 // value will block until a synchronous lookup of a replacement value is
46 // attempted. Once the lookup returns, the Query method will return with the
47 // new value or the error returned by the lookup function.
2748 Expiry time.Time
49
50 // Created stores the time at which the value was created.
51 Created time.Time
2852 }
2953
30 // IsExpired returns true if and only if value is expired. A value is expired when its non-zero
31 // expiry time is before the current time, or when the value represents an error and expiry time is
32 // the time.Time zero-value.
54 // IsExpired returns true when the value is expired.
55 //
56 // A value is expired when its non-zero expiry time is before the current time,
57 // or when the value represents an error and expiry time is the time.Time
58 // zero-value.
3359 func (tv *TimedValue) IsExpired() bool {
34 return tv.isExpired(time.Now())
60 return tv.IsExpiredAt(time.Now())
3561 }
3662
37 // provided for internal use so we don't need to repeatedly get the current time
38 func (tv *TimedValue) isExpired(when time.Time) bool {
63 // IsExpiredAt returns true when the value is expired at the specified time.
64 //
65 // A value is expired when its non-zero expiry time is before the specified
66 // time, or when the value represents an error and expiry time is the time.Time
67 // zero-value.
68 func (tv *TimedValue) IsExpiredAt(when time.Time) bool {
3969 if tv.Err == nil {
4070 return !tv.Expiry.IsZero() && when.After(tv.Expiry)
4171 }
42 // NOTE: When a TimedValue stores an error result, then Expiry and Expiry zero-values imply
43 // the value is immediately expired.
72 // NOTE: When a TimedValue stores an error result, then a zero-value for the
73 // Expiry imply the value is immediately expired.
4474 return tv.Expiry.IsZero() || when.After(tv.Expiry)
4575 }
4676
47 // IsStale returns true if and only if value is stale. A value is stale when its non-zero stale time
48 // is before the current time, or when the value represents an error and stale time is the time.Time
77 // IsStale returns true when the value is stale.
78 //
79 // A value is stale when its non-zero stale time is before the current time, or
80 // when the value represents an error and stale time is the time.Time
4981 // zero-value.
5082 func (tv *TimedValue) IsStale() bool {
51 return tv.isStale(time.Now())
83 return tv.IsStaleAt(time.Now())
5284 }
5385
54 // provided for internal use so we don't need to repeatedly get the current time
55 func (tv *TimedValue) isStale(when time.Time) bool {
86 // IsStaleAt returns true when the value is stale at the specified time.
87 //
88 // A value is stale when its non-zero stale time is before the specified time,
89 // or when the value represents an error and stale time is the time.Time
90 // zero-value.
91 func (tv *TimedValue) IsStaleAt(when time.Time) bool {
5692 if tv.Err == nil {
5793 return !tv.Stale.IsZero() && when.After(tv.Stale)
5894 }
59 // NOTE: When a TimedValue stores an error result, then Stale and Expiry zero-values imply
60 // the value is immediately stale.
95 // NOTE: When a TimedValue stores an error result, then a zero-value for the
96 // Stale or Expiry imply the value is immediately stale.
6197 return tv.Stale.IsZero() || when.After(tv.Stale)
98 }
99
100 // Status returns Fresh, Stale, or Exired, depending on the status of the
101 // TimedValue item at the current time.
102 func (tv *TimedValue) Status() TimedValueStatus {
103 return tv.StatusAt(time.Now())
104 }
105
106 // StatusAt returns Fresh, Stale, or Exired, depending on the status of the
107 // TimedValue item at the specified time.
108 func (tv *TimedValue) StatusAt(when time.Time) TimedValueStatus {
109 if tv.IsExpiredAt(when) {
110 return Expired
111 }
112 if tv.IsStaleAt(when) {
113 return Stale
114 }
115 return Fresh
62116 }
63117
64118 // helper function to wrap non TimedValue items as TimedValue items.
65119 func newTimedValue(value interface{}, err error, staleDuration, expiryDuration time.Duration) *TimedValue {
66120 switch val := value.(type) {
67121 case TimedValue:
122 if val.Created.IsZero() {
123 val.Created = time.Now()
124 }
68125 return &val
69126 case *TimedValue:
127 if val.Created.IsZero() {
128 val.Created = time.Now()
129 }
70130 return val
71131 default:
72132 if staleDuration == 0 && expiryDuration == 0 {
73 return &TimedValue{Value: value, Err: err}
133 return &TimedValue{Value: value, Err: err, Created: time.Now()}
74134 }
75135 var stale, expiry time.Time
76136 now := time.Now()
80140 if expiryDuration > 0 {
81141 expiry = now.Add(expiryDuration)
82142 }
83 return &TimedValue{Value: value, Err: err, Stale: stale, Expiry: expiry}
143 return &TimedValue{Value: value, Err: err, Created: time.Now(), Stale: stale, Expiry: expiry}
84144 }
85145 }
86146