New upstream version 2.3.0+ds
Sascha Steinbiss
2 years ago
0 | # 2.3.0 (February 2021) | |
1 | ||
2 | ## API changes: | |
3 | ||
4 | * #38: Added func (cache *Cache) SetExpirationReasonCallback(callback ExpireReasonCallback) This wil function will replace SetExpirationCallback(..) in the next major version. | |
5 | ||
6 | # 2.2.0 (January 2021) | |
7 | ||
8 | ## API changes: | |
9 | ||
10 | * #37 : a GetMetrics call is now available for some information on hits/misses etc. | |
11 | * #34 : Errors are now const | |
12 | ||
0 | 13 | # 2.1.0 (October 2020) |
1 | 14 | |
2 | 15 | ## API changes |
52 | 52 | // all other values are allowed to expire |
53 | 53 | return true |
54 | 54 | } |
55 | expirationCallback := func(key string, value interface{}) { | |
56 | fmt.Printf("This key(%s) has expired\n", key) | |
55 | ||
56 | expirationCallback := func(key string, reason ttlcache.EvictionReason, value interface{}) { | |
57 | fmt.Printf("This key(%s) has expired because of %s\n", key, reason) | |
57 | 58 | } |
58 | 59 | |
59 | 60 | loaderFunction := func(key string) (data interface{}, ttl time.Duration, err error) { |
64 | 65 | } |
65 | 66 | |
66 | 67 | cache := ttlcache.NewCache() |
67 | defer cache.Close() | |
68 | 68 | cache.SetTTL(time.Duration(10 * time.Second)) |
69 | cache.SetExpirationCallback(expirationCallback) | |
69 | cache.SetExpirationReasonCallback(expirationCallback) | |
70 | 70 | cache.SetLoaderFunction(loaderFunction) |
71 | 71 | cache.SetNewItemCallback(newItemCallback) |
72 | 72 | cache.SetCheckExpirationCallback(checkExpirationCallback) |
73 | cache.SetCacheSizeLimit(2) | |
73 | 74 | |
74 | 75 | cache.Set("key", "value") |
75 | 76 | cache.SetWithTTL("keyWithTTL", "value", 10*time.Second) |
81 | 82 | if result := cache.Remove("keyNNN"); result == notFound { |
82 | 83 | fmt.Printf("Not found, %d items left\n", count) |
83 | 84 | } |
85 | ||
86 | cache.Set("key6", "value") | |
87 | cache.Set("key7", "value") | |
88 | metrics := cache.GetMetrics() | |
89 | fmt.Printf("Total inserted: %d\n", metrics.Inserted) | |
90 | ||
91 | cache.Close() | |
92 | ||
84 | 93 | } |
85 | 94 | |
86 | 95 | func getFromNetwork(key string) (string, error) { |
8 | 8 | type CheckExpireCallback func(key string, value interface{}) bool |
9 | 9 | |
10 | 10 | // ExpireCallback is used as a callback on item expiration or when notifying of an item new to the cache |
11 | // Note that ExpireReasonCallback will be the succesor of this function in the next major release. | |
11 | 12 | type ExpireCallback func(key string, value interface{}) |
13 | ||
14 | // ExpireReasonCallback is used as a callback on item expiration with extra information why the item expired. | |
15 | type ExpireReasonCallback func(key string, reason EvictionReason, value interface{}) | |
12 | 16 | |
13 | 17 | // LoaderFunction can be supplied to retrieve an item where a cache miss occurs. Supply an item specific ttl or Duration.Zero |
14 | 18 | type LoaderFunction func(key string) (data interface{}, ttl time.Duration, err error) |
20 | 24 | items map[string]*item |
21 | 25 | loaderLock map[string]*sync.Cond |
22 | 26 | expireCallback ExpireCallback |
27 | expireReasonCallback ExpireReasonCallback | |
23 | 28 | checkExpireCallback CheckExpireCallback |
24 | 29 | newItemCallback ExpireCallback |
25 | 30 | priorityQueue *priorityQueue |
33 | 38 | metrics Metrics |
34 | 39 | } |
35 | 40 | |
41 | // EvictionReason is an enum that explains why an item was evicted | |
42 | type EvictionReason int | |
43 | ||
44 | const ( | |
45 | // Removed : explicitly removed from cache via API call | |
46 | Removed EvictionReason = iota | |
47 | // EvictedSize : evicted due to exceeding the cache size | |
48 | EvictedSize | |
49 | // Expired : the time to live is zero and therefore the item is removed | |
50 | Expired | |
51 | // Closed : the cache was closed | |
52 | Closed | |
53 | ) | |
54 | ||
36 | 55 | const ( |
37 | 56 | // ErrClosed is raised when operating on a cache where Close() has already been called. |
38 | 57 | ErrClosed = constError("cache already closed") |
50 | 69 | item, exists := cache.items[key] |
51 | 70 | if !exists || item.expired() { |
52 | 71 | return nil, false, false |
53 | } else { | |
54 | 72 | } |
55 | 73 | |
56 | 74 | if item.ttl >= 0 && (item.ttl > 0 || cache.ttl > 0) { |
102 | 120 | timer.Stop() |
103 | 121 | cache.mutex.Lock() |
104 | 122 | if cache.priorityQueue.Len() > 0 { |
105 | cache.evictjob() | |
123 | cache.evictjob(Closed) | |
106 | 124 | } |
107 | 125 | cache.mutex.Unlock() |
108 | 126 | shutdownFeedback <- struct{}{} |
125 | 143 | } |
126 | 144 | } |
127 | 145 | |
128 | func (cache *Cache) removeItem(item *item) { | |
129 | cache.metrics.Evicted++ | |
146 | func (cache *Cache) checkExpirationCallback(item *item, reason EvictionReason) { | |
130 | 147 | if cache.expireCallback != nil { |
131 | 148 | go cache.expireCallback(item.key, item.data) |
132 | 149 | } |
150 | if cache.expireReasonCallback != nil { | |
151 | go cache.expireReasonCallback(item.key, reason, item.data) | |
152 | } | |
153 | } | |
154 | ||
155 | func (cache *Cache) removeItem(item *item, reason EvictionReason) { | |
156 | cache.metrics.Evicted++ | |
157 | cache.checkExpirationCallback(item, reason) | |
133 | 158 | cache.priorityQueue.remove(item) |
134 | 159 | delete(cache.items, item.key) |
135 | 160 | |
136 | 161 | } |
137 | 162 | |
138 | func (cache *Cache) evictjob() { | |
163 | func (cache *Cache) evictjob(reason EvictionReason) { | |
139 | 164 | // index will only be advanced if the current entry will not be evicted |
140 | 165 | i := 0 |
141 | 166 | for item := cache.priorityQueue.items[i]; ; item = cache.priorityQueue.items[i] { |
142 | 167 | |
143 | cache.removeItem(item) | |
168 | cache.removeItem(item, reason) | |
144 | 169 | if cache.priorityQueue.Len() == 0 { |
145 | 170 | return |
146 | 171 | } |
164 | 189 | } |
165 | 190 | } |
166 | 191 | |
167 | cache.removeItem(item) | |
192 | cache.removeItem(item, Expired) | |
168 | 193 | if cache.priorityQueue.Len() == 0 { |
169 | 194 | return |
170 | 195 | } |
209 | 234 | item.ttl = ttl |
210 | 235 | } else { |
211 | 236 | if cache.sizeLimit != 0 && len(cache.items) >= cache.sizeLimit { |
212 | cache.removeItem(cache.priorityQueue.items[0]) | |
237 | cache.removeItem(cache.priorityQueue.items[0], EvictedSize) | |
213 | 238 | } |
214 | 239 | item = newItem(key, data, ttl) |
215 | 240 | cache.items[key] = item |
326 | 351 | if !exists { |
327 | 352 | return ErrNotFound |
328 | 353 | } |
329 | cache.removeItem(object) | |
354 | cache.removeItem(object, Removed) | |
330 | 355 | |
331 | 356 | return nil |
332 | 357 | } |
360 | 385 | // SetExpirationCallback sets a callback that will be called when an item expires |
361 | 386 | func (cache *Cache) SetExpirationCallback(callback ExpireCallback) { |
362 | 387 | cache.expireCallback = callback |
388 | } | |
389 | ||
390 | // SetExpirationReasonCallback sets a callback that will be called when an item expires, includes reason of expiry | |
391 | func (cache *Cache) SetExpirationReasonCallback(callback ExpireReasonCallback) { | |
392 | cache.expireReasonCallback = callback | |
363 | 393 | } |
364 | 394 | |
365 | 395 | // SetCheckExpirationCallback sets a callback that will be called when an item is about to expire |
17 | 17 | |
18 | 18 | func TestMain(m *testing.M) { |
19 | 19 | goleak.VerifyTestMain(m) |
20 | } | |
21 | ||
22 | // Issue #38: Feature request: ability to know why an expiry has occurred | |
23 | func TestCache_textExpirationReasons(t *testing.T) { | |
24 | t.Parallel() | |
25 | cache := NewCache() | |
26 | ||
27 | var reason EvictionReason | |
28 | var sync = make(chan struct{}) | |
29 | expirationReason := func(key string, evReason EvictionReason, value interface{}) { | |
30 | reason = evReason | |
31 | sync <- struct{}{} | |
32 | } | |
33 | cache.SetExpirationReasonCallback(expirationReason) | |
34 | ||
35 | cache.SetTTL(time.Millisecond) | |
36 | cache.Set("one", "one") | |
37 | <-sync | |
38 | assert.Equal(t, Expired, reason) | |
39 | ||
40 | cache.SetTTL(time.Hour) | |
41 | cache.SetCacheSizeLimit(1) | |
42 | cache.Set("two", "two") | |
43 | cache.Set("twoB", "twoB") | |
44 | <-sync | |
45 | assert.Equal(t, EvictedSize, reason) | |
46 | ||
47 | cache.Remove("twoB") | |
48 | <-sync | |
49 | assert.Equal(t, Removed, reason) | |
50 | ||
51 | cache.SetTTL(time.Hour) | |
52 | cache.Set("three", "three") | |
53 | cache.Close() | |
54 | <-sync | |
55 | assert.Equal(t, Closed, reason) | |
56 | ||
20 | 57 | } |
21 | 58 | |
22 | 59 | // Issue #37: Cache metrics |
0 | // Code generated by "enumer -type EvictionReason"; DO NOT EDIT. | |
1 | ||
2 | // | |
3 | package ttlcache | |
4 | ||
5 | import ( | |
6 | "fmt" | |
7 | ) | |
8 | ||
9 | const _EvictionReasonName = "RemovedEvictedSizeExpiredClosed" | |
10 | ||
11 | var _EvictionReasonIndex = [...]uint8{0, 7, 18, 25, 31} | |
12 | ||
13 | func (i EvictionReason) String() string { | |
14 | if i < 0 || i >= EvictionReason(len(_EvictionReasonIndex)-1) { | |
15 | return fmt.Sprintf("EvictionReason(%d)", i) | |
16 | } | |
17 | return _EvictionReasonName[_EvictionReasonIndex[i]:_EvictionReasonIndex[i+1]] | |
18 | } | |
19 | ||
20 | var _EvictionReasonValues = []EvictionReason{0, 1, 2, 3} | |
21 | ||
22 | var _EvictionReasonNameToValueMap = map[string]EvictionReason{ | |
23 | _EvictionReasonName[0:7]: 0, | |
24 | _EvictionReasonName[7:18]: 1, | |
25 | _EvictionReasonName[18:25]: 2, | |
26 | _EvictionReasonName[25:31]: 3, | |
27 | } | |
28 | ||
29 | // EvictionReasonString retrieves an enum value from the enum constants string name. | |
30 | // Throws an error if the param is not part of the enum. | |
31 | func EvictionReasonString(s string) (EvictionReason, error) { | |
32 | if val, ok := _EvictionReasonNameToValueMap[s]; ok { | |
33 | return val, nil | |
34 | } | |
35 | return 0, fmt.Errorf("%s does not belong to EvictionReason values", s) | |
36 | } | |
37 | ||
38 | // EvictionReasonValues returns all values of the enum | |
39 | func EvictionReasonValues() []EvictionReason { | |
40 | return _EvictionReasonValues | |
41 | } | |
42 | ||
43 | // IsAEvictionReason returns "true" if the value is listed in the enum definition. "false" otherwise | |
44 | func (i EvictionReason) IsAEvictionReason() bool { | |
45 | for _, v := range _EvictionReasonValues { | |
46 | if i == v { | |
47 | return true | |
48 | } | |
49 | } | |
50 | return false | |
51 | } |
2 | 2 | go 1.15 |
3 | 3 | |
4 | 4 | require ( |
5 | github.com/alvaroloes/enumer v1.1.2 // indirect | |
5 | 6 | github.com/davecgh/go-spew v1.1.1 // indirect |
6 | 7 | github.com/stretchr/testify v1.7.0 |
7 | 8 | go.uber.org/goleak v1.1.10 |
0 | github.com/alvaroloes/enumer v1.1.2 h1:5khqHB33TZy1GWCO/lZwcroBFh7u+0j40T83VUbfAMY= | |
1 | github.com/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT0m65DJ+Wo= | |
0 | 2 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= |
1 | 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= |
2 | 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
11 | 13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
12 | 14 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= |
13 | 15 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= |
16 | github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1 h1:/I3lTljEEDNYLho3/FUB7iD/oc2cEFgVmbHzV+O0PtU= | |
17 | github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1/go.mod h1:eD5JxqMiuNYyFNmyY9rkJ/slN8y59oEu4Ei7F8OoKWQ= | |
14 | 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
15 | 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
16 | 20 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= |
54 | 58 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
55 | 59 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
56 | 60 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
61 | golang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | |
57 | 62 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E= |
58 | 63 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
59 | 64 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |