Skip to content

Commit

Permalink
Add Pop()
Browse files Browse the repository at this point in the history
  • Loading branch information
Elias Van Ootegem authored and arp242 committed Jul 30, 2020
1 parent 672b570 commit 29e104d
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 66 deletions.
103 changes: 52 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,68 +27,69 @@ included; in short:
- Add `Keys()` to list all keys
- Add `Touch()` to update the expiry on an item.
- Add `GetStale()` to get items even after they've expired.
- Add `Pop()` to update the expiry on an item.


Usage
-----

```go
import (
"fmt"
"time"
"fmt"
"time"

"zgo.at/zcache"
"zgo.at/zcache"
)

func main() {
// Create a cache with a default expiration time of 5 minutes, and which
// purges expired items every 10 minutes
c := zcache.New(5*time.Minute, 10*time.Minute)

// Set the value of the key "foo" to "bar", with the default expiration time
c.Set("foo", "bar", zcache.DefaultExpiration)

// Set the value of the key "baz" to 42, with no expiration time
// (the item won't be removed until it is re-set, or removed using
// c.Delete("baz")
c.Set("baz", 42, zcache.NoExpiration)

// Get the string associated with the key "foo" from the cache
foo, found := c.Get("foo")
if found {
fmt.Println(foo)
}

// Since Go is statically typed, and cache values can be anything, type
// assertion is needed when values are being passed to functions that don't
// take arbitrary types, (i.e. interface{}). The simplest way to do this for
// values which will only be used once--e.g. for passing to another
// function--is:
foo, found := c.Get("foo")
if found {
MyFunction(foo.(string))
}

// This gets tedious if the value is used several times in the same function.
// You might do either of the following instead:
if x, found := c.Get("foo"); found {
foo := x.(string)
// ...
}
// or
var foo string
if x, found := c.Get("foo"); found {
foo = x.(string)
}
// ...
// foo can then be passed around freely as a string

// Want performance? Store pointers!
c.Set("foo", &MyStruct, zcache.DefaultExpiration)
if x, found := c.Get("foo"); found {
foo := x.(*MyStruct)
// ...
}
// Create a cache with a default expiration time of 5 minutes, and which
// purges expired items every 10 minutes
c := zcache.New(5*time.Minute, 10*time.Minute)

// Set the value of the key "foo" to "bar", with the default expiration time
c.Set("foo", "bar", zcache.DefaultExpiration)

// Set the value of the key "baz" to 42, with no expiration time
// (the item won't be removed until it is re-set, or removed using
// c.Delete("baz")
c.Set("baz", 42, zcache.NoExpiration)

// Get the string associated with the key "foo" from the cache
foo, ok := c.Get("foo")
if ok {
fmt.Println(foo)
}

// Since Go is statically typed, and cache values can be anything, type
// assertion is needed when values are being passed to functions that don't
// take arbitrary types, (i.e. interface{}). The simplest way to do this for
// values which will only be used once--e.g. for passing to another
// function--is:
foo, ok := c.Get("foo")
if ok {
MyFunction(foo.(string))
}

// This gets tedious if the value is used several times in the same function.
// You might do either of the following instead:
if x, ok := c.Get("foo"); ok {
foo := x.(string)
// ...
}
// or
var foo string
if x, ok := c.Get("foo"); ok {
foo = x.(string)
}
// ...
// foo can then be passed around freely as a string

// Want performance? Store pointers!
c.Set("foo", &MyStruct, zcache.DefaultExpiration)
if x, ok := c.Get("foo"); ok {
foo := x.(*MyStruct)
// ...
}
}
```

Expand Down
24 changes: 24 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,30 @@ func (c *cache) Delete(k string) {
}
}

// Pop gets an item from the cache and deletes it.
func (c *cache) Pop(k string) (interface{}, bool) {
c.mu.Lock()

// "Inlining" of get and Expired
item, ok := c.items[k]
if !ok {
c.mu.Unlock()
return nil, false
}
if item.Expiration > 0 && time.Now().UnixNano() > item.Expiration {
c.mu.Unlock()
return nil, false
}

v, evicted := c.delete(k)
c.mu.Unlock()
if evicted {
c.onEvicted(k, v)
}

return item.Object, true
}

func (c *cache) delete(k string) (interface{}, bool) {
if c.onEvicted != nil {
if v, ok := c.items[k]; ok {
Expand Down
52 changes: 52 additions & 0 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,58 @@ func TestDelete(t *testing.T) {
})
}

type onEvictTest struct {
sync.Mutex
items []struct {
k string
v interface{}
}
}

func (o *onEvictTest) add(k string, v interface{}) {
if k == "race" {
return
}
o.Lock()
o.items = append(o.items, struct {
k string
v interface{}
}{k, v})
o.Unlock()
}

func TestPop(t *testing.T) {
tc := New(DefaultExpiration, 0)

var onEvict onEvictTest
tc.OnEvicted(onEvict.add)

raceTest(tc, func() {
tc.Set("foo", "val", DefaultExpiration)

v, ok := tc.Pop("foo")
wantKeys(t, tc, []string{}, []string{"foo"})
if !ok {
t.Error("ok is false")
}
if v.(string) != "val" {
t.Errorf("wrong value: %v", v)
}

v, ok = tc.Pop("nonexistent")
if ok {
t.Error("ok is true")
}
if v != nil {
t.Errorf("v is not nil")
}
})

if fmt.Sprintf("%v", onEvict.items) != `[{foo val}]` {
t.Errorf("onEvicted: %v", onEvict.items)
}
}

func TestItems(t *testing.T) {
tc := New(DefaultExpiration, 1*time.Millisecond)
raceTest(tc, func() {
Expand Down
20 changes: 5 additions & 15 deletions issues.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ or issue that was reported should be resolved.
https://github.com/patrickmn/go-cache/pull/96
https://github.com/patrickmn/go-cache/pull/126
https://github.com/patrickmn/go-cache/issues/65
https://github.com/patrickmn/go-cache/pull/55

Added as `Touch()`
Added as `Touch()`; PR 55 also added `Pop()`, which seems like a useful thing
to add as well.

- https://github.com/patrickmn/go-cache/pull/47
https://github.com/patrickmn/go-cache/pull/53
Expand Down Expand Up @@ -56,21 +58,9 @@ or issue that was reported should be resolved.
// Do stuff while it's locked.
})

- TODO
https://github.com/patrickmn/go-cache/pull/55

Some of this looks useful.

One way to do this would be to add a list of options in an (incompatible) API,
would also solve integrate some of the other this:

c.Get("key", zcache.Pop, z.cacheIncludeExpired) // Or as bitmask?

- TODO
https://github.com/patrickmn/go-cache/pull/77
- https://github.com/patrickmn/go-cache/pull/77

Remove item and return value; don't like the function name but could include
this.
Added as Pop()

- TODO
https://github.com/patrickmn/go-cache/pull/97
Expand Down

0 comments on commit 29e104d

Please sign in to comment.