Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update roles cache to use sync.cache #1367

Merged
merged 1 commit into from
Jan 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation/formatting is broken here 😅

}
}

// 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