Add TrackingSet method
This method reduces the likelihood of a race condition where you can add a (tracked) item to the cache, and the item isn't the item you thought it was.
This commit is contained in:
parent
e9b7be5016
commit
df91803297
9 changed files with 36 additions and 20 deletions
|
@ -23,9 +23,9 @@ func (b *bucket) get(key string) *Item {
|
|||
return b.lookup[key]
|
||||
}
|
||||
|
||||
func (b *bucket) set(key string, value interface{}, duration time.Duration) (*Item, *Item) {
|
||||
func (b *bucket) set(key string, value interface{}, duration time.Duration, track bool) (*Item, *Item) {
|
||||
expires := time.Now().Add(duration).UnixNano()
|
||||
item := newItem(key, value, expires)
|
||||
item := newItem(key, value, expires, track)
|
||||
b.Lock()
|
||||
existing := b.lookup[key]
|
||||
b.lookup[key] = item
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package ccache
|
||||
|
||||
import (
|
||||
. "github.com/karlseguin/expect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/karlseguin/expect"
|
||||
)
|
||||
|
||||
type BucketTests struct {
|
||||
|
@ -32,7 +33,7 @@ func (_ *BucketTests) DeleteItemFromBucket() {
|
|||
|
||||
func (_ *BucketTests) SetsANewBucketItem() {
|
||||
bucket := testBucket()
|
||||
item, existing := bucket.set("spice", TestValue("flow"), time.Minute)
|
||||
item, existing := bucket.set("spice", TestValue("flow"), time.Minute, false)
|
||||
assertValue(item, "flow")
|
||||
item = bucket.get("spice")
|
||||
assertValue(item, "flow")
|
||||
|
@ -41,7 +42,7 @@ func (_ *BucketTests) SetsANewBucketItem() {
|
|||
|
||||
func (_ *BucketTests) SetsAnExistingItem() {
|
||||
bucket := testBucket()
|
||||
item, existing := bucket.set("power", TestValue("9001"), time.Minute)
|
||||
item, existing := bucket.set("power", TestValue("9001"), time.Minute, false)
|
||||
assertValue(item, "9001")
|
||||
item = bucket.get("power")
|
||||
assertValue(item, "9001")
|
||||
|
|
14
cache.go
14
cache.go
|
@ -98,9 +98,15 @@ func (c *Cache) TrackingGet(key string) TrackedItem {
|
|||
return item
|
||||
}
|
||||
|
||||
// Used when the cache was created with the Track() configuration option.
|
||||
// Sets the item, and returns a tracked reference to it.
|
||||
func (c *Cache) TrackingSet(key string, value interface{}, duration time.Duration) TrackedItem {
|
||||
return c.set(key, value, duration, true)
|
||||
}
|
||||
|
||||
// Set the value in the cache for the specified duration
|
||||
func (c *Cache) Set(key string, value interface{}, duration time.Duration) {
|
||||
c.set(key, value, duration)
|
||||
c.set(key, value, duration, false)
|
||||
}
|
||||
|
||||
// Replace the value if it exists, does not set if it doesn't.
|
||||
|
@ -127,7 +133,7 @@ func (c *Cache) Fetch(key string, duration time.Duration, fetch func() (interfac
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.set(key, value, duration), nil
|
||||
return c.set(key, value, duration, false), nil
|
||||
}
|
||||
|
||||
// Remove the item from the cache, return true if the item was present, false otherwise.
|
||||
|
@ -182,8 +188,8 @@ func (c *Cache) deleteItem(bucket *bucket, item *Item) {
|
|||
c.deletables <- item
|
||||
}
|
||||
|
||||
func (c *Cache) set(key string, value interface{}, duration time.Duration) *Item {
|
||||
item, existing := c.bucket(key).set(key, value, duration)
|
||||
func (c *Cache) set(key string, value interface{}, duration time.Duration, track bool) *Item {
|
||||
item, existing := c.bucket(key).set(key, value, duration, track)
|
||||
if existing != nil {
|
||||
c.deletables <- existing
|
||||
}
|
||||
|
|
|
@ -140,18 +140,21 @@ func (_ CacheTests) PromotedItemsDontGetPruned() {
|
|||
}
|
||||
|
||||
func (_ CacheTests) TrackerDoesNotCleanupHeldInstance() {
|
||||
cache := New(Configure().ItemsToPrune(10).Track())
|
||||
for i := 0; i < 10; i++ {
|
||||
cache := New(Configure().ItemsToPrune(11).Track())
|
||||
item0 := cache.TrackingSet("0", 0, time.Minute)
|
||||
for i := 1; i < 11; i++ {
|
||||
cache.Set(strconv.Itoa(i), i, time.Minute)
|
||||
}
|
||||
item := cache.TrackingGet("0")
|
||||
item1 := cache.TrackingGet("1")
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
gcCache(cache)
|
||||
Expect(cache.Get("0").Value()).To.Equal(0)
|
||||
Expect(cache.Get("1")).To.Equal(nil)
|
||||
item.Release()
|
||||
Expect(cache.Get("1").Value()).To.Equal(1)
|
||||
item0.Release()
|
||||
item1.Release()
|
||||
gcCache(cache)
|
||||
Expect(cache.Get("0")).To.Equal(nil)
|
||||
Expect(cache.Get("1")).To.Equal(nil)
|
||||
}
|
||||
|
||||
func (_ CacheTests) RemovesOldestItemWhenFull() {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package ccache
|
||||
|
||||
import (
|
||||
. "github.com/karlseguin/expect"
|
||||
"testing"
|
||||
|
||||
. "github.com/karlseguin/expect"
|
||||
)
|
||||
|
||||
type ConfigurationTests struct{}
|
||||
|
|
8
item.go
8
item.go
|
@ -52,18 +52,22 @@ type Item struct {
|
|||
element *list.Element
|
||||
}
|
||||
|
||||
func newItem(key string, value interface{}, expires int64) *Item {
|
||||
func newItem(key string, value interface{}, expires int64, track bool) *Item {
|
||||
size := int64(1)
|
||||
if sized, ok := value.(Sized); ok {
|
||||
size = sized.Size()
|
||||
}
|
||||
return &Item{
|
||||
item := &Item{
|
||||
key: key,
|
||||
value: value,
|
||||
promotions: 0,
|
||||
size: size,
|
||||
expires: expires,
|
||||
}
|
||||
if track {
|
||||
item.refCount = 1
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func (i *Item) shouldPromote(getsPerPromote int32) bool {
|
||||
|
|
|
@ -46,7 +46,7 @@ func (b *layeredBucket) set(primary, secondary string, value interface{}, durati
|
|||
b.buckets[primary] = bkt
|
||||
}
|
||||
b.Unlock()
|
||||
item, existing := bkt.set(secondary, value, duration)
|
||||
item, existing := bkt.set(secondary, value, duration, false)
|
||||
item.group = primary
|
||||
return item, existing
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ func (s *SecondaryCache) Get(secondary string) *Item {
|
|||
// Set the secondary key to a value.
|
||||
// The semantics are the same as for LayeredCache.Set
|
||||
func (s *SecondaryCache) Set(secondary string, value interface{}, duration time.Duration) *Item {
|
||||
item, existing := s.bucket.set(secondary, value, duration)
|
||||
item, existing := s.bucket.set(secondary, value, duration, false)
|
||||
if existing != nil {
|
||||
s.pCache.deletables <- existing
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package ccache
|
||||
|
||||
import (
|
||||
. "github.com/karlseguin/expect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/karlseguin/expect"
|
||||
)
|
||||
|
||||
type SecondaryCacheTests struct{}
|
||||
|
|
Loading…
Reference in a new issue