Skip to content

Commit

Permalink
Add remove reason signal to OnRemove callback
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacob Shufro committed Apr 5, 2018
1 parent e3e2429 commit 92adeca
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 21 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -46,9 +46,9 @@ config := bigcache.Config {
// if value is reached then the oldest entries can be overridden for the new ones
// 0 value means no size limit
HardMaxCacheSize: 8192,
// callback fired when the oldest entry is removed because of its
// expiration time or no space left for the new entry. Default value is nil which
// means no callback and it prevents from unwrapping the oldest entry.
// callback fired when the oldest entry is removed because of its expiration time or no space left
// for the new entry, or because delete was called. A bitmask representing the reason will be returned.
// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
OnRemove: nil,
}

Expand Down
27 changes: 21 additions & 6 deletions bigcache.go
Expand Up @@ -22,6 +22,19 @@ type BigCache struct {
maxShardSize uint32
}

// RemoveReason is a value used to signal to the user why a particular key was removed in the OnRemove callback.
type RemoveReason int32

const (
// Expired means the key is past its LifeWindow.
Expired = 1 << iota
// NoSpace means the key is the oldest and the cache size was at its maximum when Set was called, or the
// entry exceeded the maximum shard size.
NoSpace = 1 << iota
// Deleted means Delete was called and this key was removed as a result.
Deleted = 1 << iota
)

