New upstream version 1.5.0
Sascha Steinbiss
4 years ago
9 | 9 | - go install -race std |
10 | 10 | - go get golang.org/x/tools/cmd/cover |
11 | 11 | - go get golang.org/x/lint/golint |
12 | - go get github.com/tools/godep | |
13 | 12 | - export PATH=$HOME/gopath/bin:$PATH |
14 | - godep restore | |
15 | 13 | |
16 | 14 | script: |
17 | 15 | - golint . |
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 | # 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 |
12 | 12 | |
13 | 13 | // Cache is a synchronized map of items that can auto-expire once stale |
14 | 14 | type Cache struct { |
15 | mutex sync.RWMutex | |
15 | mutex sync.Mutex | |
16 | 16 | ttl time.Duration |
17 | 17 | items map[string]*item |
18 | 18 | expireCallback expireCallback |
24 | 24 | skipTTLExtension bool |
25 | 25 | } |
26 | 26 | |
27 | func (cache *Cache) getItem(key string) (*item, bool) { | |
28 | cache.mutex.RLock() | |
29 | ||
27 | func (cache *Cache) getItem(key string) (*item, bool, bool) { | |
30 | 28 | item, exists := cache.items[key] |
31 | 29 | if !exists || item.expired() { |
32 | cache.mutex.RUnlock() | |
33 | return nil, false | |
30 | return nil, false, false | |
34 | 31 | } |
35 | 32 | |
36 | 33 | if item.ttl >= 0 && (item.ttl > 0 || cache.ttl > 0) { |
44 | 41 | cache.priorityQueue.update(item) |
45 | 42 | } |
46 | 43 | |
47 | cache.mutex.RUnlock() | |
48 | cache.expirationNotificationTrigger(item) | |
49 | return item, exists | |
44 | expirationNotification := false | |
45 | if cache.expirationTime.After(time.Now().Add(item.ttl)) { | |
46 | expirationNotification = true | |
47 | } | |
48 | return item, exists, expirationNotification | |
50 | 49 | } |
51 | 50 | |
52 | 51 | func (cache *Cache) startExpirationProcessing() { |
52 | timer := time.NewTimer(time.Hour) | |
53 | 53 | for { |
54 | 54 | var sleepTime time.Duration |
55 | 55 | cache.mutex.Lock() |
73 | 73 | cache.expirationTime = time.Now().Add(sleepTime) |
74 | 74 | cache.mutex.Unlock() |
75 | 75 | |
76 | timer := time.NewTimer(sleepTime) | |
76 | timer.Reset(sleepTime) | |
77 | 77 | select { |
78 | 78 | case <-timer.C: |
79 | 79 | timer.Stop() |
118 | 118 | } |
119 | 119 | } |
120 | 120 | |
121 | func (cache *Cache) expirationNotificationTrigger(item *item) { | |
122 | cache.mutex.Lock() | |
123 | if cache.expirationTime.After(time.Now().Add(item.ttl)) { | |
124 | cache.mutex.Unlock() | |
125 | cache.expirationNotification <- true | |
126 | } else { | |
127 | cache.mutex.Unlock() | |
128 | } | |
129 | } | |
130 | ||
131 | 121 | // Set is a thread-safe way to add new items to the map |
132 | 122 | func (cache *Cache) Set(key string, data interface{}) { |
133 | 123 | cache.SetWithTTL(key, data, ItemExpireWithGlobalTTL) |
135 | 125 | |
136 | 126 | // SetWithTTL is a thread-safe way to add new items to the map with individual ttl |
137 | 127 | func (cache *Cache) SetWithTTL(key string, data interface{}, ttl time.Duration) { |
138 | item, exists := cache.getItem(key) | |
139 | cache.mutex.Lock() | |
128 | cache.mutex.Lock() | |
129 | item, exists, _ := cache.getItem(key) | |
140 | 130 | |
141 | 131 | if exists { |
142 | 132 | item.data = data |
169 | 159 | // Get is a thread-safe way to lookup items |
170 | 160 | // Every lookup, also touches the item, hence extending it's life |
171 | 161 | func (cache *Cache) Get(key string) (interface{}, bool) { |
172 | item, exists := cache.getItem(key) | |
162 | cache.mutex.Lock() | |
163 | item, exists, triggerExpirationNotification := cache.getItem(key) | |
164 | ||
165 | var dataToReturn interface{} | |
173 | 166 | if exists { |
174 | cache.mutex.RLock() | |
175 | defer cache.mutex.RUnlock() | |
176 | return item.data, true | |
177 | } | |
178 | return nil, false | |
167 | dataToReturn = item.data | |
168 | } | |
169 | cache.mutex.Unlock() | |
170 | if triggerExpirationNotification { | |
171 | cache.expirationNotification <- true | |
172 | } | |
173 | return dataToReturn, exists | |
179 | 174 | } |
180 | 175 | |
181 | 176 | func (cache *Cache) Remove(key string) bool { |
194 | 189 | |
195 | 190 | // Count returns the number of items in the cache |
196 | 191 | func (cache *Cache) Count() int { |
197 | cache.mutex.RLock() | |
192 | cache.mutex.Lock() | |
198 | 193 | length := len(cache.items) |
199 | cache.mutex.RUnlock() | |
194 | cache.mutex.Unlock() | |
200 | 195 | return length |
201 | 196 | } |
202 | 197 |
0 | 0 | package ttlcache |
1 | 1 | |
2 | 2 | import ( |
3 | "math/rand" | |
3 | 4 | "testing" |
4 | 5 | "time" |
5 | 6 | |
33 | 34 | } |
34 | 35 | } |
35 | 36 | |
36 | func TestCache_SkipTtlExtensionOnHit_ForRacesAcrossGoroutines(t *testing.T) { | |
37 | func TestCache_ForRacesAcrossGoroutines(t *testing.T) { | |
37 | 38 | cache := NewCache() |
38 | 39 | cache.SetTTL(time.Minute * 1) |
39 | cache.SkipTtlExtensionOnHit(true) | |
40 | cache.SkipTtlExtensionOnHit(false) | |
40 | 41 | |
41 | 42 | var wgSet sync.WaitGroup |
42 | 43 | var wgGet sync.WaitGroup |
47 | 48 | for i := 0; i < n; i++ { |
48 | 49 | wgSet.Add(1) |
49 | 50 | |
50 | go func() { | |
51 | cache.Set("test", false) | |
51 | go func(i int) { | |
52 | time.Sleep(time.Nanosecond * time.Duration(rand.Int63n(1000000))) | |
53 | if i%2 == 0 { | |
54 | cache.Set(fmt.Sprintf("test%d", i /10), false) | |
55 | } else { | |
56 | cache.SetWithTTL(fmt.Sprintf("test%d", i /10), false, time.Second*59) | |
57 | } | |
52 | 58 | wgSet.Done() |
53 | }() | |
59 | }(i) | |
54 | 60 | } |
55 | 61 | wgSet.Done() |
56 | 62 | }() |
59 | 65 | for i := 0; i < n; i++ { |
60 | 66 | wgGet.Add(1) |
61 | 67 | |
62 | go func() { | |
63 | cache.Get("test") | |
68 | go func(i int) { | |
69 | time.Sleep(time.Nanosecond * time.Duration(rand.Int63n(1000000))) | |
70 | cache.Get(fmt.Sprintf("test%d", i /10)) | |
64 | 71 | wgGet.Done() |
65 | }() | |
72 | }(i) | |
73 | } | |
74 | wgGet.Done() | |
75 | }() | |
76 | ||
77 | wgGet.Wait() | |
78 | wgSet.Wait() | |
79 | } | |
80 | ||
81 | func TestCache_SkipTtlExtensionOnHit_ForRacesAcrossGoroutines(t *testing.T) { | |
82 | cache := NewCache() | |
83 | cache.SetTTL(time.Minute * 1) | |
84 | cache.SkipTtlExtensionOnHit(true) | |
85 | ||
86 | var wgSet sync.WaitGroup | |
87 | var wgGet sync.WaitGroup | |
88 | ||
89 | n := 500 | |
90 | wgSet.Add(1) | |
91 | go func() { | |
92 | for i := 0; i < n; i++ { | |
93 | wgSet.Add(1) | |
94 | ||
95 | go func(i int) { | |
96 | time.Sleep(time.Nanosecond * time.Duration(rand.Int63n(1000000))) | |
97 | if i%2 == 0 { | |
98 | cache.Set(fmt.Sprintf("test%d", i /10), false) | |
99 | } else { | |
100 | cache.SetWithTTL(fmt.Sprintf("test%d", i /10), false, time.Second*59) | |
101 | } | |
102 | wgSet.Done() | |
103 | }(i) | |
104 | } | |
105 | wgSet.Done() | |
106 | }() | |
107 | wgGet.Add(1) | |
108 | go func() { | |
109 | for i := 0; i < n; i++ { | |
110 | wgGet.Add(1) | |
111 | ||
112 | go func(i int) { | |
113 | time.Sleep(time.Nanosecond * time.Duration(rand.Int63n(1000000))) | |
114 | cache.Get(fmt.Sprintf("test%d", i /10)) | |
115 | wgGet.Done() | |
116 | }(i) | |
66 | 117 | } |
67 | 118 | wgGet.Done() |
68 | 119 | }() |