Compare commits
2 commits
tcl/master
...
fix-4883-c
Author | SHA1 | Date | |
---|---|---|---|
|
544757a33b | ||
|
bfcf6baf93 |
2 changed files with 48 additions and 19 deletions
37
lib/cache/cache.go
vendored
37
lib/cache/cache.go
vendored
|
@ -22,15 +22,17 @@ func New() *Cache {
|
|||
return &Cache{
|
||||
cache: map[string]*cacheEntry{},
|
||||
expireRunning: false,
|
||||
expireDuration: 300 * time.Second,
|
||||
expireDuration: 24 * time.Hour,
|
||||
expireInterval: 60 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// cacheEntry is stored in the cache
|
||||
type cacheEntry struct {
|
||||
createMu sync.Mutex // held while creating the item
|
||||
value interface{} // cached item
|
||||
err error // creation error
|
||||
ok bool // true if entry is valid
|
||||
key string // key
|
||||
lastUsed time.Time // time used for expiry
|
||||
pinCount int // non zero if the entry should not be removed
|
||||
|
@ -55,23 +57,27 @@ func (c *Cache) used(entry *cacheEntry) {
|
|||
// afresh with the create function.
|
||||
func (c *Cache) Get(key string, create CreateFunc) (value interface{}, err error) {
|
||||
c.mu.Lock()
|
||||
entry, ok := c.cache[key]
|
||||
if !ok {
|
||||
c.mu.Unlock() // Unlock in case Get is called recursively
|
||||
value, ok, err = create(key)
|
||||
if err != nil && !ok {
|
||||
return value, err
|
||||
}
|
||||
entry, found := c.cache[key]
|
||||
if !found {
|
||||
entry = &cacheEntry{
|
||||
value: value,
|
||||
key: key,
|
||||
err: err,
|
||||
key: key,
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.cache[key] = entry
|
||||
}
|
||||
defer c.mu.Unlock()
|
||||
c.used(entry)
|
||||
c.mu.Unlock()
|
||||
// Only one racing Get will have found=false here
|
||||
entry.createMu.Lock()
|
||||
if !found {
|
||||
entry.value, entry.ok, entry.err = create(key)
|
||||
}
|
||||
entry.createMu.Unlock()
|
||||
c.mu.Lock()
|
||||
if !found && !entry.ok {
|
||||
delete(c.cache, key)
|
||||
} else {
|
||||
c.used(entry)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return entry.value, entry.err
|
||||
}
|
||||
|
||||
|
@ -102,6 +108,7 @@ func (c *Cache) Put(key string, value interface{}) {
|
|||
entry := &cacheEntry{
|
||||
value: value,
|
||||
key: key,
|
||||
ok: true,
|
||||
}
|
||||
c.used(entry)
|
||||
c.cache[key] = entry
|
||||
|
@ -112,7 +119,7 @@ func (c *Cache) GetMaybe(key string) (value interface{}, found bool) {
|
|||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
entry, found := c.cache[key]
|
||||
if !found {
|
||||
if !found || !entry.ok {
|
||||
return nil, found
|
||||
}
|
||||
c.used(entry)
|
||||
|
|
30
lib/cache/cache_test.go
vendored
30
lib/cache/cache_test.go
vendored
|
@ -3,6 +3,8 @@ package cache
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -11,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
called = 0
|
||||
called = int32(0)
|
||||
errSentinel = errors.New("an error")
|
||||
errCached = errors.New("a cached error")
|
||||
)
|
||||
|
@ -19,17 +21,19 @@ var (
|
|||
func setup(t *testing.T) (*Cache, CreateFunc) {
|
||||
called = 0
|
||||
create := func(path string) (interface{}, bool, error) {
|
||||
assert.Equal(t, 0, called)
|
||||
called++
|
||||
newCalled := atomic.AddInt32(&called, 1)
|
||||
assert.Equal(t, int32(1), newCalled)
|
||||
switch path {
|
||||
case "/":
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return "/", true, nil
|
||||
case "/file.txt":
|
||||
return "/file.txt", true, errCached
|
||||
case "/error":
|
||||
return nil, false, errSentinel
|
||||
}
|
||||
panic(fmt.Sprintf("Unknown path %q", path))
|
||||
assert.Fail(t, fmt.Sprintf("Unknown path %q", path))
|
||||
return nil, false, nil
|
||||
}
|
||||
c := New()
|
||||
return c, create
|
||||
|
@ -51,6 +55,24 @@ func TestGet(t *testing.T) {
|
|||
assert.Equal(t, f, f2)
|
||||
}
|
||||
|
||||
func TestGetConcurrent(t *testing.T) {
|
||||
c, create := setup(t)
|
||||
assert.Equal(t, 0, len(c.cache))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, err := c.Get("/", create)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(t, 1, len(c.cache))
|
||||
}
|
||||
|
||||
func TestGetFile(t *testing.T) {
|
||||
c, create := setup(t)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue