From d53a86923570d403b6bd9157cf66b1396dde6393 Mon Sep 17 00:00:00 2001 From: "J. David Lee" Date: Tue, 6 Apr 2021 17:51:16 +0200 Subject: [PATCH] kvmemcache: tests complete --- kvmemcache/cache.go | 2 +- kvmemcache/cache_test.go | 78 ++++++++++++++++++++++++++++++++++------ kvmemcache/internal.go | 10 +++--- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/kvmemcache/cache.go b/kvmemcache/cache.go index eea9b90..b451913 100644 --- a/kvmemcache/cache.go +++ b/kvmemcache/cache.go @@ -47,7 +47,7 @@ func New(conf Config) *Cache { } func (c *Cache) Get(key string) (interface{}, error) { - val, err, ok := c.get(key) + ok, val, err := c.get(key) if ok { return val, err } diff --git a/kvmemcache/cache_test.go b/kvmemcache/cache_test.go index a9eb5b4..2231ebb 100644 --- a/kvmemcache/cache_test.go +++ b/kvmemcache/cache_test.go @@ -3,6 +3,7 @@ package kvmemcache import ( "errors" "fmt" + "sync" "testing" "time" ) @@ -48,15 +49,15 @@ func (c *Cache) assert(state State) error { return nil } -var TestError = errors.New("Hello") +var ErrTest = errors.New("Hello") -func TestCache_Basic(t *testing.T) { +func TestCache_basic(t *testing.T) { c := New(Config{ MaxSize: 4, TTL: 50 * time.Millisecond, Src: func(key string) (interface{}, error) { if key == "err" { - return nil, TestError + return nil, ErrTest } return key, nil }, @@ -66,6 +67,7 @@ func TestCache_Basic(t *testing.T) { name string sleep time.Duration key string + evict bool state State } @@ -127,17 +129,43 @@ func TestCache_Basic(t *testing.T) { Keys: []string{"b", "c", "d", "e"}, Stats: Stats{Hits: 2, Misses: 6}, }, + }, { + name: "get err", + key: "err", + state: State{ + Keys: []string{"c", "d", "e", "err"}, + Stats: Stats{Hits: 2, Misses: 7}, + }, + }, { + name: "get err again", + key: "err", + state: State{ + Keys: []string{"c", "d", "e", "err"}, + Stats: Stats{Hits: 3, Misses: 7}, + }, + }, { + name: "evict c", + key: "c", + evict: true, + state: State{ + Keys: []string{"d", "e", "err"}, + Stats: Stats{Hits: 3, Misses: 7}, + }, }, } for _, tc := range cases { time.Sleep(tc.sleep) - val, err := c.Get(tc.key) - if tc.key == "err" && err != TestError { - t.Fatal(tc.name, val) - } - if tc.key != "err" && val.(string) != tc.key { - t.Fatal(tc.name, tc.key, val) + if !tc.evict { + val, err := c.Get(tc.key) + if tc.key == "err" && err != ErrTest { + t.Fatal(tc.name, val) + } + if tc.key != "err" && val.(string) != tc.key { + t.Fatal(tc.name, tc.key, val) + } + } else { + c.Evict(tc.key) } if err := c.assert(tc.state); err != nil { @@ -146,4 +174,34 @@ func TestCache_Basic(t *testing.T) { } } -// TODO: Test thundering herd mitigation. +func TestCache_thunderingHerd(t *testing.T) { + c := New(Config{ + MaxSize: 4, + Src: func(key string) (interface{}, error) { + time.Sleep(time.Second) + return key, nil + }, + }) + + wg := sync.WaitGroup{} + for i := 0; i < 1024; i++ { + wg.Add(1) + go func() { + defer wg.Done() + val, err := c.Get("a") + if err != nil { + panic(err) + } + if val != "a" { + panic(err) + } + }() + } + + wg.Wait() + + stats := c.Stats() + if stats.Hits != 1023 || stats.Misses != 1 { + t.Fatal(stats) + } +} diff --git a/kvmemcache/internal.go b/kvmemcache/internal.go index ea4efee..693dc1e 100644 --- a/kvmemcache/internal.go +++ b/kvmemcache/internal.go @@ -30,24 +30,24 @@ func (c *Cache) evict(key string) { } } -func (c *Cache) get(key string) (val interface{}, err error, ok bool) { +func (c *Cache) get(key string) (ok bool, val interface{}, err error) { c.lock.Lock() defer c.lock.Unlock() li := c.cache[key] if li == nil { - return nil, nil, false + return false, nil, nil } item := li.Value.(lruItem) // Maybe evict. if c.ttl != 0 && time.Since(item.createdAt) > c.ttl { c.evict(key) - return nil, nil, false + return false, nil, nil } c.stats.Hits++ - return item.value, item.err, true + return true, item.value, item.err } func (c *Cache) load(key string) (interface{}, error) { @@ -55,7 +55,7 @@ func (c *Cache) load(key string) (interface{}, error) { defer c.updateLock.Unlock(key) // Check again in case we lost the update race. - val, err, ok := c.get(key) + ok, val, err := c.get(key) if ok { return val, err }