Provide GetAndSet(k string, setFn func) to allow synchronized atomic upsert behavior when key doesn't exist in cache

This commit is contained in:
Douglas Daniels 2016-02-10 14:56:17 -06:00
parent e7a9def80f
commit 15c04393c8
No known key found for this signature in database
GPG Key ID: 3367E6E0094014E9
2 changed files with 90 additions and 1 deletions

View File

@ -149,6 +149,38 @@ func (c *cache) get(k string) (interface{}, bool) {
return item.Object, true return item.Object, true
} }
func (c *cache) getWithExpiration(k string) (interface{}, time.Time, bool) {
item, found := c.items[k]
if !found {
return nil, time.Time{}, false
}
// "Inlining" of Expired
if item.Expiration > 0 {
if time.Now().UnixNano() > item.Expiration {
return nil, time.Time{}, false
}
}
return item.Object, time.Unix(0, item.Expiration), true
}
// SetTransaction given a values current state and expiration and whether it was found in the cache
// atomically update and return the value and the new expiration time
type SetTransaction func(interface{}, time.Time, bool) (interface{}, time.Duration)
// GetAndSet allows retrieval of a cache key and setting the value based on a setFn in a synchronized atomic manner
func (c *cache) GetAndSet(k string, setFn SetTransaction) (interface{}, bool) {
c.mu.Lock()
defer c.mu.Unlock()
v, expiration, found := c.getWithExpiration(k)
newV, duration := setFn(v, expiration, found)
c.set(k, newV, duration)
return newV, found
}
// Increment an item of type int, int8, int16, int32, int64, uintptr, uint, // Increment an item of type int, int8, int16, int32, int64, uintptr, uint,
// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the // uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the
// item's value is not an integer, if it was not found, or if it is not // item's value is not an integer, if it was not found, or if it is not

View File

@ -68,6 +68,63 @@ func TestCache(t *testing.T) {
} }
} }
func TestGetAndSet(t *testing.T) {
var found bool
tc := New(50*time.Millisecond, 1*time.Millisecond)
setHandler := func(v interface{}, expiration time.Time, found bool) (interface{}, time.Duration) {
// Cache miss so set to 1 with default expiration
if !found {
return int64(1), DefaultExpiration
}
return v.(int64) + 1, time.Now().Sub(expiration)
}
x, found := tc.GetAndSet("a", func(v interface{}, expiration time.Time, found bool) (interface{}, time.Duration) {
if found {
t.Error("a was found while GetAndSet")
}
if v != nil {
t.Error("expected key[a] to be nil")
}
if !expiration.IsZero() {
t.Error("expected expiration to not exist")
}
return setHandler(v, expiration, found)
})
if x != int64(1) {
t.Errorf("Expected x = 1 after GetAndSet but was %d", x)
}
if found {
t.Errorf("Expected found[false] on initial GetAndSet but was %t", found)
}
x, found = tc.GetAndSet("a", func(v interface{}, expiration time.Time, found bool) (interface{}, time.Duration) {
if !found {
t.Error("a was not found in second GetAndSet")
}
if v == nil {
t.Error("expected key[a] to not be nil")
}
if expiration.IsZero() {
t.Error("expected expiration to exist")
}
return setHandler(v, expiration, found)
})
if x != int64(2) {
t.Errorf("Expected x = 2 after GetAndSet but was %d", x)
}
if !found {
t.Errorf("Expected found[true] on second GetAndSet was %t", found)
}
}
func TestCacheTimes(t *testing.T) { func TestCacheTimes(t *testing.T) {
var found bool var found bool
@ -1459,7 +1516,7 @@ func BenchmarkRWMutexMapGet(b *testing.B) {
func BenchmarkRWMutexInterfaceMapGetStruct(b *testing.B) { func BenchmarkRWMutexInterfaceMapGetStruct(b *testing.B) {
b.StopTimer() b.StopTimer()
s := struct{name string}{name: "foo"} s := struct{ name string }{name: "foo"}
m := map[interface{}]string{ m := map[interface{}]string{
s: "bar", s: "bar",
} }