-
-
Notifications
You must be signed in to change notification settings - Fork 13
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
2 changed files
with
205 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 |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package cache | ||
|
||
// simplified version of github.com/patrickmn/go-cache | ||
|
||
import ( | ||
"sync" | ||
"time" | ||
) | ||
|
||
type Item struct { | ||
Object interface{} | ||
Expiration int64 | ||
} | ||
|
||
// Returns true if the item has expired. | ||
func (item Item) Expired() bool { | ||
if item.Expiration == 0 { | ||
return false | ||
} | ||
return time.Now().UnixNano() > item.Expiration | ||
} | ||
|
||
const ( | ||
// For use with functions that take an expiration time. | ||
NoExpiration time.Duration = -1 | ||
// For use with functions that take an expiration time. Equivalent to | ||
// passing in the same expiration duration as was given to New() or | ||
// NewFrom() when the cache was created (e.g. 5 minutes.) | ||
DefaultExpiration time.Duration = 0 | ||
) | ||
|
||
type C struct { | ||
defaultExpiration time.Duration | ||
items map[string]Item | ||
mu sync.RWMutex | ||
} | ||
|
||
func newCache(de time.Duration, m map[string]Item) *C { | ||
if de == 0 { | ||
de = -1 | ||
} | ||
c := &C{ | ||
defaultExpiration: de, | ||
items: m, | ||
} | ||
return c | ||
} | ||
|
||
// Return a new cache with a given default expiration duration and cleanup | ||
// interval. If the expiration duration is less than one (or NoExpiration), | ||
// the items in the cache never expire (by default), and must be deleted | ||
// manually. If the cleanup interval is less than one, expired items are not | ||
// deleted from the cache before calling c.DeleteExpired(). | ||
func New(defaultExpiration time.Duration) *C { | ||
items := make(map[string]Item) | ||
return newCache(defaultExpiration, items) | ||
} | ||
|
||
func (c *C) Set(k string, x interface{}, d time.Duration) { | ||
// "Inlining" of set | ||
var e int64 | ||
if d == DefaultExpiration { | ||
d = c.defaultExpiration | ||
} | ||
if d > 0 { | ||
e = time.Now().Add(d).UnixNano() | ||
} | ||
c.mu.Lock() | ||
c.items[k] = Item{ | ||
Object: x, | ||
Expiration: e, | ||
} | ||
// TODO: Calls to mu.Unlock are currently not deferred because defer | ||
// adds ~200 ns (as of go1.) | ||
c.mu.Unlock() | ||
} | ||
|
||
func (c *C) Get(k string) (interface{}, bool) { | ||
c.mu.RLock() | ||
// "Inlining" of get and Expired | ||
item, found := c.items[k] | ||
if !found { | ||
c.mu.RUnlock() | ||
return nil, false | ||
} | ||
if item.Expiration > 0 { | ||
if time.Now().UnixNano() > item.Expiration { | ||
c.mu.RUnlock() | ||
go c.DeleteExpired() | ||
return nil, false | ||
} | ||
} | ||
c.mu.RUnlock() | ||
return item.Object, true | ||
} | ||
|
||
// Delete all expired items from the cache. | ||
func (c *C) DeleteExpired() { | ||
now := time.Now().UnixNano() | ||
c.mu.Lock() | ||
for k, v := range c.items { | ||
// "Inlining" of expired | ||
if v.Expiration > 0 && now > v.Expiration { | ||
delete(c.items, k) | ||
} | ||
} | ||
c.mu.Unlock() | ||
} |
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,97 @@ | ||
package cache | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestCache(t *testing.T) { | ||
tc := New(DefaultExpiration) | ||
|
||
a, found := tc.Get("a") | ||
if found || a != nil { | ||
t.Error("Getting A found value that shouldn't exist:", a) | ||
} | ||
|
||
b, found := tc.Get("b") | ||
if found || b != nil { | ||
t.Error("Getting B found value that shouldn't exist:", b) | ||
} | ||
|
||
c, found := tc.Get("c") | ||
if found || c != nil { | ||
t.Error("Getting C found value that shouldn't exist:", c) | ||
} | ||
|
||
tc.Set("a", 1, DefaultExpiration) | ||
tc.Set("b", "b", DefaultExpiration) | ||
tc.Set("c", 3.5, DefaultExpiration) | ||
|
||
x, found := tc.Get("a") | ||
if !found { | ||
t.Error("a was not found while getting a2") | ||
} | ||
if x == nil { | ||
t.Error("x for a is nil") | ||
} else if a2 := x.(int); a2+2 != 3 { | ||
t.Error("a2 (which should be 1) plus 2 does not equal 3; value:", a2) | ||
} | ||
|
||
x, found = tc.Get("b") | ||
if !found { | ||
t.Error("b was not found while getting b2") | ||
} | ||
if x == nil { | ||
t.Error("x for b is nil") | ||
} else if b2 := x.(string); b2+"B" != "bB" { | ||
t.Error("b2 (which should be b) plus B does not equal bB; value:", b2) | ||
} | ||
|
||
x, found = tc.Get("c") | ||
if !found { | ||
t.Error("c was not found while getting c2") | ||
} | ||
if x == nil { | ||
t.Error("x for c is nil") | ||
} else if c2 := x.(float64); c2+1.2 != 4.7 { | ||
t.Error("c2 (which should be 3.5) plus 1.2 does not equal 4.7; value:", c2) | ||
} | ||
} | ||
|
||
func TestCacheTimes(t *testing.T) { | ||
var found bool | ||
|
||
tc := New(50 * time.Millisecond) | ||
tc.Set("a", 1, DefaultExpiration) | ||
tc.Set("b", 2, NoExpiration) | ||
tc.Set("c", 3, 20*time.Millisecond) | ||
tc.Set("d", 4, 70*time.Millisecond) | ||
|
||
<-time.After(25 * time.Millisecond) | ||
_, found = tc.Get("c") | ||
if found { | ||
t.Error("Found c when it should have been automatically deleted") | ||
} | ||
|
||
<-time.After(30 * time.Millisecond) | ||
_, found = tc.Get("a") | ||
if found { | ||
t.Error("Found a when it should have been automatically deleted") | ||
} | ||
|
||
_, found = tc.Get("b") | ||
if !found { | ||
t.Error("Did not find b even though it was set to never expire") | ||
} | ||
|
||
_, found = tc.Get("d") | ||
if !found { | ||
t.Error("Did not find d even though it was set to expire later than the default") | ||
} | ||
|
||
<-time.After(20 * time.Millisecond) | ||
_, found = tc.Get("d") | ||
if found { | ||
t.Error("Found d when it should have been automatically deleted (later than the default)") | ||
} | ||
} |