update roles cache to use sync.cache

add tests for roles cache
add changelog
use strings in sync cache tests
This commit is contained in:
Florian Schade
2021-01-21 00:04:25 +01:00
parent 03867f4230
commit 03c1416a4a
4 changed files with 102 additions and 77 deletions
+7
View File
@@ -0,0 +1,7 @@
Enhancement: use sync.cache for roles cache
Tags: ocis-pkg
update ocis-pkg/roles cache to use ocis-pkg/sync cache
https://github.com/owncloud/ocis/pull/1367
+9 -45
View File
@@ -1,71 +1,35 @@
package roles package roles
import ( import (
"sync"
"time" "time"
"github.com/owncloud/ocis/ocis-pkg/sync"
settings "github.com/owncloud/ocis/settings/pkg/proto/v0" settings "github.com/owncloud/ocis/settings/pkg/proto/v0"
) )
// entry extends a bundle and adds a TTL
type entry struct {
*settings.Bundle
inserted time.Time
}
// cache is a cache implementation for roles, keyed by roleIDs. // cache is a cache implementation for roles, keyed by roleIDs.
type cache struct { type cache struct {
entries map[string]entry sc sync.Cache
size int ttl time.Duration
ttl time.Duration
m sync.Mutex
} }
// newCache returns a new instance of Cache. // newCache returns a new instance of Cache.
func newCache(size int, ttl time.Duration) cache { func newCache(capacity int, ttl time.Duration) cache {
return cache{ return cache{
size: size,
ttl: ttl, ttl: ttl,
entries: map[string]entry{}, sc: sync.NewCache(capacity),
} }
} }
// get gets a role-bundle by a given `roleID`. // get gets a role-bundle by a given `roleID`.
func (c *cache) get(roleID string) *settings.Bundle { func (c *cache) get(roleID string) *settings.Bundle {
c.m.Lock() if ce := c.sc.Load(roleID); ce != nil {
defer c.m.Unlock() return ce.V.(*settings.Bundle)
if _, ok := c.entries[roleID]; ok {
return c.entries[roleID].Bundle
} }
return nil return nil
} }
// set sets a roleID / role-bundle. // set sets a roleID / role-bundle.
func (c *cache) set(roleID string, value *settings.Bundle) { func (c *cache) set(roleID string, value *settings.Bundle) {
c.m.Lock() c.sc.Store(roleID, value, time.Now().Add(c.ttl))
defer c.m.Unlock()
if !c.fits() {
c.evict()
}
c.entries[roleID] = entry{
value,
time.Now(),
}
}
// evict frees memory from the cache by removing entries that exceeded the cache TTL.
func (c *cache) evict() {
for i := range c.entries {
if c.entries[i].inserted.Add(c.ttl).Before(time.Now()) {
delete(c.entries, i)
}
}
}
// fits returns whether the cache fits more entries.
func (c *cache) fits() bool {
return c.size > len(c.entries)
} }
+54
View File
@@ -0,0 +1,54 @@
package roles
import (
settings "github.com/owncloud/ocis/settings/pkg/proto/v0"
"github.com/stretchr/testify/assert"
"strconv"
"sync"
"testing"
"time"
)
func cacheRunner(size int, ttl time.Duration) (*cache, func(f func(v string))) {
c := newCache(size, ttl)
run := func(f func(v string)) {
wg := sync.WaitGroup{}
for i := 0; i < size; i++ {
wg.Add(1)
go func(i int) {
f(strconv.Itoa(i))
wg.Done()
}(i)
}
wg.Wait()
}
return &c, run
}
func BenchmarkCache(b *testing.B) {
b.ReportAllocs()
size := 1024
c, cr := cacheRunner(size, 100 * time.Millisecond)
cr(func(v string) { c.set(v, &settings.Bundle{})})
cr(func(v string) { c.get(v)})
}
func TestCache(t *testing.T) {
size := 1024
ttl := 100 * time.Millisecond
c, cr := cacheRunner(size, ttl)
cr(func(v string) {
c.set(v, &settings.Bundle{Id: v})
})
assert.Equal(t, "50", c.get("50").Id, "it returns the right bundle")
assert.Nil(t, c.get("unknown"), "unknown bundle ist nil")
time.Sleep(ttl + 1)
// roles cache has no access to evict, adding new items triggers a cleanup
c.set("evict", nil)
assert.Nil(t, c.get("50"), "old bundles get removed")
}
+31 -31
View File
@@ -8,16 +8,16 @@ import (
"time" "time"
) )
func cacheRunner(size int) (*Cache, func(f func(i int))) { func cacheRunner(size int) (*Cache, func(f func(v string))) {
c := NewCache(size) c := NewCache(size)
run := func(f func(i int)) { run := func(f func(v string)) {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
wg.Add(1) wg.Add(1)
go func(i int) { go func(v string) {
f(i) f(v)
wg.Done() wg.Done()
}(i) }(strconv.Itoa(i))
} }
wg.Wait() wg.Wait()
} }
@@ -30,21 +30,21 @@ func BenchmarkCache(b *testing.B) {
size := 1024 size := 1024
c, cr := cacheRunner(size) c, cr := cacheRunner(size)
cr(func(i int) { c.Store(strconv.Itoa(i), i, time.Now().Add(100*time.Millisecond)) }) cr(func(v string) { c.Store(v, v, time.Now().Add(100*time.Millisecond)) })
cr(func(i int) { c.Delete(strconv.Itoa(i)) }) cr(func(v string) { c.Delete(v) })
} }
func TestCache(t *testing.T) { func TestCache(t *testing.T) {
size := 1024 size := 1024
c, cr := cacheRunner(size) c, cr := cacheRunner(size)
cr(func(i int) { c.Store(strconv.Itoa(i), i, time.Now().Add(100*time.Millisecond)) }) cr(func(v string) { c.Store(v, v, time.Now().Add(100*time.Millisecond)) })
assert.Equal(t, size, int(c.length), "length is atomic") assert.Equal(t, size, int(c.length), "length is atomic")
cr(func(i int) { c.Delete(strconv.Itoa(i)) }) cr(func(v string) { c.Delete(v) })
assert.Equal(t, 0, int(c.length), "delete is atomic") assert.Equal(t, 0, int(c.length), "delete is atomic")
cr(func(i int) { cr(func(v string) {
time.Sleep(101 * time.Millisecond) time.Sleep(101 * time.Millisecond)
c.evict() c.evict()
}) })
@@ -55,50 +55,50 @@ func TestCache_Load(t *testing.T) {
size := 1024 size := 1024
c, cr := cacheRunner(size) c, cr := cacheRunner(size)
cr(func(i int) { cr(func(v string) {
c.Store(strconv.Itoa(i), i, time.Now().Add(10*time.Second)) c.Store(v, v, time.Now().Add(10*time.Second))
}) })
cr(func(i int) { cr(func(v string) {
assert.Equal(t, i, c.Load(strconv.Itoa(i)).V, "entry value is the same") assert.Equal(t, v, c.Load(v).V, "entry value is the same")
}) })
cr(func(i int) { cr(func(v string) {
assert.Nil(t, c.Load(strconv.Itoa(i+size)), "entry is nil if unknown") assert.Nil(t, c.Load(v+strconv.Itoa(size)), "entry is nil if unknown")
}) })
cr(func(i int) { cr(func(v string) {
wait := 100 * time.Millisecond wait := 100 * time.Millisecond
c.Store(strconv.Itoa(i), i, time.Now().Add(wait)) c.Store(v, v, time.Now().Add(wait))
time.Sleep(wait + 1) time.Sleep(wait + 1)
assert.Nil(t, c.Load(strconv.Itoa(i)), "entry is nil if it's expired") assert.Nil(t, c.Load(v), "entry is nil if it's expired")
}) })
} }
func TestCache_Store(t *testing.T) { func TestCache_Store(t *testing.T) {
c, cr := cacheRunner(1024) c, cr := cacheRunner(1024)
cr(func(i int) { cr(func(v string) {
c.Store(strconv.Itoa(i), i, time.Now().Add(100*time.Millisecond)) c.Store(v, v, time.Now().Add(100*time.Millisecond))
assert.Equal(t, i, c.Load(strconv.Itoa(i)).V, "new entries can be added") assert.Equal(t, v, c.Load(v).V, "new entries can be added")
}) })
cr(func(i int) { cr(func(v string) {
replacedExpiration := time.Now().Add(10 * time.Minute) replacedExpiration := time.Now().Add(10 * time.Minute)
c.Store(strconv.Itoa(i), "old", time.Now().Add(10*time.Minute)) c.Store(v, "old", time.Now().Add(10*time.Minute))
c.Store(strconv.Itoa(i), "updated", replacedExpiration) c.Store(v, "updated", replacedExpiration)
assert.Equal(t, "updated", c.Load(strconv.Itoa(i)).V, "entry values can be updated") assert.Equal(t, "updated", c.Load(v).V, "entry values can be updated")
assert.Equal(t, replacedExpiration, c.Load(strconv.Itoa(i)).expiration, "entry expiration can be updated") assert.Equal(t, replacedExpiration, c.Load(v).expiration, "entry expiration can be updated")
}) })
} }
func TestCache_Delete(t *testing.T) { func TestCache_Delete(t *testing.T) {
c, cr := cacheRunner(1024) c, cr := cacheRunner(1024)
cr(func(i int) { cr(func(v string) {
c.Store(strconv.Itoa(i), i, time.Now().Add(100*time.Millisecond)) c.Store(v, v, time.Now().Add(100*time.Millisecond))
c.Delete(strconv.Itoa(i)) c.Delete(v)
assert.Nil(t, c.Load(strconv.Itoa(i)), "entries can be deleted") assert.Nil(t, c.Load(v), "entries can be deleted")
}) })
assert.Equal(t, 0, int(c.length), "removing a entry decreases the cache size") assert.Equal(t, 0, int(c.length), "removing a entry decreases the cache size")