mirror of
https://github.com/rclone/rclone.git
synced 2025-01-08 12:34:53 +02:00
lib/cache: add Rename, Pin and Unpin
This commit is contained in:
parent
ca1856724c
commit
42f9f7fb5d
45
lib/cache/cache.go
vendored
45
lib/cache/cache.go
vendored
@ -33,6 +33,7 @@ type cacheEntry struct {
|
||||
err error // creation error
|
||||
key string // key
|
||||
lastUsed time.Time // time used for expiry
|
||||
pinCount int // non zero if the entry should not be removed
|
||||
}
|
||||
|
||||
// CreateFunc is called to create new values. If the create function
|
||||
@ -74,6 +75,26 @@ func (c *Cache) Get(key string, create CreateFunc) (value interface{}, err error
|
||||
return entry.value, entry.err
|
||||
}
|
||||
|
||||
func (c *Cache) addPin(key string, count int) {
|
||||
c.mu.Lock()
|
||||
entry, ok := c.cache[key]
|
||||
if ok {
|
||||
entry.pinCount += count
|
||||
c.used(entry)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Pin a value in the cache if it exists
|
||||
func (c *Cache) Pin(key string) {
|
||||
c.addPin(key, 1)
|
||||
}
|
||||
|
||||
// Unpin a value in the cache if it exists
|
||||
func (c *Cache) Unpin(key string) {
|
||||
c.addPin(key, -1)
|
||||
}
|
||||
|
||||
// Put puts an value named key into the cache
|
||||
func (c *Cache) Put(key string, value interface{}) {
|
||||
c.mu.Lock()
|
||||
@ -98,13 +119,35 @@ func (c *Cache) GetMaybe(key string) (value interface{}, found bool) {
|
||||
return entry.value, found
|
||||
}
|
||||
|
||||
// Rename renames the item at oldKey to newKey.
|
||||
//
|
||||
// If there was an existing item at newKey then it takes precedence
|
||||
// and is returned otherwise the item (if any) at oldKey is returned.
|
||||
func (c *Cache) Rename(oldKey, newKey string) (value interface{}, found bool) {
|
||||
c.mu.Lock()
|
||||
if newEntry, newFound := c.cache[newKey]; newFound {
|
||||
// If new entry is found use that
|
||||
delete(c.cache, oldKey)
|
||||
value, found = newEntry.value, newFound
|
||||
c.used(newEntry)
|
||||
} else if oldEntry, oldFound := c.cache[oldKey]; oldFound {
|
||||
// If old entry is found rename it to new and use that
|
||||
c.cache[newKey] = oldEntry
|
||||
delete(c.cache, oldKey)
|
||||
c.used(oldEntry)
|
||||
value, found = oldEntry.value, oldFound
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return value, found
|
||||
}
|
||||
|
||||
// cacheExpire expires any entries that haven't been used recently
|
||||
func (c *Cache) cacheExpire() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
now := time.Now()
|
||||
for key, entry := range c.cache {
|
||||
if now.Sub(entry.lastUsed) > c.expireDuration {
|
||||
if entry.pinCount <= 0 && now.Sub(entry.lastUsed) > c.expireDuration {
|
||||
delete(c.cache, key)
|
||||
}
|
||||
}
|
||||
|
89
lib/cache/cache_test.go
vendored
89
lib/cache/cache_test.go
vendored
@ -106,22 +106,74 @@ func TestCacheExpire(t *testing.T) {
|
||||
|
||||
c.mu.Lock()
|
||||
entry := c.cache["/"]
|
||||
|
||||
assert.Equal(t, 1, len(c.cache))
|
||||
c.mu.Unlock()
|
||||
|
||||
c.cacheExpire()
|
||||
|
||||
c.mu.Lock()
|
||||
assert.Equal(t, 1, len(c.cache))
|
||||
entry.lastUsed = time.Now().Add(-c.expireDuration - 60*time.Second)
|
||||
assert.Equal(t, true, c.expireRunning)
|
||||
c.mu.Unlock()
|
||||
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
|
||||
c.mu.Lock()
|
||||
assert.Equal(t, false, c.expireRunning)
|
||||
assert.Equal(t, 0, len(c.cache))
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func TestCachePin(t *testing.T) {
|
||||
c, create := setup(t)
|
||||
|
||||
_, err := c.Get("/", create)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Pin a non existent item to show nothing happens
|
||||
c.Pin("notfound")
|
||||
|
||||
c.mu.Lock()
|
||||
entry := c.cache["/"]
|
||||
assert.Equal(t, 1, len(c.cache))
|
||||
c.mu.Unlock()
|
||||
|
||||
c.cacheExpire()
|
||||
|
||||
c.mu.Lock()
|
||||
assert.Equal(t, 1, len(c.cache))
|
||||
c.mu.Unlock()
|
||||
|
||||
// Pin the entry and check it does not get expired
|
||||
c.Pin("/")
|
||||
|
||||
// Reset last used to make the item expirable
|
||||
c.mu.Lock()
|
||||
entry.lastUsed = time.Now().Add(-c.expireDuration - 60*time.Second)
|
||||
c.mu.Unlock()
|
||||
|
||||
c.cacheExpire()
|
||||
|
||||
c.mu.Lock()
|
||||
assert.Equal(t, 1, len(c.cache))
|
||||
c.mu.Unlock()
|
||||
|
||||
// Unpin the entry and check it does get expired now
|
||||
c.Unpin("/")
|
||||
|
||||
// Reset last used
|
||||
c.mu.Lock()
|
||||
entry.lastUsed = time.Now().Add(-c.expireDuration - 60*time.Second)
|
||||
c.mu.Unlock()
|
||||
|
||||
c.cacheExpire()
|
||||
|
||||
c.mu.Lock()
|
||||
assert.Equal(t, 0, len(c.cache))
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func TestClear(t *testing.T) {
|
||||
c, create := setup(t)
|
||||
|
||||
@ -172,3 +224,38 @@ func TestGetMaybe(t *testing.T) {
|
||||
assert.Equal(t, false, found)
|
||||
assert.Nil(t, value)
|
||||
}
|
||||
|
||||
func TestCacheRename(t *testing.T) {
|
||||
c := New()
|
||||
create := func(path string) (interface{}, bool, error) {
|
||||
return path, true, nil
|
||||
}
|
||||
|
||||
existing1, err := c.Get("existing1", create)
|
||||
require.NoError(t, err)
|
||||
_, err = c.Get("existing2", create)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 2, c.Entries())
|
||||
|
||||
// rename to non existent
|
||||
value, found := c.Rename("existing1", "EXISTING1")
|
||||
assert.Equal(t, true, found)
|
||||
assert.Equal(t, existing1, value)
|
||||
|
||||
assert.Equal(t, 2, c.Entries())
|
||||
|
||||
// rename to existent and check existing value is returned
|
||||
value, found = c.Rename("existing2", "EXISTING1")
|
||||
assert.Equal(t, true, found)
|
||||
assert.Equal(t, existing1, value)
|
||||
|
||||
assert.Equal(t, 1, c.Entries())
|
||||
|
||||
// rename non existent
|
||||
value, found = c.Rename("notfound", "NOTFOUND")
|
||||
assert.Equal(t, false, found)
|
||||
assert.Nil(t, value)
|
||||
|
||||
assert.Equal(t, 1, c.Entries())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user