Package list golang-github-renekroon-ttlcache / upstream/2.1.0+ds
New upstream version 2.1.0+ds Sascha Steinbiss 11 months ago
8 changed file(s) with 147 addition(s) and 47 deletion(s). Raw diff Collapse all Expand all
00 language: go
11
22 go:
3 - "1.14"
4 - "1.13"
3 - "1.15.x"
4 - "1.14.x"
5
56 git:
67 depth: 1
78
1011 - go install golang.org/x/tools/cmd/cover
1112 - go install golang.org/x/lint/golint
1213 - export PATH=$HOME/gopath/bin:$PATH
14 - go get golang.org/x/tools/cmd/cover
15 - go get github.com/mattn/goveralls
1316
1417 script:
1518 - golint .
1619 - go test -cover -race -count=1 -timeout=30s -run .
17 - cd bench; go test -run=Bench.* -bench=. -benchmem
20 - go test -covermode=count -coverprofile=coverage.out -timeout=90s -run .
21 - '[ ! -z "$COVERALLS_TOKEN" ] && $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN'
22 - cd bench; go test -run=Bench.* -bench=. -benchmem; cd ..
0 # 2.1.0 (October 2020)
1
2 ## API changes
3
4 * `SetCacheSizeLimit(limit int)` a call was contributed to set a cache limit. #35
5
06 # 2.0.0 (July 2020)
17
28 ## Fixes #29, #30, #31
00 # TTLCache - an in-memory cache with expiration
1
2 [![Documentation](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/ReneKroon/ttlcache/v2)
3 [![Release](https://img.shields.io/github/release/ReneKroon/ttlcache.svg?label=Release)](https://github.com/ReneKroon/ttlcache/releases)
14
25 TTLCache is a simple key/value cache in golang with the following functions:
36
47 1. Expiration of items based on time, or custom function
5 2. Loader function to retrieve missing keys can be provided. Additional `Get` calls on the same key block while fetching is in progress (groupcache style).
8 2. Loader function to retrieve missing keys can be provided. Additional `Get` calls on the same key block while fetching is in progress (groupcache style).
69 3. Individual expiring time or global expiring time, you can choose
710 4. Auto-Extending expiration on `Get` -or- DNS style TTL, see `SkipTTLExtensionOnHit(bool)`
811 5. Can trigger callback on key expiration
1215 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.
1316
1417 [![Build Status](https://travis-ci.org/ReneKroon/ttlcache.svg?branch=master)](https://travis-ci.org/ReneKroon/ttlcache)
18 [![Go Report Card](https://goreportcard.com/badge/github.com/ReneKroon/ttlcache)](https://goreportcard.com/report/github.com/ReneKroon/ttlcache)
19 [![Coverage Status](https://coveralls.io/repos/github/ReneKroon/ttlcache/badge.svg?branch=master)](https://coveralls.io/github/ReneKroon/ttlcache?branch=master)
20 [![GitHub issues](https://img.shields.io/github/issues/ReneKroon/ttlcache.svg)](https://github.com/ReneKroon/ttlcache/issues)
21 [![license](https://img.shields.io/github/license/ReneKroon/ttlcache.svg?maxAge=2592000)](https://github.com/ReneKroon/ttlcache/LICENSE)
1522
1623 ## Usage
1724
25 You can copy it as a full standalone demo program.
26
1827 ```go
28 package main
29
1930 import (
20 "time"
21 "fmt"
31 "fmt"
32 "time"
2233
23 "github.com/ReneKroon/ttlcache"
34 "github.com/ReneKroon/ttlcache/v2"
2435 )
2536
26 func main () {
27 newItemCallback := func(key string, value interface{}) {
28 fmt.Printf("New key(%s) added\n", key)
29 }
30 checkExpirationCallback := func(key string, value interface{}) bool {
31 if key == "key1" {
32 // if the key equals "key1", the value
33 // will not be allowed to expire
34 return false
35 }
36 // all other values are allowed to expire
37 return true
38 }
39 expirationCallback := func(key string, value interface{}) {
40 fmt.Printf("This key(%s) has expired\n", key)
41 }
37 var (
38 notFound = ttlcache.ErrNotFound
39 isClosed = ttlcache.ErrClosed
40 )
4241
43 loaderFunction := func(key string) (data interface{}, ttl time.Duration, err error) {
44 ttl = time.Second * 300
45 data, err = getFromNetwork(key)
42 func main() {
43 newItemCallback := func(key string, value interface{}) {
44 fmt.Printf("New key(%s) added\n", key)
45 }
46 checkExpirationCallback := func(key string, value interface{}) bool {
47 if key == "key1" {
48 // if the key equals "key1", the value
49 // will not be allowed to expire
50 return false
51 }
52 // all other values are allowed to expire
53 return true
54 }
55 expirationCallback := func(key string, value interface{}) {
56 fmt.Printf("This key(%s) has expired\n", key)
57 }
4658
47 return data, ttl, err
48 }
59 loaderFunction := func(key string) (data interface{}, ttl time.Duration, err error) {
60 ttl = time.Second * 300
61 data, err = getFromNetwork(key)
4962
50 cache := ttlcache.NewCache()
51 defer cache.Close()
52 cache.SetTTL(time.Duration(10 * time.Second))
53 cache.SetExpirationCallback(expirationCallback)
54 cache.SetLoaderFunction(loaderFunction)
63 return data, ttl, err
64 }
5565
56 cache.Set("key", "value")
57 cache.SetWithTTL("keyWithTTL", "value", 10 * time.Second)
66 cache := ttlcache.NewCache()
67 defer cache.Close()
68 cache.SetTTL(time.Duration(10 * time.Second))
69 cache.SetExpirationCallback(expirationCallback)
70 cache.SetLoaderFunction(loaderFunction)
71 cache.SetNewItemCallback(newItemCallback)
72 cache.SetCheckExpirationCallback(checkExpirationCallback)
5873
59 value, exists := cache.Get("key")
60 count := cache.Count()
61 result := cache.Remove("key")
74 cache.Set("key", "value")
75 cache.SetWithTTL("keyWithTTL", "value", 10*time.Second)
76
77 if value, exists := cache.Get("key"); exists == nil {
78 fmt.Printf("Got value: %v\n", value)
79 }
80 count := cache.Count()
81 if result := cache.Remove("keyNNN"); result == notFound {
82 fmt.Printf("Not found, %d items left\n", count)
83 }
84 }
85
86 func getFromNetwork(key string) (string, error) {
87 time.Sleep(time.Millisecond * 30)
88 return "value", nil
6289 }
6390 ```
6491
78105 3. The expiration can be either global or per item
79106 4. Items can exist without expiration time (time.Zero)
80107 5. Expirations and callbacks are realtime. Don't have a pooling time to check anymore, now it's done with a heap.
108 6. A cache count limiter
3030 shutdownSignal chan (chan struct{})
3131 isShutDown bool
3232 loaderFunction LoaderFunction
33 sizeLimit int
3334 }
3435
3536 var (
165166 // Close calls Purge after stopping the goroutine that does ttl checking, for a clean shutdown.
166167 // The cache is no longer cleaning up after the first call to Close, repeated calls are safe and return ErrClosed.
167168 func (cache *Cache) Close() error {
168
169169 cache.mutex.Lock()
170170 if !cache.isShutDown {
171171 cache.isShutDown = true
174174 cache.shutdownSignal <- feedback
175175 <-feedback
176176 close(cache.shutdownSignal)
177 cache.Purge()
177178 } else {
178179 cache.mutex.Unlock()
179180 return ErrClosed
180181 }
181 cache.Purge()
182182 return nil
183183 }
184184
200200 item.data = data
201201 item.ttl = ttl
202202 } else {
203 if cache.sizeLimit != 0 && len(cache.items) >= cache.sizeLimit {
204 cache.removeItem(cache.priorityQueue.items[0])
205 }
203206 item = newItem(key, data, ttl)
204207 cache.items[key] = item
205208 }
240243 dataToReturn = item.data
241244 }
242245
243 var err error = nil
246 var err error
244247 if !exists {
245248 err = ErrNotFound
246249 }
380383 cache.items = make(map[string]*item)
381384 cache.priorityQueue = newPriorityQueue()
382385 return nil
386 }
387
388 // SetCacheSizeLimit sets a limit to the amount of cached items.
389 // If a new item is getting cached, the closes item to being timed out will be replaced
390 // Set to 0 to turn off
391 func (cache *Cache) SetCacheSizeLimit(limit int) {
392 cache.sizeLimit = limit
383393 }
384394
385395 // NewCache is a helper to create instance of the Cache struct
396406 shutdownSignal: shutdownChan,
397407 isShutDown: false,
398408 loaderFunction: nil,
409 sizeLimit: 0,
399410 }
400411 go cache.startExpirationProcessing()
401412 return cache
00 package ttlcache_test
11
22 import (
3 "go.uber.org/goleak"
34 "math/rand"
5 "strconv"
46 "sync/atomic"
57 "testing"
68 "time"
7
8 "go.uber.org/goleak"
99
1010 "fmt"
1111 "sync"
366366 cache := NewCache()
367367 defer cache.Close()
368368
369 ch := make(chan struct{}, 1024)
369370 cache.SetTTL(time.Second * 1)
370371 cache.SetExpirationCallback(func(key string, value interface{}) {
371372 t.Logf("This key(%s) has expired\n", key)
373 ch <- struct{}{}
372374 })
373375 for i := 0; i < 1024; i++ {
374376 cache.Set(fmt.Sprintf("item_%d", i), A{})
378380
379381 if cache.Count() > 100 {
380382 t.Fatal("Cache should empty entries >1 second old")
383 }
384
385 expired := 0
386 for expired != 1024 {
387 <-ch
388 expired++
381389 }
382390 }
383391
660668 }
661669
662670 }
671
672 func TestCache_Limit(t *testing.T) {
673 t.Parallel()
674
675 cache := NewCache()
676 defer cache.Close()
677
678 cache.SetTTL(time.Duration(100 * time.Second))
679 cache.SetCacheSizeLimit(10)
680
681 for i := 0; i < 100; i++ {
682 cache.Set("key"+strconv.FormatInt(int64(i), 10), "value")
683 }
684 assert.Equal(t, 10, cache.Count(), "Cache should equal to limit")
685 for i := 90; i < 100; i++ {
686 key := "key" + strconv.FormatInt(int64(i), 10)
687 val, _ := cache.Get(key)
688 assert.Equal(t, "value", val, "Cache should be set [key90, key99]")
689 }
690 }
55 github.com/davecgh/go-spew v1.1.1 // indirect
66 github.com/stretchr/testify v1.6.1
77 go.uber.org/goleak v1.1.10
8 golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
9 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d // indirect
810 )
1313 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
1414 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
1515 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
16 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
1617 go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
1718 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
1819 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
20 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
21 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
1922 golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
2023 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
24 golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
25 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
26 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
27 golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
28 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
2129 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
30 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
2231 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
32 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
2333 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
34 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
2435 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
36 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
37 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
2538 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
2639 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
2740 golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E=
2841 golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
42 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
43 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
44 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d h1:szSOL78iTCl0LF1AMjhSWJj8tIM0KixlUUnBtYXsmd8=
45 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
2946 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
47 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
48 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
49 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
3050 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3151 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
3252 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
6868 if item == nil {
6969 break
7070 }
71 assert.NotEqual(t, itemRemove.key, item.key, "This element was not supose to be in the queue")
71 assert.NotEqual(t, itemRemove.key, item.key, "This element was not supposed to be in the queue")
7272 }
7373
74 assert.Equal(t, queue.Len(), 0, "The queue is supose to be with 0 items")
74 assert.Equal(t, queue.Len(), 0, "The queue is supposed to be with 0 items")
7575 }
7676
7777 func TestPriorityQueueUpdate(t *testing.T) {
7878 queue := newPriorityQueue()
7979 item := newItem("key", "data", 1*time.Second)
8080 queue.push(item)
81 assert.Equal(t, queue.Len(), 1, "The queue is supose to be with 1 item")
81 assert.Equal(t, queue.Len(), 1, "The queue is supposed to be with 1 item")
8282
8383 item.key = "newKey"
8484 queue.update(item)
8585 newItem := queue.pop()
8686 assert.Equal(t, newItem.key, "newKey", "The item key didn't change")
87 assert.Equal(t, queue.Len(), 0, "The queue is supose to be with 0 items")
87 assert.Equal(t, queue.Len(), 0, "The queue is supposed to be with 0 items")
8888 }