-
Notifications
You must be signed in to change notification settings - Fork 1
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
6 changed files
with
305 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 +1,20 @@ | ||
# niftycache | ||
|
||
```go | ||
cb := func(k string, v interface{}) { | ||
fmt.Printf("key: %s | value: %+v\n", k, v) | ||
} | ||
|
||
cache := niftycache.New(10*time.Minute, | ||
niftycache.ExpireCallback(cb), | ||
niftycache.RemoveCallback(cb), | ||
niftycache.SetCallback(cb), | ||
niftycache.UpdateCallback(cb), | ||
// controls whether a successful cache hit extends the item's ttl (default false) | ||
niftycache.ExtendTTLOnHit(), | ||
// controls the max amount of items that can be expired at once (default 10000) | ||
niftycache.MaxExpires(10000), | ||
// controls the max amount of concurrent callbacks that can be fired (default 1000) | ||
niftycache.MaxCallbacks(1000), | ||
) | ||
``` |
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/c3mb0/niftycache | ||
|
||
go 1.13 | ||
|
||
require github.com/eapache/queue v1.1.0 |
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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= | ||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= |
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,34 @@ | ||
package niftycache | ||
|
||
import "time" | ||
|
||
type item struct { | ||
key string | ||
value interface{} | ||
ttl time.Duration | ||
expireAt time.Time | ||
index int | ||
} | ||
|
||
func newItem(key string, value interface{}, ttl time.Duration) *item { | ||
item := &item{ | ||
value: value, | ||
ttl: ttl, | ||
key: key, | ||
} | ||
item.touch() | ||
return item | ||
} | ||
|
||
func (item *item) update(value interface{}) { | ||
item.value = value | ||
item.touch() | ||
} | ||
|
||
func (item *item) touch() { | ||
item.expireAt = time.Now().Add(item.ttl) | ||
} | ||
|
||
func (item *item) expired() bool { | ||
return item.expireAt.Before(time.Now()) | ||
} |
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,66 @@ | ||
package niftycache | ||
|
||
import "container/heap" | ||
|
||
func newItemsHeap() *itemsHeap { | ||
ih := new(itemsHeap) | ||
heap.Init(ih) | ||
return ih | ||
} | ||
|
||
type itemsHeap struct { | ||
items []*item | ||
} | ||
|
||
func (ih *itemsHeap) peek() *item { | ||
if ih.Len() == 0 { | ||
return nil | ||
} | ||
return ih.items[0] | ||
} | ||
|
||
func (ih *itemsHeap) update(item *item) { | ||
heap.Fix(ih, item.index) | ||
} | ||
|
||
func (ih *itemsHeap) push(item *item) { | ||
heap.Push(ih, item) | ||
} | ||
|
||
func (ih *itemsHeap) pop() { | ||
heap.Pop(ih) | ||
} | ||
|
||
func (ih *itemsHeap) remove(item *item) { | ||
heap.Remove(ih, item.index) | ||
} | ||
|
||
func (ih itemsHeap) Len() int { | ||
return len(ih.items) | ||
} | ||
|
||
func (ih itemsHeap) Less(i, j int) bool { | ||
return ih.items[i].expireAt.Before(ih.items[j].expireAt) | ||
} | ||
|
||
func (ih itemsHeap) Swap(i, j int) { | ||
ih.items[i], ih.items[j] = ih.items[j], ih.items[i] | ||
ih.items[i].index = i | ||
ih.items[j].index = j | ||
} | ||
|
||
func (ih *itemsHeap) Push(x interface{}) { | ||
item := x.(*item) | ||
item.index = len(ih.items) | ||
ih.items = append(ih.items, item) | ||
} | ||
|
||
func (ih *itemsHeap) Pop() interface{} { | ||
old := ih.items | ||
n := len(old) | ||
item := old[n-1] | ||
item.index = -1 | ||
old[n-1] = nil | ||
ih.items = old[0 : n-1] | ||
return item | ||
} |
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,179 @@ | ||
package niftycache | ||
|
||
import ( | ||
"sync" | ||
"time" | ||
|
||
"github.com/eapache/queue" | ||
) | ||
|
||
type Cache struct { | ||
ttl time.Duration | ||
removeCB callback | ||
setCB callback | ||
updateCB callback | ||
expireCB callback | ||
extendTTL bool | ||
items map[string]*item | ||
ih *itemsHeap | ||
m *sync.Mutex | ||
maxExpires int | ||
maxCallbacks int | ||
cbLimiter chan struct{} | ||
callbacks *queue.Queue | ||
} | ||
|
||
type Option func(*Cache) | ||
|
||
type Callback func(string, interface{}) | ||
|
||
type callback func(string, interface{}) func() | ||
|
||
func createCBClosure(f Callback) callback { | ||
return func(k string, v interface{}) func() { | ||
return func() { | ||
f(k, v) | ||
} | ||
} | ||
} | ||
|
||
func RemoveCallback(f Callback) Option { | ||
return func(nc *Cache) { | ||
nc.removeCB = createCBClosure(f) | ||
} | ||
} | ||
|
||
func UpdateCallback(f Callback) Option { | ||
return func(nc *Cache) { | ||
nc.updateCB = createCBClosure(f) | ||
} | ||
} | ||
|
||
func SetCallback(f Callback) Option { | ||
return func(nc *Cache) { | ||
nc.setCB = createCBClosure(f) | ||
} | ||
} | ||
|
||
func ExpireCallback(f Callback) Option { | ||
return func(nc *Cache) { | ||
nc.expireCB = createCBClosure(f) | ||
} | ||
} | ||
|
||
func ExtendTTLOnHit() Option { | ||
return func(nc *Cache) { | ||
nc.extendTTL = true | ||
} | ||
} | ||
|
||
func MaxExpires(maxExpires int) Option { | ||
return func(nc *Cache) { | ||
nc.maxExpires = maxExpires | ||
} | ||
} | ||
|
||
func MaxCallbacks(maxCallbacks int) Option { | ||
return func(nc *Cache) { | ||
nc.maxCallbacks = maxCallbacks | ||
} | ||
} | ||
|
||
func New(ttl time.Duration, options ...Option) *Cache { | ||
nc := &Cache{ | ||
ttl: ttl, | ||
items: make(map[string]*item), | ||
ih: newItemsHeap(), | ||
m: new(sync.Mutex), | ||
maxExpires: 10000, | ||
maxCallbacks: 1000, | ||
callbacks: queue.New(), | ||
} | ||
for _, o := range options { | ||
o(nc) | ||
} | ||
nc.cbLimiter = make(chan struct{}, nc.maxCallbacks) | ||
go nc.handleExpirations() | ||
go nc.handleCallbacks() | ||
return nc | ||
} | ||
|
||
func (nc *Cache) handleCallbacks() { | ||
for { | ||
nc.m.Lock() | ||
if nc.callbacks.Length() == 0 { | ||
nc.m.Unlock() | ||
time.Sleep(time.Second) | ||
continue | ||
} | ||
out := nc.callbacks.Remove() | ||
nc.m.Unlock() | ||
cb := out.(func()) | ||
nc.cbLimiter <- struct{}{} | ||
go func() { | ||
cb() | ||
<-nc.cbLimiter | ||
}() | ||
} | ||
} | ||
|
||
func (nc *Cache) handleExpirations() { | ||
for range time.Tick(time.Second) { | ||
nc.m.Lock() | ||
for item, j := nc.ih.peek(), 0; j < nc.maxExpires && item != nil && item.expired(); item, j = nc.ih.peek(), j+1 { | ||
delete(nc.items, item.key) | ||
nc.ih.pop() | ||
if nc.expireCB != nil { | ||
nc.callbacks.Add(nc.expireCB(item.key, item.value)) | ||
} | ||
} | ||
nc.m.Unlock() | ||
} | ||
} | ||
|
||
func (nc *Cache) Remove(key string) { | ||
nc.m.Lock() | ||
defer nc.m.Unlock() | ||
item, ok := nc.items[key] | ||
if !ok { | ||
return | ||
} | ||
delete(nc.items, key) | ||
nc.ih.remove(item) | ||
if nc.removeCB != nil { | ||
nc.callbacks.Add(nc.removeCB(item.key, item.value)) | ||
} | ||
} | ||
|
||
func (nc *Cache) Get(key string) (interface{}, bool) { | ||
nc.m.Lock() | ||
defer nc.m.Unlock() | ||
item, ok := nc.items[key] | ||
if !ok { | ||
return nil, false | ||
} | ||
if nc.extendTTL { | ||
item.touch() | ||
} | ||
return item.value, true | ||
} | ||
|
||
func (nc *Cache) Set(key string, value interface{}) { | ||
nc.m.Lock() | ||
item, ok := nc.items[key] | ||
if !ok { | ||
item = newItem(key, value, nc.ttl) | ||
nc.items[key] = item | ||
nc.ih.push(item) | ||
if nc.setCB != nil { | ||
nc.callbacks.Add(nc.setCB(item.key, item.value)) | ||
} | ||
} else { | ||
item.update(value) | ||
nc.ih.update(item) | ||
if nc.updateCB != nil { | ||
nc.callbacks.Add(nc.updateCB(item.key, item.value)) | ||
} | ||
} | ||
nc.m.Unlock() | ||
} |