From 4a8b6e3e10f65029649c144e9298b2009164a261 Mon Sep 17 00:00:00 2001 From: David Caiazzo Date: Thu, 19 Dec 2019 18:12:32 -0500 Subject: [PATCH] comfortablynull:context-support --- README.md | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ arc.go | 18 ++++++--- cache.go | 90 ++++++++++++++++++++++++++------------------ lfu.go | 19 +++++++--- lru.go | 18 ++++++--- simple.go | 19 +++++++--- 6 files changed, 218 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 6524de5..1b5bb84 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Cache library for golang. It supports expirable Cache, LFU, LRU and ARC. * Automatically load cache if it doesn't exists. (Optional) + ## Install ``` @@ -110,8 +111,46 @@ func main() { fmt.Println("Get:", value) } ``` +``` +Get: ok +``` + +### Automatically load value with context +```go +package main + +import ( + "context" + "fmt" + + "github.com/bluele/gcache" +) + +func main() { + gc := gcache.New(20). + LRU(). + LoaderContextFunc(func(ctx context.Context, key interface{}) (interface{}, error) { + // should print hello + fmt.Println("Ctx Value:", ctx.Value("something")) + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + return "ok", nil + } + }). + Build() + value, err := gc.GetContext(context.WithValue(context.Background(), "something", "hello"), "key") + if err != nil { + panic(err) + } + // should print ok + fmt.Println("Get:", value) +} +``` ``` +Ctx Value: hello Get: ok ``` @@ -170,6 +209,76 @@ Get: ok purged key: key ``` +### Automatically load value with expiration and context + +```go +package main + +import ( + "context" + "fmt" + "time" + + "github.com/bluele/gcache" +) + +func main() { + var evictCounter, loaderCounter, purgeCounter int + gc := gcache.New(20). + LRU(). + LoaderExpireContextFunc(func(ctx context.Context, key interface{}) (interface{}, *time.Duration, error) { + if v := ctx.Value("popular"); v != nil { + // should print lemonade + fmt.Println("popular:", v) + } + loaderCounter++ + expire := 1 * time.Second + return "ok", &expire, nil + }). + EvictedFunc(func(key, value interface{}) { + evictCounter++ + fmt.Println("evicted key:", key) + }). + PurgeVisitorFunc(func(key, value interface{}) { + purgeCounter++ + fmt.Println("purged key:", key) + }). + Build() + value, err := gc.Get("key") + if err != nil { + panic(err) + } + fmt.Println("Get:", value) + time.Sleep(1 * time.Second) + value, err = gc.Get("key") + if err != nil { + panic(err) + } + fmt.Println("Get:", value) + time.Sleep(1 * time.Second) + value, err = gc.GetContext(context.WithValue(context.Background(), "popular", "lemonade"), "key") + if err != nil { + panic(err) + } + fmt.Println("Get:", value) + time.Sleep(1 * time.Second) + gc.Purge() + if loaderCounter != evictCounter+purgeCounter { + panic("bad") + } +} +``` + +``` +Get: ok +evicted key: key +Get: ok +evicted key: key +popular: lemonade +Get: ok +purged key: key +``` + ## Cache Algorithm diff --git a/arc.go b/arc.go index e2015e9..16b5a5c 100644 --- a/arc.go +++ b/arc.go @@ -2,6 +2,7 @@ package gcache import ( "container/list" + "context" "time" ) @@ -164,9 +165,13 @@ func (c *ARC) set(key, value interface{}) (interface{}, error) { // Get a value from cache pool using key if it exists. If not exists and it has LoaderFunc, it will generate the value using you have specified LoaderFunc method returns value. func (c *ARC) Get(key interface{}) (interface{}, error) { + return c.GetContext(context.Background(), key) +} + +func (c *ARC) GetContext(ctx context.Context, key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { - return c.getWithLoader(key, true) + return c.getWithLoader(ctx, key, true) } return v, err } @@ -175,9 +180,12 @@ func (c *ARC) Get(key interface{}) (interface{}, error) { // If it dose not exists key, returns KeyNotFoundError. // And send a request which refresh value for specified key if cache object has LoaderFunc. func (c *ARC) GetIFPresent(key interface{}) (interface{}, error) { + return c.GetIFPresentContext(context.Background(), key) +} +func (c *ARC) GetIFPresentContext(ctx context.Context, key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { - return c.getWithLoader(key, false) + return c.getWithLoader(ctx, key, false) } return v, err } @@ -237,11 +245,11 @@ func (c *ARC) getValue(key interface{}, onLoad bool) (interface{}, error) { return nil, KeyNotFoundError } -func (c *ARC) getWithLoader(key interface{}, isWait bool) (interface{}, error) { - if c.loaderExpireFunc == nil { +func (c *ARC) getWithLoader(ctx context.Context, key interface{}, isWait bool) (interface{}, error) { + if c.loaderExpireContextFunc == nil { return nil, KeyNotFoundError } - value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { + value, _, err := c.load(ctx, key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { if e != nil { return nil, e } diff --git a/cache.go b/cache.go index e13e6f1..553e05f 100644 --- a/cache.go +++ b/cache.go @@ -1,6 +1,7 @@ package gcache import ( + "context" "errors" "fmt" "sync" @@ -28,46 +29,50 @@ type Cache interface { Keys(checkExpired bool) []interface{} Len(checkExpired bool) int Has(key interface{}) bool + GetContext(ctx context.Context, key interface{}) (interface{}, error) + GetIFPresentContext(ctx context.Context, key interface{}) (interface{}, error) statsAccessor } type baseCache struct { - clock Clock - size int - loaderExpireFunc LoaderExpireFunc - evictedFunc EvictedFunc - purgeVisitorFunc PurgeVisitorFunc - addedFunc AddedFunc - deserializeFunc DeserializeFunc - serializeFunc SerializeFunc - expiration *time.Duration - mu sync.RWMutex - loadGroup Group + clock Clock + size int + loaderExpireContextFunc LoaderExpireContextFunc + evictedFunc EvictedFunc + purgeVisitorFunc PurgeVisitorFunc + addedFunc AddedFunc + deserializeFunc DeserializeFunc + serializeFunc SerializeFunc + expiration *time.Duration + mu sync.RWMutex + loadGroup Group *stats } type ( - LoaderFunc func(interface{}) (interface{}, error) - LoaderExpireFunc func(interface{}) (interface{}, *time.Duration, error) - EvictedFunc func(interface{}, interface{}) - PurgeVisitorFunc func(interface{}, interface{}) - AddedFunc func(interface{}, interface{}) - DeserializeFunc func(interface{}, interface{}) (interface{}, error) - SerializeFunc func(interface{}, interface{}) (interface{}, error) + LoaderFunc func(interface{}) (interface{}, error) + LoaderContextFunc func(context.Context, interface{}) (interface{}, error) + LoaderExpireFunc func(interface{}) (interface{}, *time.Duration, error) + LoaderExpireContextFunc func(context.Context, interface{}) (interface{}, *time.Duration, error) + EvictedFunc func(interface{}, interface{}) + PurgeVisitorFunc func(interface{}, interface{}) + AddedFunc func(interface{}, interface{}) + DeserializeFunc func(interface{}, interface{}) (interface{}, error) + SerializeFunc func(interface{}, interface{}) (interface{}, error) ) type CacheBuilder struct { - clock Clock - tp string - size int - loaderExpireFunc LoaderExpireFunc - evictedFunc EvictedFunc - purgeVisitorFunc PurgeVisitorFunc - addedFunc AddedFunc - expiration *time.Duration - deserializeFunc DeserializeFunc - serializeFunc SerializeFunc + clock Clock + tp string + size int + loaderExpireContextFunc LoaderExpireContextFunc + evictedFunc EvictedFunc + purgeVisitorFunc PurgeVisitorFunc + addedFunc AddedFunc + expiration *time.Duration + deserializeFunc DeserializeFunc + serializeFunc SerializeFunc } func New(size int) *CacheBuilder { @@ -86,18 +91,33 @@ func (cb *CacheBuilder) Clock(clock Clock) *CacheBuilder { // Set a loader function. // loaderFunc: create a new value with this function if cached value is expired. func (cb *CacheBuilder) LoaderFunc(loaderFunc LoaderFunc) *CacheBuilder { - cb.loaderExpireFunc = func(k interface{}) (interface{}, *time.Duration, error) { + cb.loaderExpireContextFunc = func(_ context.Context, k interface{}) (interface{}, *time.Duration, error) { v, err := loaderFunc(k) return v, nil, err } return cb } +func (cb *CacheBuilder) LoaderContextFunc(loaderContextFunc LoaderContextFunc) *CacheBuilder { + cb.loaderExpireContextFunc = func(ctx context.Context, k interface{}) (interface{}, *time.Duration, error) { + v, err := loaderContextFunc(ctx, k) + return v, nil, err + } + return cb +} + // Set a loader function with expiration. -// loaderExpireFunc: create a new value with this function if cached value is expired. -// If nil returned instead of time.Duration from loaderExpireFunc than value will never expire. +// loaderExpireContextFunc: create a new value with this function if cached value is expired. +// If nil returned instead of time.Duration from loaderExpireContextFunc than value will never expire. func (cb *CacheBuilder) LoaderExpireFunc(loaderExpireFunc LoaderExpireFunc) *CacheBuilder { - cb.loaderExpireFunc = loaderExpireFunc + cb.loaderExpireContextFunc = func(_ context.Context, i2 interface{}) (i interface{}, duration *time.Duration, err error) { + return loaderExpireFunc(i2) + } + return cb +} + +func (cb *CacheBuilder) LoaderExpireContextFunc(loaderExpireContextFunc LoaderExpireContextFunc) *CacheBuilder { + cb.loaderExpireContextFunc = loaderExpireContextFunc return cb } @@ -178,7 +198,7 @@ func (cb *CacheBuilder) build() Cache { func buildCache(c *baseCache, cb *CacheBuilder) { c.clock = cb.clock c.size = cb.size - c.loaderExpireFunc = cb.loaderExpireFunc + c.loaderExpireContextFunc = cb.loaderExpireContextFunc c.expiration = cb.expiration c.addedFunc = cb.addedFunc c.deserializeFunc = cb.deserializeFunc @@ -189,14 +209,14 @@ func buildCache(c *baseCache, cb *CacheBuilder) { } // load a new value using by specified key. -func (c *baseCache) load(key interface{}, cb func(interface{}, *time.Duration, error) (interface{}, error), isWait bool) (interface{}, bool, error) { +func (c *baseCache) load(ctx context.Context, key interface{}, cb func(interface{}, *time.Duration, error) (interface{}, error), isWait bool) (interface{}, bool, error) { v, called, err := c.loadGroup.Do(key, func() (v interface{}, e error) { defer func() { if r := recover(); r != nil { e = fmt.Errorf("Loader panics: %v", r) } }() - return cb(c.loaderExpireFunc(key)) + return cb(c.loaderExpireContextFunc(ctx, key)) }, isWait) if err != nil { return nil, called, err diff --git a/lfu.go b/lfu.go index f781a1f..72633c3 100644 --- a/lfu.go +++ b/lfu.go @@ -2,6 +2,7 @@ package gcache import ( "container/list" + "context" "time" ) @@ -100,9 +101,13 @@ func (c *LFUCache) set(key, value interface{}) (interface{}, error) { // If it dose not exists key and has LoaderFunc, // generate a value using `LoaderFunc` method returns value. func (c *LFUCache) Get(key interface{}) (interface{}, error) { + return c.GetContext(context.Background(), key) +} + +func (c *LFUCache) GetContext(ctx context.Context, key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { - return c.getWithLoader(key, true) + return c.getWithLoader(ctx, key, true) } return v, err } @@ -111,9 +116,13 @@ func (c *LFUCache) Get(key interface{}) (interface{}, error) { // If it dose not exists key, returns KeyNotFoundError. // And send a request which refresh value for specified key if cache object has LoaderFunc. func (c *LFUCache) GetIFPresent(key interface{}) (interface{}, error) { + return c.GetIFPresentContext(context.Background(), key) +} + +func (c *LFUCache) GetIFPresentContext(ctx context.Context, key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { - return c.getWithLoader(key, false) + return c.getWithLoader(ctx, key, false) } return v, err } @@ -151,11 +160,11 @@ func (c *LFUCache) getValue(key interface{}, onLoad bool) (interface{}, error) { return nil, KeyNotFoundError } -func (c *LFUCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) { - if c.loaderExpireFunc == nil { +func (c *LFUCache) getWithLoader(ctx context.Context, key interface{}, isWait bool) (interface{}, error) { + if c.loaderExpireContextFunc == nil { return nil, KeyNotFoundError } - value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { + value, _, err := c.load(ctx, key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { if e != nil { return nil, e } diff --git a/lru.go b/lru.go index a85d660..70bcd7c 100644 --- a/lru.go +++ b/lru.go @@ -2,6 +2,7 @@ package gcache import ( "container/list" + "context" "time" ) @@ -92,9 +93,12 @@ func (c *LRUCache) SetWithExpire(key, value interface{}, expiration time.Duratio // If it dose not exists key and has LoaderFunc, // generate a value using `LoaderFunc` method returns value. func (c *LRUCache) Get(key interface{}) (interface{}, error) { + return c.GetContext(context.Background(), key) +} +func (c *LRUCache) GetContext(ctx context.Context, key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { - return c.getWithLoader(key, true) + return c.getWithLoader(ctx, key, true) } return v, err } @@ -103,9 +107,13 @@ func (c *LRUCache) Get(key interface{}) (interface{}, error) { // If it dose not exists key, returns KeyNotFoundError. // And send a request which refresh value for specified key if cache object has LoaderFunc. func (c *LRUCache) GetIFPresent(key interface{}) (interface{}, error) { + return c.GetIFPresentContext(context.Background(), key) +} + +func (c *LRUCache) GetIFPresentContext(ctx context.Context, key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { - return c.getWithLoader(key, false) + return c.getWithLoader(ctx, key, false) } return v, err } @@ -144,11 +152,11 @@ func (c *LRUCache) getValue(key interface{}, onLoad bool) (interface{}, error) { return nil, KeyNotFoundError } -func (c *LRUCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) { - if c.loaderExpireFunc == nil { +func (c *LRUCache) getWithLoader(ctx context.Context, key interface{}, isWait bool) (interface{}, error) { + if c.loaderExpireContextFunc == nil { return nil, KeyNotFoundError } - value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { + value, _, err := c.load(ctx, key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { if e != nil { return nil, e } diff --git a/simple.go b/simple.go index 7310af1..5f54c5b 100644 --- a/simple.go +++ b/simple.go @@ -1,6 +1,7 @@ package gcache import ( + "context" "time" ) @@ -90,9 +91,13 @@ func (c *SimpleCache) set(key, value interface{}) (interface{}, error) { // If it dose not exists key and has LoaderFunc, // generate a value using `LoaderFunc` method returns value. func (c *SimpleCache) Get(key interface{}) (interface{}, error) { + return c.GetContext(context.Background(), key) +} + +func (c *SimpleCache) GetContext(ctx context.Context, key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { - return c.getWithLoader(key, true) + return c.getWithLoader(ctx, key, true) } return v, err } @@ -101,9 +106,13 @@ func (c *SimpleCache) Get(key interface{}) (interface{}, error) { // If it dose not exists key, returns KeyNotFoundError. // And send a request which refresh value for specified key if cache object has LoaderFunc. func (c *SimpleCache) GetIFPresent(key interface{}) (interface{}, error) { + return c.GetIFPresentContext(context.Background(), key) +} + +func (c *SimpleCache) GetIFPresentContext(ctx context.Context, key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { - return c.getWithLoader(key, false) + return c.getWithLoader(ctx, key, false) } return v, nil } @@ -140,11 +149,11 @@ func (c *SimpleCache) getValue(key interface{}, onLoad bool) (interface{}, error return nil, KeyNotFoundError } -func (c *SimpleCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) { - if c.loaderExpireFunc == nil { +func (c *SimpleCache) getWithLoader(ctx context.Context, key interface{}, isWait bool) (interface{}, error) { + if c.loaderExpireContextFunc == nil { return nil, KeyNotFoundError } - value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { + value, _, err := c.load(ctx, key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { if e != nil { return nil, e }