-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
147 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,25 @@ | ||
# cache-loader | ||
Golang Cache Loader | ||
|
||
## Feature | ||
* Thread safe | ||
* Fetch once even when concurrent process request same key. | ||
* stale-while-revalidate when item is expired | ||
|
||
## Example | ||
|
||
```go | ||
func main() { | ||
itemLoader := loader.NewLRU(fetchItem, 5 * time.Minute, 1000) | ||
item, err := loader.Get("key") | ||
// use item | ||
} | ||
|
||
func fetchItem(key interface{}) (interface{}, error) { | ||
res, err := http.Get("https://example.com/item/" + key) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return processResponse(res) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/abihf/cache-loader | ||
|
||
go 1.14 | ||
|
||
require github.com/hashicorp/golang-lru v0.5.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= | ||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package loader | ||
|
||
import ( | ||
"sync" | ||
"time" | ||
) | ||
|
||
// LoadFunc loads the value based on key | ||
type LoadFunc func(key interface{}) (interface{}, error) | ||
|
||
// Cache stores the items | ||
// you can use ARCCache or TwoQueueCache from github.com/hashicorp/golang-lru | ||
type Cache interface { | ||
Add(key, value interface{}) | ||
Get(key interface{}) (value interface{}, ok bool) | ||
Remove(key interface{}) | ||
} | ||
|
||
// Loader manage items in cache and fetch them if not exist | ||
type Loader struct { | ||
fn LoadFunc | ||
cache Cache | ||
ttl time.Duration | ||
|
||
mutex sync.Mutex | ||
} | ||
|
||
// New creates new Loader | ||
func New(fn LoadFunc, ttl time.Duration, cache Cache) *Loader { | ||
return &Loader{ | ||
fn: fn, | ||
cache: cache, | ||
ttl: ttl, | ||
|
||
mutex: sync.Mutex{}, | ||
} | ||
} | ||
|
||
// Get the item. | ||
// If it doesn't exist on cache, Loader will call LoadFunc once even when other go routine access the same key. | ||
// If the item is expired, it will return old value while loading new one. | ||
func (l *Loader) Get(key interface{}) (interface{}, error) { | ||
l.mutex.Lock() | ||
cached, ok := l.cache.Get(key) | ||
if ok { | ||
defer l.mutex.Unlock() | ||
|
||
item := cached.(*cacheItem) | ||
item.mutex.Lock() | ||
defer item.mutex.Unlock() | ||
|
||
if item.expire.Before(time.Now()) && !item.isFetching { | ||
item.isFetching = true // so other thread don't fetch | ||
go l.refetch(key, item) | ||
} | ||
return item.value, nil | ||
} | ||
|
||
item := &cacheItem{isFetching: true, mutex: sync.Mutex{}} | ||
item.mutex.Lock() | ||
defer item.mutex.Unlock() | ||
defer func() { | ||
item.isFetching = false | ||
}() | ||
l.cache.Add(key, item) | ||
l.mutex.Unlock() | ||
|
||
value, err := l.fn(key) | ||
if err != nil { | ||
l.cache.Remove(key) | ||
return nil, err | ||
} | ||
item.value = value | ||
item.updateExpire(l.ttl) | ||
return value, nil | ||
} | ||
|
||
func (l *Loader) refetch(key interface{}, item *cacheItem) { | ||
item.isFetching = true // to make sure, lol | ||
defer func() { | ||
item.isFetching = false | ||
}() | ||
|
||
value, err := l.fn(key) | ||
if err != nil { | ||
l.cache.Remove(key) | ||
return | ||
} | ||
item.value = value | ||
item.updateExpire(l.ttl) | ||
} | ||
|
||
type cacheItem struct { | ||
value interface{} | ||
expire time.Time | ||
|
||
mutex sync.Mutex | ||
isFetching bool | ||
} | ||
|
||
func (i *cacheItem) updateExpire(ttl time.Duration) { | ||
newExpire := time.Now().Add(ttl) | ||
i.expire = newExpire | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package loader | ||
|
||
import ( | ||
"time" | ||
|
||
lru "github.com/hashicorp/golang-lru" | ||
) | ||
|
||
// NewLRU creates Loader with lru based cache | ||
func NewLRU(fn LoadFunc, ttl time.Duration, size int) *Loader { | ||
cache, _ := lru.NewARC(size) | ||
return New(fn, ttl, cache) | ||
} |