Skip to content

Commit

Permalink
update roles cache to use sync.cache
Browse files Browse the repository at this point in the history
add tests for roles cache
add changelog
use strings in sync cache tests
  • Loading branch information
fschade committed Jan 20, 2021
1 parent 03867f4 commit 03c1416
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 77 deletions.
7 changes: 7 additions & 0 deletions changelog/unreleased/roles-cache.md
Original file line number Diff line number Diff line change
@@ -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
56 changes: 10 additions & 46 deletions ocis-pkg/roles/cache.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,35 @@
package roles

import (
"sync"
"time"

"github.com/owncloud/ocis/ocis-pkg/sync"
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.
type cache struct {
entries map[string]entry
size int
ttl time.Duration
m sync.Mutex
sc sync.Cache
ttl time.Duration
}

// 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{
size: size,
ttl: ttl,
entries: map[string]entry{},
sc: sync.NewCache(capacity),
}
}

// get gets a role-bundle by a given `roleID`.
func (c *cache) get(roleID string) *settings.Bundle {
c.m.Lock()
defer c.m.Unlock()

if _, ok := c.entries[roleID]; ok {
return c.entries[roleID].Bundle
if ce := c.sc.Load(roleID); ce != nil {
return ce.V.(*settings.Bundle)
}

return nil
}

// set sets a roleID / role-bundle.
func (c *cache) set(roleID string, value *settings.Bundle) {
c.m.Lock()
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)
}
c.sc.Store(roleID, value, time.Now().Add(c.ttl))
}
54 changes: 54 additions & 0 deletions ocis-pkg/roles/cache_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
62 changes: 31 additions & 31 deletions ocis-pkg/sync/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import (
"time"
)

func cacheRunner(size int) (*Cache, func(f func(i int))) {
func cacheRunner(size int) (*Cache, func(f func(v string))) {
c := NewCache(size)
run := func(f func(i int)) {
run := func(f func(v string)) {
wg := sync.WaitGroup{}
for i := 0; i < size; i++ {
wg.Add(1)
go func(i int) {
f(i)
go func(v string) {
f(v)
wg.Done()
}(i)
}(strconv.Itoa(i))
}
wg.Wait()
}
Expand All @@ -30,21 +30,21 @@ func BenchmarkCache(b *testing.B) {
size := 1024
c, cr := cacheRunner(size)

cr(func(i int) { c.Store(strconv.Itoa(i), i, time.Now().Add(100*time.Millisecond)) })
cr(func(i int) { c.Delete(strconv.Itoa(i)) })
cr(func(v string) { c.Store(v, v, time.Now().Add(100*time.Millisecond)) })
cr(func(v string) { c.Delete(v) })
}

func TestCache(t *testing.T) {
size := 1024
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")

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")

cr(func(i int) {
cr(func(v string) {
time.Sleep(101 * time.Millisecond)
c.evict()
})
Expand All @@ -55,50 +55,50 @@ func TestCache_Load(t *testing.T) {
size := 1024
c, cr := cacheRunner(size)

cr(func(i int) {
c.Store(strconv.Itoa(i), i, time.Now().Add(10*time.Second))
cr(func(v string) {
c.Store(v, v, time.Now().Add(10*time.Second))
})

cr(func(i int) {
assert.Equal(t, i, c.Load(strconv.Itoa(i)).V, "entry value is the same")
cr(func(v string) {
assert.Equal(t, v, c.Load(v).V, "entry value is the same")
})

cr(func(i int) {
assert.Nil(t, c.Load(strconv.Itoa(i+size)), "entry is nil if unknown")
cr(func(v string) {
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
c.Store(strconv.Itoa(i), i, time.Now().Add(wait))
c.Store(v, v, time.Now().Add(wait))
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) {
c, cr := cacheRunner(1024)

cr(func(i int) {
c.Store(strconv.Itoa(i), i, time.Now().Add(100*time.Millisecond))
assert.Equal(t, i, c.Load(strconv.Itoa(i)).V, "new entries can be added")
cr(func(v string) {
c.Store(v, v, time.Now().Add(100*time.Millisecond))
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)
c.Store(strconv.Itoa(i), "old", time.Now().Add(10*time.Minute))
c.Store(strconv.Itoa(i), "updated", replacedExpiration)
assert.Equal(t, "updated", c.Load(strconv.Itoa(i)).V, "entry values can be updated")
assert.Equal(t, replacedExpiration, c.Load(strconv.Itoa(i)).expiration, "entry expiration can be updated")
c.Store(v, "old", time.Now().Add(10*time.Minute))
c.Store(v, "updated", replacedExpiration)
assert.Equal(t, "updated", c.Load(v).V, "entry values can be updated")
assert.Equal(t, replacedExpiration, c.Load(v).expiration, "entry expiration can be updated")
})
}

func TestCache_Delete(t *testing.T) {
c, cr := cacheRunner(1024)

cr(func(i int) {
c.Store(strconv.Itoa(i), i, time.Now().Add(100*time.Millisecond))
c.Delete(strconv.Itoa(i))
assert.Nil(t, c.Load(strconv.Itoa(i)), "entries can be deleted")
cr(func(v string) {
c.Store(v, v, time.Now().Add(100*time.Millisecond))
c.Delete(v)
assert.Nil(t, c.Load(v), "entries can be deleted")
})

assert.Equal(t, 0, int(c.length), "removing a entry decreases the cache size")
Expand Down

0 comments on commit 03c1416

Please sign in to comment.