Skip to content

Commit a2d6215

Browse files
authored
Merge pull request #8 from jdeppe-pivotal/master
Add a SecondaryCache which exposes the secondary part of a LayeredCache
2 parents 8adbb56 + a451d72 commit a2d6215

File tree

5 files changed

+216
-1
lines changed

5 files changed

+216
-1
lines changed

layeredbucket.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,21 @@ type layeredBucket struct {
1111
}
1212

1313
func (b *layeredBucket) get(primary, secondary string) *Item {
14+
bucket := b.getSecondaryBucket(primary)
15+
if bucket == nil {
16+
return nil
17+
}
18+
return bucket.get(secondary)
19+
}
20+
21+
func (b *layeredBucket) getSecondaryBucket(primary string) *bucket {
1422
b.RLock()
1523
bucket, exists := b.buckets[primary]
1624
b.RUnlock()
1725
if exists == false {
1826
return nil
1927
}
20-
return bucket.get(secondary)
28+
return bucket
2129
}
2230

2331
func (b *layeredBucket) set(primary, secondary string, value interface{}, duration time.Duration) (*Item, *Item) {

layeredcache.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,24 @@ func (c *LayeredCache) Get(primary, secondary string) *Item {
6464
return item
6565
}
6666

67+
// Get the secondary cache for a given primary key. This operation will
68+
// never return nil. In the case where the primary key does not exist, a
69+
// new, underlying, empty bucket will be created and returned.
70+
func (c *LayeredCache) GetOrCreateSecondaryCache(primary string) *SecondaryCache {
71+
primaryBkt := c.bucket(primary)
72+
bkt := primaryBkt.getSecondaryBucket(primary)
73+
primaryBkt.Lock()
74+
if bkt == nil {
75+
bkt = &bucket{lookup: make(map[string]*Item)}
76+
primaryBkt.buckets[primary] = bkt
77+
}
78+
primaryBkt.Unlock()
79+
return &SecondaryCache{
80+
bucket: bkt,
81+
pCache: c,
82+
}
83+
}
84+
6785
// Used when the cache was created with the Track() configuration option.
6886
// Avoid otherwise
6987
func (c *LayeredCache) TrackingGet(primary, secondary string) TrackedItem {

readme.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,18 @@ cache.Delete("/users/goku", "type:xml")
151151
cache.DeleteAll("/users/goku")
152152
```
153153

154+
# SecondaryCache
155+
156+
In some cases, when using a `LayeredCache`, it may be desirable to always be acting on the secondary portion of the cache entry. This could be the case where the primary key is used as a key elsewhere in your code. The `SecondaryCache` is retrieved with:
157+
158+
```go
159+
cache := ccache.Layered(ccache.Configure())
160+
sCache := cache.GetOrCreateSecondaryCache("/users/goku")
161+
sCache.Set("type:json", "{value_to_cache}", time.Minute * 5)
162+
```
163+
164+
The semantics for interacting with the `SecondaryCache` are exactly the same as for a regular `Cache`. However, one difference is that `Get` will not return nil, but will return an empty 'cache' for a non-existent primary key.
165+
154166
## Size
155167
By default, items added to a cache have a size of 1. This means that if you configure `MaxSize(10000)`, you'll be able to store 10000 items in the cache.
156168

secondarycache.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package ccache
2+
3+
import "time"
4+
5+
type SecondaryCache struct {
6+
bucket *bucket
7+
pCache *LayeredCache
8+
}
9+
10+
// Get the secondary key.
11+
// The semantics are the same as for LayeredCache.Get
12+
func (s *SecondaryCache) Get(secondary string) *Item {
13+
return s.bucket.get(secondary)
14+
}
15+
16+
// Set the secondary key to a value.
17+
// The semantics are the same as for LayeredCache.Set
18+
func (s *SecondaryCache) Set(secondary string, value interface{}, duration time.Duration) *Item {
19+
item, existing := s.bucket.set(secondary, value, duration)
20+
if existing != nil {
21+
s.pCache.deletables <- existing
22+
}
23+
s.pCache.promote(item)
24+
return item
25+
}
26+
27+
// Fetch or set a secondary key.
28+
// The semantics are the same as for LayeredCache.Fetch
29+
func (s *SecondaryCache) Fetch(secondary string, duration time.Duration, fetch func() (interface{}, error)) (*Item, error) {
30+
item := s.Get(secondary)
31+
if item != nil {
32+
return item, nil
33+
}
34+
value, err := fetch()
35+
if err != nil {
36+
return nil, err
37+
}
38+
return s.Set(secondary, value, duration), nil
39+
}
40+
41+
// Delete a secondary key.
42+
// The semantics are the same as for LayeredCache.Delete
43+
func (s *SecondaryCache) Delete(secondary string) bool {
44+
item := s.bucket.delete(secondary)
45+
if item != nil {
46+
s.pCache.deletables <- item
47+
return true
48+
}
49+
return false
50+
}
51+
52+
// Replace a secondary key.
53+
// The semantics are the same as for LayeredCache.Replace
54+
func (s *SecondaryCache) Replace(secondary string, value interface{}) bool {
55+
item := s.Get(secondary)
56+
if item == nil {
57+
return false
58+
}
59+
s.Set(secondary, value, item.TTL())
60+
return true
61+
}
62+
63+
// Track a secondary key.
64+
// The semantics are the same as for LayeredCache.TrackingGet
65+
func (c *SecondaryCache) TrackingGet(secondary string) TrackedItem {
66+
item := c.Get(secondary)
67+
if item == nil {
68+
return NilTracked
69+
}
70+
item.track()
71+
return item
72+
}

secondarycache_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package ccache
2+
3+
import (
4+
. "github.com/karlseguin/expect"
5+
"testing"
6+
"time"
7+
"strconv"
8+
)
9+
10+
type SecondaryCacheTests struct{}
11+
12+
func Test_SecondaryCache(t *testing.T) {
13+
Expectify(new(SecondaryCacheTests), t)
14+
}
15+
16+
func (_ SecondaryCacheTests) GetsANonExistantValue() {
17+
cache := newLayered().GetOrCreateSecondaryCache("foo")
18+
Expect(cache).Not.To.Equal(nil)
19+
}
20+
21+
func (_ SecondaryCacheTests) SetANewValue() {
22+
cache := newLayered()
23+
cache.Set("spice", "flow", "a value", time.Minute)
24+
sCache := cache.GetOrCreateSecondaryCache("spice")
25+
Expect(sCache.Get("flow").Value()).To.Equal("a value")
26+
Expect(sCache.Get("stop")).To.Equal(nil)
27+
}
28+
29+
func (_ SecondaryCacheTests) ValueCanBeSeenInBothCaches1() {
30+
cache := newLayered()
31+
cache.Set("spice", "flow", "a value", time.Minute)
32+
sCache := cache.GetOrCreateSecondaryCache("spice")
33+
sCache.Set("orinoco", "another value", time.Minute)
34+
Expect(sCache.Get("orinoco").Value()).To.Equal("another value")
35+
Expect(cache.Get("spice", "orinoco").Value()).To.Equal("another value")
36+
}
37+
38+
func (_ SecondaryCacheTests) ValueCanBeSeenInBothCaches2() {
39+
cache := newLayered()
40+
sCache := cache.GetOrCreateSecondaryCache("spice")
41+
sCache.Set("flow", "a value", time.Minute)
42+
Expect(sCache.Get("flow").Value()).To.Equal("a value")
43+
Expect(cache.Get("spice", "flow").Value()).To.Equal("a value")
44+
}
45+
46+
func (_ SecondaryCacheTests) DeletesAreReflectedInBothCaches() {
47+
cache := newLayered()
48+
cache.Set("spice", "flow", "a value", time.Minute)
49+
cache.Set("spice", "sister", "ghanima", time.Minute)
50+
sCache := cache.GetOrCreateSecondaryCache("spice")
51+
52+
cache.Delete("spice", "flow")
53+
Expect(cache.Get("spice", "flow")).To.Equal(nil)
54+
Expect(sCache.Get("flow")).To.Equal(nil)
55+
56+
sCache.Delete("sister")
57+
Expect(cache.Get("spice", "sister")).To.Equal(nil)
58+
Expect(sCache.Get("sister")).To.Equal(nil)
59+
}
60+
61+
func (_ SecondaryCacheTests) ReplaceDoesNothingIfKeyDoesNotExist() {
62+
cache := newLayered()
63+
sCache := cache.GetOrCreateSecondaryCache("spice")
64+
Expect(sCache.Replace("flow", "value-a")).To.Equal(false)
65+
Expect(cache.Get("spice", "flow")).To.Equal(nil)
66+
}
67+
68+
func (_ SecondaryCacheTests) ReplaceUpdatesTheValue() {
69+
cache := newLayered()
70+
cache.Set("spice", "flow", "value-a", time.Minute)
71+
sCache := cache.GetOrCreateSecondaryCache("spice")
72+
Expect(sCache.Replace("flow", "value-b")).To.Equal(true)
73+
Expect(cache.Get("spice", "flow").Value().(string)).To.Equal("value-b")
74+
}
75+
76+
func (_ SecondaryCacheTests) FetchReturnsAnExistingValue() {
77+
cache := newLayered()
78+
cache.Set("spice", "flow", "value-a", time.Minute)
79+
sCache := cache.GetOrCreateSecondaryCache("spice")
80+
val, _ := sCache.Fetch("flow", time.Minute, func() (interface{}, error) {return "a fetched value", nil})
81+
Expect(val.Value().(string)).To.Equal("value-a")
82+
}
83+
84+
func (_ SecondaryCacheTests) FetchReturnsANewValue() {
85+
cache := newLayered()
86+
sCache := cache.GetOrCreateSecondaryCache("spice")
87+
val, _ := sCache.Fetch("flow", time.Minute, func() (interface{}, error) {return "a fetched value", nil})
88+
Expect(val.Value().(string)).To.Equal("a fetched value")
89+
}
90+
91+
func (_ SecondaryCacheTests) TrackerDoesNotCleanupHeldInstance() {
92+
cache := Layered(Configure().ItemsToPrune(10).Track())
93+
for i := 0; i < 10; i++ {
94+
cache.Set(strconv.Itoa(i), "a", i, time.Minute)
95+
}
96+
sCache := cache.GetOrCreateSecondaryCache("0")
97+
item := sCache.TrackingGet("a")
98+
time.Sleep(time.Millisecond * 10)
99+
cache.gc()
100+
Expect(cache.Get("0", "a").Value()).To.Equal(0)
101+
Expect(cache.Get("1", "a")).To.Equal(nil)
102+
item.Release()
103+
cache.gc()
104+
Expect(cache.Get("0", "a")).To.Equal(nil)
105+
}

0 commit comments

Comments
 (0)