New upstream version 2.5.0+ds
Sascha Steinbiss
2 years ago
0 | # 2.4.0 (May 2021) | |
1 | ||
2 | ## API changes: | |
3 | ||
4 | * #42 : Add option to get list of keys | |
5 | * #40: Allow 'Touch' on items without other operation | |
6 | ||
7 | // Touch resets the TTL of the key when it exists, returns ErrNotFound if the key is not present. | |
8 | func (cache *Cache) Touch(key string) error | |
9 | ||
10 | // GetKeys returns all keys of items in the cache. Returns nil when the cache has been closed. | |
11 | func (cache *Cache) GetKeys() []string | |
12 | ||
0 | 13 | # 2.3.0 (February 2021) |
1 | 14 | |
2 | 15 | ## API changes: |
22 | 22 | |
23 | 23 | ## Usage |
24 | 24 | |
25 | You can copy it as a full standalone demo program. | |
25 | 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. | |
26 | 26 | |
27 | Basic: | |
28 | ```go | |
29 | package main | |
30 | ||
31 | import ( | |
32 | "fmt" | |
33 | "time" | |
34 | ||
35 | "github.com/ReneKroon/ttlcache/v2" | |
36 | ) | |
37 | ||
38 | var notFound = ttlcache.ErrNotFound | |
39 | ||
40 | func main() { | |
41 | var cache ttlcache.SimpleCache = ttlcache.NewCache() | |
42 | ||
43 | cache.SetTTL(time.Duration(10 * time.Second)) | |
44 | cache.Set("MyKey", "MyValue") | |
45 | cache.Set("MyNumber", 1000) | |
46 | ||
47 | if val, err := cache.Get("MyKey"); err != notFound { | |
48 | fmt.Printf("Got it: %s\n", val) | |
49 | } | |
50 | ||
51 | cache.Remove("MyNumber") | |
52 | cache.Purge() | |
53 | cache.Close() | |
54 | } | |
55 | ``` | |
56 | ||
57 | Advanced: | |
27 | 58 | ```go |
28 | 59 | package main |
29 | 60 |
16 | 16 | |
17 | 17 | // LoaderFunction can be supplied to retrieve an item where a cache miss occurs. Supply an item specific ttl or Duration.Zero |
18 | 18 | type LoaderFunction func(key string) (data interface{}, ttl time.Duration, err error) |
19 | ||
20 | // SimpleCache interface enables a quick-start. Interface for basic usage. | |
21 | type SimpleCache interface { | |
22 | Get(key string) (interface{}, error) | |
23 | Set(key string, data interface{}) error | |
24 | SetTTL(ttl time.Duration) error | |
25 | SetWithTTL(key string, data interface{}, ttl time.Duration) error | |
26 | Remove(key string) error | |
27 | Close() error | |
28 | Purge() error | |
29 | } | |
19 | 30 | |
20 | 31 | // Cache is a synchronized map of items that can auto-expire once stale |
21 | 32 | type Cache struct { |
265 | 276 | // Get is a thread-safe way to lookup items |
266 | 277 | // Every lookup, also touches the item, hence extending it's life |
267 | 278 | func (cache *Cache) Get(key string) (interface{}, error) { |
279 | return cache.GetByLoader(key, nil) | |
280 | } | |
281 | ||
282 | // GetByLoader can take a per key loader function (ie. to propagate context) | |
283 | func (cache *Cache) GetByLoader(key string, customLoaderFunction LoaderFunction) (interface{}, error) { | |
268 | 284 | cache.mutex.Lock() |
269 | 285 | if cache.isShutDown { |
270 | 286 | cache.mutex.Unlock() |
285 | 301 | cache.metrics.Misses++ |
286 | 302 | err = ErrNotFound |
287 | 303 | } |
288 | if cache.loaderFunction == nil || exists { | |
289 | cache.mutex.Unlock() | |
290 | } | |
291 | ||
292 | if cache.loaderFunction != nil && !exists { | |
304 | ||
305 | loaderFunction := cache.loaderFunction | |
306 | if customLoaderFunction != nil { | |
307 | loaderFunction = customLoaderFunction | |
308 | } | |
309 | ||
310 | if loaderFunction == nil || exists { | |
311 | cache.mutex.Unlock() | |
312 | } | |
313 | ||
314 | if loaderFunction != nil && !exists { | |
293 | 315 | if lock, ok := cache.loaderLock[key]; ok { |
294 | 316 | // if a lock is present then a fetch is in progress and we wait. |
295 | 317 | cache.mutex.Unlock() |
309 | 331 | cache.loaderLock[key] = m |
310 | 332 | cache.mutex.Unlock() |
311 | 333 | // cache is not blocked during IO |
312 | dataToReturn, err = cache.invokeLoader(key) | |
334 | dataToReturn, err = cache.invokeLoader(key, loaderFunction) | |
313 | 335 | cache.mutex.Lock() |
314 | 336 | m.Broadcast() |
315 | 337 | // cleanup so that we don't block consecutive access. |
326 | 348 | return dataToReturn, err |
327 | 349 | } |
328 | 350 | |
329 | func (cache *Cache) invokeLoader(key string) (dataToReturn interface{}, err error) { | |
351 | func (cache *Cache) invokeLoader(key string, loaderFunction LoaderFunction) (dataToReturn interface{}, err error) { | |
330 | 352 | var ttl time.Duration |
331 | 353 | |
332 | dataToReturn, ttl, err = cache.loaderFunction(key) | |
354 | dataToReturn, ttl, err = loaderFunction(key) | |
333 | 355 | if err == nil { |
334 | 356 | err = cache.SetWithTTL(key, dataToReturn, ttl) |
335 | 357 | if err != nil { |
17 | 17 | |
18 | 18 | func TestMain(m *testing.M) { |
19 | 19 | goleak.VerifyTestMain(m) |
20 | } | |
21 | ||
22 | // The SimpleCache interface enables quick-start. | |
23 | func TestCache_SimpleCache(t *testing.T) { | |
24 | t.Parallel() | |
25 | var cache SimpleCache = NewCache() | |
26 | ||
27 | cache.SetTTL(time.Second) | |
28 | cache.Set("k", "v") | |
29 | cache.Get("k") | |
30 | cache.Purge() | |
31 | cache.Close() | |
32 | ||
33 | } | |
34 | ||
35 | // Issue / PR #39: add customer loader function for each Get() # | |
36 | // some middleware prefers to define specific context's etc per Get. | |
37 | // This is faciliated by supplying a loder function with Get's. | |
38 | func TestCache_GetByLoader(t *testing.T) { | |
39 | t.Parallel() | |
40 | cache := NewCache() | |
41 | defer cache.Close() | |
42 | ||
43 | globalLoader := func(key string) (data interface{}, ttl time.Duration, err error) { | |
44 | return "global", 0, nil | |
45 | } | |
46 | cache.SetLoaderFunction(globalLoader) | |
47 | ||
48 | localLoader := func(key string) (data interface{}, ttl time.Duration, err error) { | |
49 | return "local", 0, nil | |
50 | } | |
51 | ||
52 | key, _ := cache.Get("test") | |
53 | assert.Equal(t, "global", key) | |
54 | ||
55 | cache.Remove("test") | |
56 | ||
57 | localKey, _ := cache.GetByLoader("test", localLoader) | |
58 | assert.Equal(t, "local", localKey) | |
59 | ||
60 | cache.Remove("test") | |
61 | ||
62 | globalKey, _ := cache.GetByLoader("test", globalLoader) | |
63 | assert.Equal(t, "global", globalKey) | |
64 | ||
65 | cache.Remove("test") | |
66 | ||
67 | defaultKey, _ := cache.GetByLoader("test", nil) | |
68 | assert.Equal(t, "global", defaultKey) | |
69 | ||
70 | cache.Remove("test") | |
20 | 71 | } |
21 | 72 | |
22 | 73 | // Issue #38: Feature request: ability to know why an expiry has occurred |