Codebase list golang-github-renekroon-ttlcache / upstream/2.8.0+ds
New upstream version 2.8.0+ds Sascha Steinbiss 2 years ago
4 changed file(s) with 185 addition(s) and 18 deletion(s). Raw diff Collapse all Expand all
00 language: go
11
22 go:
3 - "1.17.x"
34 - "1.16.x"
4 - "1.15.x"
55
66 git:
77 depth: 1
1414
1515 Note (issue #25): by default, due to historic reasons, the TTL will be reset on each cache hit and you need to explicitly configure the cache to use a TTL that will not get extended.
1616
17 [![Build Status](https://travis-ci.org/ReneKroon/ttlcache.svg?branch=master)](https://travis-ci.org/ReneKroon/ttlcache)
17 [![Build Status](https://www.travis-ci.com/ReneKroon/ttlcache.svg?branch=master)](https://travis-ci.com/ReneKroon/ttlcache)
1818 [![Go Report Card](https://goreportcard.com/badge/github.com/ReneKroon/ttlcache)](https://goreportcard.com/report/github.com/ReneKroon/ttlcache)
1919 [![Coverage Status](https://coveralls.io/repos/github/ReneKroon/ttlcache/badge.svg?branch=master)](https://coveralls.io/github/ReneKroon/ttlcache?branch=master)
2020 [![GitHub issues](https://img.shields.io/github/issues/ReneKroon/ttlcache.svg)](https://github.com/ReneKroon/ttlcache/issues)
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
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) {
285 cache.mutex.Lock()
286 if cache.isShutDown {
287 cache.mutex.Unlock()
288 return nil, ErrClosed
293 dataToReturn, _, err := cache.GetByLoaderWithTtl(key, customLoaderFunction)
294
295 return dataToReturn, err
296 }
297
298 // GetByLoaderWithTtl can take a per key loader function (ie. to propagate context)
299 func (cache *Cache) GetByLoaderWithTtl(key string, customLoaderFunction LoaderFunction) (interface{}, time.Duration, error) {
300 cache.mutex.Lock()
301 if cache.isShutDown {
302 cache.mutex.Unlock()
303 return nil, 0, ErrClosed
289304 }
290305
291306 cache.metrics.Hits++
292307 item, exists, triggerExpirationNotification := cache.getItem(key)
293308
294309 var dataToReturn interface{}
310 ttlToReturn := time.Duration(0)
295311 if exists {
296312 cache.metrics.Retrievals++
297313 dataToReturn = item.data
314 ttlToReturn = time.Until(item.expireAt)
315 if ttlToReturn < 0 {
316 ttlToReturn = 0
317 }
298318 }
299319
300320 var err error
313333 }
314334
315335 if loaderFunction != nil && !exists {
316 cache.mutex.Unlock()
317 dataToReturn, err, _ = cache.loaderLock.Do(key, func() (interface{}, error) {
336 type loaderResult struct {
337 data interface{}
338 ttl time.Duration
339 }
340 ch := cache.loaderLock.DoChan(key, func() (interface{}, error) {
318341 // cache is not blocked during io
319 invokeData, err := cache.invokeLoader(key, loaderFunction)
320 return invokeData, err
342 invokeData, ttl, err := cache.invokeLoader(key, loaderFunction)
343 lr := &loaderResult{
344 data: invokeData,
345 ttl: ttl,
346 }
347 return lr, err
321348 })
349 cache.mutex.Unlock()
350 res := <-ch
351 dataToReturn = res.Val.(*loaderResult).data
352 ttlToReturn = res.Val.(*loaderResult).ttl
353 err = res.Err
322354 }
323355
324356 if triggerExpirationNotification {
325357 cache.expirationNotification <- true
326358 }
327359
328 return dataToReturn, err
329 }
330
331 func (cache *Cache) invokeLoader(key string, loaderFunction LoaderFunction) (dataToReturn interface{}, err error) {
332 var ttl time.Duration
333
360 return dataToReturn, ttlToReturn, err
361 }
362
363 func (cache *Cache) invokeLoader(key string, loaderFunction LoaderFunction) (dataToReturn interface{}, ttl time.Duration, err error) {
334364 dataToReturn, ttl, err = loaderFunction(key)
335365 if err == nil {
336366 err = cache.SetWithTTL(key, dataToReturn, ttl)
338368 dataToReturn = nil
339369 }
340370 }
341 return dataToReturn, err
371 return dataToReturn, ttl, err
342372 }
343373
344374 // 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