Codebase list golang-github-renekroon-ttlcache / upstream/2.7.0+ds+git20210725.1.c2e46a7
Import upstream version 2.7.0+ds+git20210725.1.c2e46a7 Debian Janitor 2 years ago
4 changed file(s) with 256 addition(s) and 8 deletion(s). Raw diff Collapse all Expand all
2121 [![license](https://img.shields.io/github/license/ReneKroon/ttlcache.svg?maxAge=2592000)](https://github.com/ReneKroon/ttlcache/LICENSE)
2222
2323 ## Usage
24
25 `go get github.com/ReneKroon/ttlcache/v2`
2426
2527 You can copy it as a full standalone demo program. The first snippet is basic usage, where the second exploits more options in the cache.
2628
0 package bench
1
2 import (
3 "fmt"
4 "testing"
5 "time"
6
7 ttlcache "github.com/ReneKroon/ttlcache/v2"
8 )
9
10 func BenchmarkCacheSetWithoutTTL(b *testing.B) {
11 cache := ttlcache.NewCache()
12 defer cache.Close()
13
14 for n := 0; n < b.N; n++ {
15 cache.Set(fmt.Sprint(n%1000000), "value")
16 }
17 }
18
19 func BenchmarkCacheSetWithGlobalTTL(b *testing.B) {
20 cache := ttlcache.NewCache()
21 defer cache.Close()
22
23 cache.SetTTL(time.Duration(50 * time.Millisecond))
24 for n := 0; n < b.N; n++ {
25 cache.Set(fmt.Sprint(n%1000000), "value")
26 }
27 }
28
29 func BenchmarkCacheSetWithTTL(b *testing.B) {
30 cache := ttlcache.NewCache()
31 defer cache.Close()
32
33 for n := 0; n < b.N; n++ {
34 cache.SetWithTTL(fmt.Sprint(n%1000000), "value", time.Duration(50*time.Millisecond))
35 }
36 }
00 package ttlcache
11
22 import (
3 "golang.org/x/sync/singleflight"
43 "sync"
54 "time"
5
6 "golang.org/x/sync/singleflight"
67 )
78
89 // CheckExpireCallback is used as a callback for an external check on item expiration
2122 // SimpleCache interface enables a quick-start. Interface for basic usage.
2223 type SimpleCache interface {
2324 Get(key string) (interface{}, error)
25 GetWithTTL(key string) (interface{}, time.Duration, error)
2426 Set(key string, data interface{}) error
2527 SetTTL(ttl time.Duration) error
2628 SetWithTTL(key string, data interface{}, ttl time.Duration) error
280282 return cache.GetByLoader(key, nil)
281283 }
282284
285 // GetWithTTL has exactly the same behaviour as Get but also returns
286 // the remaining TTL for an specific item at the moment it its retrieved
287 func (cache *Cache) GetWithTTL(key string) (interface{}, time.Duration, error) {
288 return cache.GetByLoaderWithTtl(key, nil)
289 }
290
283291 // GetByLoader can take a per key loader function (ie. to propagate context)
284292 func (cache *Cache) GetByLoader(key string, customLoaderFunction LoaderFunction) (interface{}, error) {
285293 cache.mutex.Lock()
313321 }
314322
315323 if loaderFunction != nil && !exists {
316 cache.mutex.Unlock()
317 dataToReturn, err, _ = cache.loaderLock.Do(key, func() (interface{}, error) {
324 ch := cache.loaderLock.DoChan(key, func() (interface{}, error) {
318325 // cache is not blocked during io
319 invokeData, err := cache.invokeLoader(key, loaderFunction)
326 invokeData, _, err := cache.invokeLoader(key, loaderFunction)
320327 return invokeData, err
321328 })
329 cache.mutex.Unlock()
330 res := <-ch
331 dataToReturn = res.Val
332 err = res.Err
322333 }
323334
324335 if triggerExpirationNotification {
328339 return dataToReturn, err
329340 }
330341
331 func (cache *Cache) invokeLoader(key string, loaderFunction LoaderFunction) (dataToReturn interface{}, err error) {
332 var ttl time.Duration
333
342 // GetByLoaderWithTtl can take a per key loader function (ie. to propagate context)
343 func (cache *Cache) GetByLoaderWithTtl(key string, customLoaderFunction LoaderFunction) (interface{}, time.Duration, error) {
344 cache.mutex.Lock()
345 if cache.isShutDown {
346 cache.mutex.Unlock()
347 return nil, 0, ErrClosed
348 }
349
350 cache.metrics.Hits++
351 item, exists, triggerExpirationNotification := cache.getItem(key)
352
353 var dataToReturn interface{}
354 ttlToReturn := time.Duration(0)
355 if exists {
356 cache.metrics.Retrievals++
357 dataToReturn = item.data
358 ttlToReturn = time.Until(item.expireAt)
359 if ttlToReturn < 0 {
360 ttlToReturn = 0
361 }
362 }
363
364 var err error
365 if !exists {
366 cache.metrics.Misses++
367 err = ErrNotFound
368 }
369
370 loaderFunction := cache.loaderFunction
371 if customLoaderFunction != nil {
372 loaderFunction = customLoaderFunction
373 }
374
375 if loaderFunction == nil || exists {
376 cache.mutex.Unlock()
377 }
378
379 if loaderFunction != nil && !exists {
380 type loaderResult struct {
381 data interface{}
382 ttl time.Duration
383 }
384 ch := cache.loaderLock.DoChan(key, func() (interface{}, error) {
385 // cache is not blocked during io
386 invokeData, ttl, err := cache.invokeLoader(key, loaderFunction)
387 lr := &loaderResult{
388 data: invokeData,
389 ttl: ttl,
390 }
391 return lr, err
392 })
393 cache.mutex.Unlock()
394 res := <-ch
395 dataToReturn = res.Val.(*loaderResult).data
396 ttlToReturn = res.Val.(*loaderResult).ttl
397 err = res.Err
398 }
399
400 if triggerExpirationNotification {
401 cache.expirationNotification <- true
402 }
403
404 return dataToReturn, ttlToReturn, err
405 }
406
407 func (cache *Cache) invokeLoader(key string, loaderFunction LoaderFunction) (dataToReturn interface{}, ttl time.Duration, err error) {
334408 dataToReturn, ttl, err = loaderFunction(key)
335409 if err == nil {
336410 err = cache.SetWithTTL(key, dataToReturn, ttl)
338412 dataToReturn = nil
339413 }
340414 }
341 return dataToReturn, err
415 return dataToReturn, ttl, err
342416 }
343417
344418 // Remove removes an item from the cache if it exists, triggers expiration callback when set. Can return ErrNotFound if the entry was not present.
3232
3333 }
3434
35 // Issue 45 : This test was used to test different code paths for best performance.
36 func TestCache_GetByLoaderRace(t *testing.T) {
37 t.Skip()
38 t.Parallel()
39 cache := NewCache()
40 cache.SetTTL(time.Microsecond)
41 defer cache.Close()
42
43 loaderInvocations := uint64(0)
44 inFlight := uint64(0)
45
46 globalLoader := func(key string) (data interface{}, ttl time.Duration, err error) {
47 atomic.AddUint64(&inFlight, 1)
48 atomic.AddUint64(&loaderInvocations, 1)
49 time.Sleep(time.Microsecond)
50 assert.Equal(t, uint64(1), inFlight)
51 defer atomic.AddUint64(&inFlight, ^uint64(0))
52 return "global", 0, nil
53
54 }
55 cache.SetLoaderFunction(globalLoader)
56
57 for i := 0; i < 1000; i++ {
58 wg := sync.WaitGroup{}
59 for i := 0; i < 1000; i++ {
60 wg.Add(1)
61 go func() {
62 key, _ := cache.Get("test")
63 assert.Equal(t, "global", key)
64 wg.Done()
65
66 }()
67 }
68 wg.Wait()
69 t.Logf("Invocations: %d\n", loaderInvocations)
70 loaderInvocations = 0
71 }
72
73 }
74
3575 // Issue / PR #39: add customer loader function for each Get() #
3676 // some middleware prefers to define specific context's etc per Get.
3777 // This is faciliated by supplying a loder function with Get's.
67107 defaultKey, _ := cache.GetByLoader("test", nil)
68108 assert.Equal(t, "global", defaultKey)
69109
110 cache.Remove("test")
111 }
112
113 func TestCache_GetByLoaderWithTtl(t *testing.T) {
114 t.Parallel()
115 cache := NewCache()
116 defer cache.Close()
117
118 globalTtl := time.Duration(time.Minute)
119 globalLoader := func(key string) (data interface{}, ttl time.Duration, err error) {
120 return "global", globalTtl, nil
121 }
122 cache.SetLoaderFunction(globalLoader)
123
124 localTtl := time.Duration(time.Hour)
125 localLoader := func(key string) (data interface{}, ttl time.Duration, err error) {
126 return "local", localTtl, nil
127 }
128
129 key, ttl, _ := cache.GetWithTTL("test")
130 assert.Equal(t, "global", key)
131 assert.Equal(t, ttl, globalTtl)
132 cache.Remove("test")
133
134 localKey, ttl2, _ := cache.GetByLoaderWithTtl("test", localLoader)
135 assert.Equal(t, "local", localKey)
136 assert.Equal(t, ttl2, localTtl)
137 cache.Remove("test")
138
139 globalKey, ttl3, _ := cache.GetByLoaderWithTtl("test", globalLoader)
140 assert.Equal(t, "global", globalKey)
141 assert.Equal(t, ttl3, globalTtl)
142 cache.Remove("test")
143
144 defaultKey, ttl4, _ := cache.GetByLoaderWithTtl("test", nil)
145 assert.Equal(t, "global", defaultKey)
146 assert.Equal(t, ttl4, globalTtl)
70147 cache.Remove("test")
71148 }
72149
751828 assert.Equal(t, []string{"hello"}, keys, "Expected keys contains 'hello'")
752829 }
753830
831 func TestCacheGetWithTTL(t *testing.T) {
832 t.Parallel()
833
834 cache := NewCache()
835 defer cache.Close()
836
837 data, ttl, exists := cache.GetWithTTL("hello")
838 assert.Equal(t, exists, ErrNotFound, "Expected empty cache to return no data")
839 assert.Nil(t, data, "Expected data to be empty")
840 assert.Equal(t, int(ttl), 0, "Expected item TTL to be 0")
841
842 cache.Set("hello", "world")
843 data, ttl, exists = cache.GetWithTTL("hello")
844 assert.NotNil(t, data, "Expected data to be not nil")
845 assert.Equal(t, nil, exists, "Expected data to exist")
846 assert.Equal(t, "world", (data.(string)), "Expected data content to be 'world'")
847 assert.Equal(t, int(ttl), 0, "Expected item TTL to be 0")
848
849 orgttl := time.Duration(500 * time.Millisecond)
850 cache.SetWithTTL("hello", "world", orgttl)
851 time.Sleep(10 * time.Millisecond)
852 data, ttl, exists = cache.GetWithTTL("hello")
853 assert.NotNil(t, data, "Expected data to be not nil")
854 assert.Equal(t, nil, exists, "Expected data to exist")
855 assert.Equal(t, "world", (data.(string)), "Expected data content to be 'world'")
856 assert.Less(t, ttl, orgttl, "Expected item TTL to be less than the original TTL")
857 assert.NotEqual(t, int(ttl), 0, "Expected item TTL to be not 0")
858 }
859
860 func TestCache_TestGetWithTTLAndLoaderFunction(t *testing.T) {
861 t.Parallel()
862 cache := NewCache()
863
864 cache.SetLoaderFunction(func(key string) (data interface{}, ttl time.Duration, err error) {
865 return nil, 0, ErrNotFound
866 })
867
868 _, ttl, err := cache.GetWithTTL("1")
869 assert.Equal(t, ErrNotFound, err, "Expected error to be ErrNotFound")
870 assert.Equal(t, int(ttl), 0, "Expected item TTL to be 0")
871
872 orgttl := time.Duration(1 * time.Second)
873 cache.SetLoaderFunction(func(key string) (data interface{}, ttl time.Duration, err error) {
874 return "1", orgttl, nil
875 })
876
877 value, ttl, found := cache.GetWithTTL("1")
878 assert.Equal(t, nil, found)
879 assert.Equal(t, "1", value)
880 assert.Equal(t, ttl, orgttl, "Expected item TTL to be the same as the original TTL")
881 cache.Close()
882
883 value, ttl, found = cache.GetWithTTL("1")
884 assert.Equal(t, ErrClosed, found)
885 assert.Equal(t, nil, value)
886 assert.Equal(t, int(ttl), 0, "Expected returned ttl for an ErrClosed err to be 0")
887 }
888
754889 func TestCacheExpirationCallbackFunction(t *testing.T) {
755890 t.Parallel()
756891