// NewBigCache initialize new instance of BigCache
func NewBigCache(config Config) (*BigCache, error) {
return newBigCache(config, &systemClock{})
Expand All @@ -47,7 +60,7 @@ func newBigCache(config Config, clock clock) (*BigCache, error) {
maxShardSize: uint32(config.maximumShardSize()),
}

var onRemove func(wrappedEntry []byte)
var onRemove func(wrappedEntry []byte, reason RemoveReason)
if config.OnRemove == nil {
onRemove = cache.notProvidedOnRemove
} else {
Expand Down Expand Up @@ -128,10 +141,10 @@ func (c *BigCache) Iterator() *EntryInfoIterator {
return newIterator(c)
}

func (c *BigCache) onEvict(oldestEntry []byte, currentTimestamp uint64, evict func() error) bool {
func (c *BigCache) onEvict(oldestEntry []byte, currentTimestamp uint64, evict func(reason RemoveReason) error) bool {
oldestTimestamp := readTimestampFromEntry(oldestEntry)
if currentTimestamp-oldestTimestamp > c.lifeWindow {
evict()
evict(Expired)
return true
}
return false
Expand All @@ -147,9 +160,11 @@ func (c *BigCache) getShard(hashedKey uint64) (shard *cacheShard) {
return c.shards[hashedKey&c.shardMask]
}

func (c *BigCache) providedOnRemove(wrappedEntry []byte) {
c.config.OnRemove(readKeyFromEntry(wrappedEntry), readEntry(wrappedEntry))
func (c *BigCache) providedOnRemove(wrappedEntry []byte, reason RemoveReason) {
if c.config.OnRemoveFilter == 0 || int(reason)&c.config.OnRemoveFilter > 0 {
c.config.OnRemove(readKeyFromEntry(wrappedEntry), readEntry(wrappedEntry), reason)
}
}

func (c *BigCache) notProvidedOnRemove(wrappedEntry []byte) {
func (c *BigCache) notProvidedOnRemove(wrappedEntry []byte, reason RemoveReason) {
}
36 changes: 35 additions & 1 deletion bigcache_test.go
Expand Up @@ -181,10 +181,11 @@ func TestOnRemoveCallback(t *testing.T) {
// given
clock := mockedClock{value: 0}
onRemoveInvoked := false
onRemove := func(key string, entry []byte) {
onRemove := func(key string, entry []byte, reason RemoveReason) {
onRemoveInvoked = true
assert.Equal(t, "key", key)
assert.Equal(t, []byte("value"), entry)
assert.Equal(t, reason, RemoveReason(Expired))
}
cache, _ := newBigCache(Config{
Shards: 1,
Expand All @@ -203,6 +204,39 @@ func TestOnRemoveCallback(t *testing.T) {
assert.True(t, onRemoveInvoked)
}

func TestOnRemoveFilter(t *testing.T) {
t.Parallel()

// given
clock := mockedClock{value: 0}
onRemoveInvoked := false
onRemove := func(key string, entry []byte, reason RemoveReason) {
onRemoveInvoked = true
}
cache, _ := newBigCache(Config{
Shards: 1,
LifeWindow: time.Second,
MaxEntriesInWindow: 1,
MaxEntrySize: 256,
OnRemove: onRemove,
OnRemoveFilter: Deleted | NoSpace,
}, &clock)

// when
cache.Set("key", []byte("value"))
clock.set(5)
cache.Set("key2", []byte("value2"))

// then
assert.False(t, onRemoveInvoked)

// and when
cache.Delete("key2")

// then
assert.True(t, onRemoveInvoked)
}

func TestCacheLen(t *testing.T) {
t.Parallel()

Expand Down
10 changes: 8 additions & 2 deletions config.go
Expand Up @@ -26,8 +26,14 @@ type Config struct {
// the oldest entries are overridden for the new ones.
HardMaxCacheSize int
// OnRemove is a callback fired when the oldest entry is removed because of its expiration time or no space left
// for the new entry. Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
OnRemove func(key string, entry []byte)
// for the new entry, or because delete was called. A bitmask representing the reason will be returned.
// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
OnRemove func(key string, entry []byte, reason RemoveReason)
// OnRemoveFilter is a value the signals to the cache which removals should invoke OnRemove.
// If unspecified, all removals will be reported. If a user only cares about space evictions and
// life expirations, for example, setting this to "Expiration | NoSpace" will prevent unwrapping on
// other removals (Delete calls).
OnRemoveFilter int

// Logger is a logging interface and used in combination with `Verbose`
// Defaults to `DefaultLogger()`
Expand Down
18 changes: 9 additions & 9 deletions shard.go
Expand Up @@ -8,12 +8,14 @@ import (
"github.com/allegro/bigcache/queue"
)

type onRemoveCallback func(wrappedEntry []byte, reason RemoveReason)

type cacheShard struct {
hashmap map[uint64]uint32
entries queue.BytesQueue
lock sync.RWMutex
entryBuffer []byte
onRemove func(wrappedEntry []byte)
onRemove onRemoveCallback

isVerbose bool
logger Logger
Expand All @@ -23,8 +25,6 @@ type cacheShard struct {
stats Stats
}

type onRemoveCallback func(wrappedEntry []byte)

func (s *cacheShard) get(key string, hashedKey uint64) ([]byte, error) {
s.lock.RLock()
itemIndex := s.hashmap[hashedKey]
Expand Down Expand Up @@ -77,7 +77,7 @@ func (s *cacheShard) set(key string, hashedKey uint64, entry []byte) error {
s.lock.Unlock()
return nil
}
if s.removeOldestEntry() != nil {
if s.removeOldestEntry(NoSpace) != nil {
s.lock.Unlock()
return fmt.Errorf("entry is bigger than max shard size")
}
Expand Down Expand Up @@ -105,7 +105,7 @@ func (s *cacheShard) del(key string, hashedKey uint64) error {
s.lock.Lock()
{
delete(s.hashmap, hashedKey)
s.onRemove(wrappedEntry)
s.onRemove(wrappedEntry, Deleted)
resetKeyFromEntry(wrappedEntry)
}
s.lock.Unlock()
Expand All @@ -114,10 +114,10 @@ func (s *cacheShard) del(key string, hashedKey uint64) error {
return nil
}

func (s *cacheShard) onEvict(oldestEntry []byte, currentTimestamp uint64, evict func() error) bool {
func (s *cacheShard) onEvict(oldestEntry []byte, currentTimestamp uint64, evict func(reason RemoveReason) error) bool {
oldestTimestamp := readTimestampFromEntry(oldestEntry)
if currentTimestamp-oldestTimestamp > s.lifeWindow {
evict()
evict(Expired)
return true
}
return false
Expand Down Expand Up @@ -157,12 +157,12 @@ func (s *cacheShard) copyKeys() (keys []uint32, next int) {
return keys, next
}

func (s *cacheShard) removeOldestEntry() error {
func (s *cacheShard) removeOldestEntry(reason RemoveReason) error {
oldest, err := s.entries.Pop()
if err == nil {
hash := readHashFromEntry(oldest)
delete(s.hashmap, hash)
s.onRemove(oldest)
s.onRemove(oldest, reason)
return nil
}
return err
Expand Down

0 comments on commit 92adeca

Please sign in to comment.