Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context support #65

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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

Expand Down
18 changes: 13 additions & 5 deletions arc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gcache

import (
"container/list"
"context"
"time"
)

Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
90 changes: 55 additions & 35 deletions cache.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gcache

import (
"context"
"errors"
"fmt"
"sync"
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
19 changes: 14 additions & 5 deletions lfu.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gcache

import (
"container/list"
"context"
"time"
)

Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
Loading