Package list golang-github-renekroon-ttlcache / 0634dc9
New upstream version 1.6.0 Sascha Steinbiss 1 year, 10 months ago
9 changed file(s) with 175 addition(s) and 107 deletion(s). Raw diff Collapse all Expand all
00 language: go
11
22 go:
3 - 1.13
34 - 1.12
4 - 1.11
55 git:
66 depth: 1
77
1313
1414 script:
1515 - golint .
16 - go test ./... -race -count=1 -timeout=1m -run .
17 - go test -cover ./...
18 - go test -run=Bench.* -bench=. -benchmem
16 - go test -cover -race -count=1 -timeout=30s -run .
17 - cd bench; go test -run=Bench.* -bench=. -benchmem
+0
-33
Gopkg.lock less more
0 # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
1
2
3 [[projects]]
4 digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
5 name = "github.com/davecgh/go-spew"
6 packages = ["spew"]
7 pruneopts = "UT"
8 revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
9 version = "v1.1.1"
10
11 [[projects]]
12 digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
13 name = "github.com/pmezard/go-difflib"
14 packages = ["difflib"]
15 pruneopts = "UT"
16 revision = "792786c7400a136282c1664665ae0a8db921c6c2"
17 version = "v1.0.0"
18
19 [[projects]]
20 digest = "1:972c2427413d41a1e06ca4897e8528e5a1622894050e2f527b38ddf0f343f759"
21 name = "github.com/stretchr/testify"
22 packages = ["assert"]
23 pruneopts = "UT"
24 revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
25 version = "v1.3.0"
26
27 [solve-meta]
28 analyzer-name = "dep"
29 analyzer-version = 1
30 input-imports = ["github.com/stretchr/testify/assert"]
31 solver-name = "gps-cdcl"
32 solver-version = 1
+0
-34
Gopkg.toml less more
0 # Gopkg.toml example
1 #
2 # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
3 # for detailed Gopkg.toml documentation.
4 #
5 # required = ["github.com/user/thing/cmd/thing"]
6 # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
7 #
8 # [[constraint]]
9 # name = "github.com/user/project"
10 # version = "1.0.0"
11 #
12 # [[constraint]]
13 # name = "github.com/user/project2"
14 # branch = "dev"
15 # source = "github.com/myfork/project2"
16 #
17 # [[override]]
18 # name = "github.com/x/y"
19 # version = "2.4.0"
20 #
21 # [prune]
22 # non-go = false
23 # go-tests = true
24 # unused-packages = true
25
26
27 [[constraint]]
28 name = "github.com/stretchr/testify"
29 version = "1.3.0"
30
31 [prune]
32 go-tests = true
33 unused-packages = true
66 3. Auto-Extending expiration on `Get` -or- DNS style TTL, see `SkipTtlExtensionOnHit(bool)`
77 4. Fast and memory efficient
88 5. Can trigger callback on key expiration
9 6. Cleanup resources by calling `Close()` at end of lifecycle.
910
1011 [![Build Status](https://travis-ci.org/ReneKroon/ttlcache.svg?branch=master)](https://travis-ci.org/ReneKroon/ttlcache)
1112
3637 }
3738
3839 cache := ttlcache.NewCache()
40 defer ttlcache.Close()
3941 cache.SetTTL(time.Duration(10 * time.Second))
4042 cache.SetExpirationCallback(expirationCallback)
4143
4850 }
4951 ```
5052
53 #### TTLCache - Some design considerations
54
55 1. The complexity of the current cache is already quite high. Therefore i will not add 'convenience' features like an interface to supply a function to get missing keys.
56 2. The locking should be done only in the functions of the Cache struct. Else data races can occur or recursive locks are needed, which are both unwanted.
57 3. I prefer correct functionality over fast tests. It's ok for new tests to take seconds to proof something.
58
5159 #### Original Project
5260
5361 TTLCache was forked from [wunderlist/ttlcache](https://github.com/wunderlist/ttlcache) to add extra functions not avaiable in the original scope.
0 package bench
1
2 import (
3 "testing"
4 "time"
5
6 "github.com/ReneKroon/ttlcache"
7 )
8
9 func BenchmarkCacheSetWithoutTTL(b *testing.B) {
10 cache := ttlcache.NewCache()
11 defer cache.Close()
12
13 for n := 0; n < b.N; n++ {
14 cache.Set(string(n%1000000), "value")
15 }
16 }
17
18 func BenchmarkCacheSetWithGlobalTTL(b *testing.B) {
19 cache := ttlcache.NewCache()
20 defer cache.Close()
21
22 cache.SetTTL(time.Duration(50 * time.Millisecond))
23 for n := 0; n < b.N; n++ {
24 cache.Set(string(n%1000000), "value")
25 }
26 }
27
28 func BenchmarkCacheSetWithTTL(b *testing.B) {
29 cache := ttlcache.NewCache()
30 defer cache.Close()
31
32 for n := 0; n < b.N; n++ {
33 cache.SetWithTTL(string(n%1000000), "value", time.Duration(50*time.Millisecond))
34 }
35 }
2222 expirationNotification chan bool
2323 expirationTime time.Time
2424 skipTTLExtension bool
25 shutdownSignal chan (chan struct{})
26 isShutDown bool
2527 }
2628
2729 func (cache *Cache) getItem(key string) (*item, bool, bool) {
7577
7678 timer.Reset(sleepTime)
7779 select {
80 case shutdownFeedback := <-cache.shutdownSignal:
81 timer.Stop()
82 shutdownFeedback <- struct{}{}
83 return
7884 case <-timer.C:
7985 timer.Stop()
8086 cache.mutex.Lock()
118124 }
119125 }
120126
127 // Close calls Purge, and then stops the goroutine that does ttl checking, for a clean shutdown.
128 // The cache is no longer cleaning up after the first call to Close, repeated calls are safe though.
129 func (cache *Cache) Close() {
130
131 cache.mutex.Lock()
132 if !cache.isShutDown {
133 cache.isShutDown = true
134 cache.mutex.Unlock()
135 feedback := make(chan struct{})
136 cache.shutdownSignal <- feedback
137 <-feedback
138 close(cache.shutdownSignal)
139 } else {
140 cache.mutex.Unlock()
141 }
142 cache.Purge()
143 }
144
121145 // Set is a thread-safe way to add new items to the map
122146 func (cache *Cache) Set(key string, data interface{}) {
123147 cache.SetWithTTL(key, data, ItemExpireWithGlobalTTL)
235259
236260 // NewCache is a helper to create instance of the Cache struct
237261 func NewCache() *Cache {
262
263 shutdownChan := make(chan chan struct{})
264
238265 cache := &Cache{
239266 items: make(map[string]*item),
240267 priorityQueue: newPriorityQueue(),
241268 expirationNotification: make(chan bool),
242269 expirationTime: time.Now(),
270 shutdownSignal: shutdownChan,
271 isShutDown: false,
243272 }
244273 go cache.startExpirationProcessing()
245274 return cache
44 "testing"
55 "time"
66
7 "go.uber.org/goleak"
8
79 "fmt"
8 "log"
910 "sync"
1011
1112 "github.com/stretchr/testify/assert"
1213 )
1314
14 // test for Feature request in issue #12
15 //
16 func TestCache_SkipTtlExtensionOnHit(t *testing.T) {
17 cache := NewCache()
15 func TestMain(m *testing.M) {
16 goleak.VerifyTestMain(m)
17 }
18
19 // Issue #23: Goroutine leak on closing. When adding a close method i would like to see
20 // that it can be called in a repeated way without problems.
21 func TestCache_MultipleCloseCalls(t *testing.T) {
22 cache := NewCache()
23
1824 cache.SetTTL(time.Millisecond * 100)
1925
2026 cache.SkipTtlExtensionOnHit(false)
2733
2834 }
2935
36 cache.Close()
37 cache.Close()
38 cache.Close()
39 cache.Close()
40 }
41
42 // test for Feature request in issue #12
43 //
44 func TestCache_SkipTtlExtensionOnHit(t *testing.T) {
45 cache := NewCache()
46 defer cache.Close()
47
48 cache.SetTTL(time.Millisecond * 100)
49
50 cache.SkipTtlExtensionOnHit(false)
51 cache.Set("test", "!")
52 startTime := time.Now()
53 for now := time.Now(); now.Before(startTime.Add(time.Second * 3)); now = time.Now() {
54 if _, found := cache.Get("test"); !found {
55 t.Errorf("Item was not found, even though it should not expire.")
56 }
57
58 }
59
3060 cache.SkipTtlExtensionOnHit(true)
3161 cache.Set("expireTest", "!")
3262 // will loop if item does not expire
3666
3767 func TestCache_ForRacesAcrossGoroutines(t *testing.T) {
3868 cache := NewCache()
69 defer cache.Close()
70
3971 cache.SetTTL(time.Minute * 1)
4072 cache.SkipTtlExtensionOnHit(false)
4173
5183 go func(i int) {
5284 time.Sleep(time.Nanosecond * time.Duration(rand.Int63n(1000000)))
5385 if i%2 == 0 {
54 cache.Set(fmt.Sprintf("test%d", i /10), false)
86 cache.Set(fmt.Sprintf("test%d", i/10), false)
5587 } else {
56 cache.SetWithTTL(fmt.Sprintf("test%d", i /10), false, time.Second*59)
88 cache.SetWithTTL(fmt.Sprintf("test%d", i/10), false, time.Second*59)
5789 }
5890 wgSet.Done()
5991 }(i)
6799
68100 go func(i int) {
69101 time.Sleep(time.Nanosecond * time.Duration(rand.Int63n(1000000)))
70 cache.Get(fmt.Sprintf("test%d", i /10))
102 cache.Get(fmt.Sprintf("test%d", i/10))
71103 wgGet.Done()
72104 }(i)
73105 }
80112
81113 func TestCache_SkipTtlExtensionOnHit_ForRacesAcrossGoroutines(t *testing.T) {
82114 cache := NewCache()
115 defer cache.Close()
116
83117 cache.SetTTL(time.Minute * 1)
84118 cache.SkipTtlExtensionOnHit(true)
85119
95129 go func(i int) {
96130 time.Sleep(time.Nanosecond * time.Duration(rand.Int63n(1000000)))
97131 if i%2 == 0 {
98 cache.Set(fmt.Sprintf("test%d", i /10), false)
132 cache.Set(fmt.Sprintf("test%d", i/10), false)
99133 } else {
100 cache.SetWithTTL(fmt.Sprintf("test%d", i /10), false, time.Second*59)
134 cache.SetWithTTL(fmt.Sprintf("test%d", i/10), false, time.Second*59)
101135 }
102136 wgSet.Done()
103137 }(i)
111145
112146 go func(i int) {
113147 time.Sleep(time.Nanosecond * time.Duration(rand.Int63n(1000000)))
114 cache.Get(fmt.Sprintf("test%d", i /10))
148 cache.Get(fmt.Sprintf("test%d", i/10))
115149 wgGet.Done()
116150 }(i)
117151 }
129163 ch := make(chan struct{})
130164
131165 cacheAD := NewCache()
166 defer cacheAD.Close()
167
132168 cacheAD.SetTTL(time.Millisecond)
133169 cacheAD.SetCheckExpirationCallback(func(key string, value interface{}) bool {
134170 v := value.(*int)
135 log.Printf("key=%v, value=%d\n", key, *v)
171 t.Logf("key=%v, value=%d\n", key, *v)
136172 iterated++
137173 if iterated == 1 {
138174 // this is the breaking test case for issue #14
160196
161197 // Setup the TTL cache
162198 cache := NewCache()
199 defer cache.Close()
200
163201 cache.SetTTL(time.Second * 1)
164202 cache.SetExpirationCallback(func(key string, value interface{}) {
165 fmt.Printf("This key(%s) has expired\n", key)
203 t.Logf("This key(%s) has expired\n", key)
166204 })
167205 for i := 0; i < 1024; i++ {
168206 cache.Set(fmt.Sprintf("item_%d", i), A{})
169207 time.Sleep(time.Millisecond * 10)
170 fmt.Printf("Cache size: %d\n", cache.Count())
208 t.Logf("Cache size: %d\n", cache.Count())
171209 }
172210
173211 if cache.Count() > 100 {
178216 // test github issue #4
179217 func TestRemovalAndCountDoesNotPanic(t *testing.T) {
180218 cache := NewCache()
219 defer cache.Close()
220
181221 cache.Set("key", "value")
182222 cache.Remove("key")
183223 count := cache.Count()
187227 // test github issue #3
188228 func TestRemovalWithTtlDoesNotPanic(t *testing.T) {
189229 cache := NewCache()
230 defer cache.Close()
231
190232 cache.SetExpirationCallback(func(key string, value interface{}) {
191233 t.Logf("This key(%s) has expired\n", key)
192234 })
216258
217259 func TestCacheIndividualExpirationBiggerThanGlobal(t *testing.T) {
218260 cache := NewCache()
261 defer cache.Close()
262
219263 cache.SetTTL(time.Duration(50 * time.Millisecond))
220264 cache.SetWithTTL("key", "value", time.Duration(100*time.Millisecond))
221265 <-time.After(150 * time.Millisecond)
226270
227271 func TestCacheGlobalExpirationByGlobal(t *testing.T) {
228272 cache := NewCache()
273 defer cache.Close()
274
229275 cache.Set("key", "value")
230276 <-time.After(50 * time.Millisecond)
231277 data, exists := cache.Get("key")
245291
246292 func TestCacheGlobalExpiration(t *testing.T) {
247293 cache := NewCache()
294 defer cache.Close()
295
248296 cache.SetTTL(time.Duration(100 * time.Millisecond))
249297 cache.Set("key_1", "value")
250298 cache.Set("key_2", "value")
255303
256304 func TestCacheMixedExpirations(t *testing.T) {
257305 cache := NewCache()
306 defer cache.Close()
307
258308 cache.SetExpirationCallback(func(key string, value interface{}) {
259309 t.Logf("expired: %s", key)
260310 })
267317
268318 func TestCacheIndividualExpiration(t *testing.T) {
269319 cache := NewCache()
320 defer cache.Close()
321
270322 cache.SetWithTTL("key", "value", time.Duration(100*time.Millisecond))
271323 cache.SetWithTTL("key2", "value", time.Duration(100*time.Millisecond))
272324 cache.SetWithTTL("key3", "value", time.Duration(100*time.Millisecond))
283335
284336 func TestCacheGet(t *testing.T) {
285337 cache := NewCache()
338 defer cache.Close()
339
286340 data, exists := cache.Get("hello")
287341 assert.Equal(t, exists, false, "Expected empty cache to return no data")
288342 assert.Nil(t, data, "Expected data to be empty")
299353 var lock sync.Mutex
300354
301355 cache := NewCache()
356 defer cache.Close()
357
302358 cache.SetTTL(time.Duration(500 * time.Millisecond))
303359 cache.SetExpirationCallback(func(key string, value interface{}) {
304360 lock.Lock()
321377 var lock sync.Mutex
322378
323379 cache := NewCache()
380 defer cache.Close()
381
324382 cache.SkipTtlExtensionOnHit(true)
325383 cache.SetTTL(time.Duration(50 * time.Millisecond))
326384 cache.SetCheckExpirationCallback(func(key string, value interface{}) bool {
348406 func TestCacheNewItemCallbackFunction(t *testing.T) {
349407 newItemCount := 0
350408 cache := NewCache()
409 defer cache.Close()
410
351411 cache.SetTTL(time.Duration(50 * time.Millisecond))
352412 cache.SetNewItemCallback(func(key string, value interface{}) {
353413 newItemCount = newItemCount + 1
361421
362422 func TestCacheRemove(t *testing.T) {
363423 cache := NewCache()
424 defer cache.Close()
425
364426 cache.SetTTL(time.Duration(50 * time.Millisecond))
365427 cache.SetWithTTL("key", "value", time.Duration(100*time.Millisecond))
366428 cache.Set("key_2", "value")
373435
374436 func TestCacheSetWithTTLExistItem(t *testing.T) {
375437 cache := NewCache()
438 defer cache.Close()
439
376440 cache.SetTTL(time.Duration(100 * time.Millisecond))
377441 cache.SetWithTTL("key", "value", time.Duration(50*time.Millisecond))
378442 <-time.After(30 * time.Millisecond)
384448
385449 func TestCache_Purge(t *testing.T) {
386450 cache := NewCache()
451 defer cache.Close()
452
387453 cache.SetTTL(time.Duration(100 * time.Millisecond))
388454
389455 for i := 0; i < 5; i++ {
398464 }
399465
400466 }
401
402 func BenchmarkCacheSetWithoutTTL(b *testing.B) {
403 cache := NewCache()
404 for n := 0; n < b.N; n++ {
405 cache.Set(string(n), "value")
406 }
407 }
408
409 func BenchmarkCacheSetWithGlobalTTL(b *testing.B) {
410 cache := NewCache()
411 cache.SetTTL(time.Duration(50 * time.Millisecond))
412 for n := 0; n < b.N; n++ {
413 cache.Set(string(n), "value")
414 }
415 }
416
417 func BenchmarkCacheSetWithTTL(b *testing.B) {
418 cache := NewCache()
419 for n := 0; n < b.N; n++ {
420 cache.SetWithTTL(string(n), "value", time.Duration(50*time.Millisecond))
421 }
422 }
0 module github.com/ReneKroon/ttlcache
1
2 go 1.12
3
4 require (
5 github.com/davecgh/go-spew v1.1.1 // indirect
6 github.com/stretchr/testify v1.3.0
7 go.uber.org/goleak v0.10.0
8 )
0 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
7 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
8 go.uber.org/goleak v0.10.0 h1:G3eWbSNIskeRqtsN/1uI5B+eP73y3JUuBsv9AZjehb4=
9 go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=