Skip to content

Commit

Permalink
Merge d0eab91 into 2d6fab2
Browse files Browse the repository at this point in the history
  • Loading branch information
jgheewala committed Sep 26, 2019
2 parents 2d6fab2 + d0eab91 commit ad141c7
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 18 deletions.
14 changes: 13 additions & 1 deletion bigcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ type BigCache struct {
close chan struct{}
}

// Response will contain metadata about the entry for which GetWithInfo(key) was called
type Response struct {
EntryStatus RemoveReason
}

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

const (
// @TODO: GO defaults to 0 so in case we want to return EntryStatus back to the caller Expired cannot be differentiated
// Expired means the key is past its LifeWindow.
Expired RemoveReason = iota
// NoSpace means the key is the oldest and the cache size was at its maximum when Set was called, or the
Expand Down Expand Up @@ -110,6 +116,12 @@ func (c *BigCache) Get(key string) ([]byte, error) {
return shard.get(key, hashedKey)
}

func (c *BigCache) GetWithInfo(key string) ([]byte, Response, error) {
hashedKey := c.hash.Sum64(key)
shard := c.getShard(hashedKey)
return shard.getWithInfo(key, hashedKey)
}

// Set saves entry under the key
func (c *BigCache) Set(key string, entry []byte) error {
hashedKey := c.hash.Sum64(key)
Expand All @@ -121,7 +133,7 @@ func (c *BigCache) Set(key string, entry []byte) error {
func (c *BigCache) Delete(key string) error {
hashedKey := c.hash.Sum64(key)
shard := c.getShard(hashedKey)
return shard.del(key, hashedKey)
return shard.del(hashedKey)
}

// Reset empties all cache shards
Expand Down
17 changes: 14 additions & 3 deletions bigcache_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,18 @@ func BenchmarkWriteToCache(b *testing.B) {
func BenchmarkReadFromCache(b *testing.B) {
for _, shards := range []int{1, 512, 1024, 8192} {
b.Run(fmt.Sprintf("%d-shards", shards), func(b *testing.B) {
readFromCache(b, 1024)
readFromCache(b, 1024, false)
})
}
}

func BenchmarkReadFromCacheWithInfo(b *testing.B) {
for _, shards := range []int{1, 512, 1024, 8192} {
b.Run(fmt.Sprintf("%d-shards", shards), func(b *testing.B) {
readFromCache(b, 1024, true)
})
}
}
func BenchmarkIterateOverCache(b *testing.B) {

m := blob('a', 1)
Expand Down Expand Up @@ -127,7 +134,7 @@ func writeToCache(b *testing.B, shards int, lifeWindow time.Duration, requestsIn
})
}

func readFromCache(b *testing.B, shards int) {
func readFromCache(b *testing.B, shards int, info bool) {
cache, _ := NewBigCache(Config{
Shards: shards,
LifeWindow: 1000 * time.Second,
Expand All @@ -143,7 +150,11 @@ func readFromCache(b *testing.B, shards int) {
b.ReportAllocs()

for pb.Next() {
cache.Get(strconv.Itoa(rand.Intn(b.N)))
if info {
cache.GetWithInfo(strconv.Itoa(rand.Intn(b.N)))
} else {
cache.Get(strconv.Itoa(rand.Intn(b.N)))
}
}
})
}
Expand Down
68 changes: 68 additions & 0 deletions bigcache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,28 @@ func TestCacheLen(t *testing.T) {
assert.Equal(t, keys, cache.Len())
}

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

// given
cache, _ := NewBigCache(Config{
Shards: 8,
LifeWindow: time.Second,
MaxEntriesInWindow: 1,
MaxEntrySize: 256,
})
keys := 1337

// when
for i := 0; i < keys; i++ {
cache.Set(fmt.Sprintf("key%d", i), []byte("value"))
}

// then
assert.Equal(t, keys, cache.Len())
assert.Equal(t, 81920, cache.Capacity())
}

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

Expand Down Expand Up @@ -703,6 +725,52 @@ func TestClosing(t *testing.T) {
assert.InDelta(t, endGR, startGR, 25)
}

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

// given
clock := mockedClock{value: 0}
cache, _ := newBigCache(Config{
Shards: 1,
LifeWindow: 5 * time.Second,
MaxEntriesInWindow: 1,
MaxEntrySize: 1,
HardMaxCacheSize: 1,
}, &clock)

// when
value, resp, err := cache.GetWithInfo("blah")
assert.Error(t, err)
assert.Equal(t, resp.EntryStatus, RemoveReason(0))
assert.Equal(t, cache.Stats().Misses, int64(1))
assert.Nil(t, value)
}

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

// given
clock := mockedClock{value: 0}
cache, _ := newBigCache(Config{
Shards: 1,
LifeWindow: 2 * time.Second,
CleanWindow: 5 * time.Minute,
MaxEntriesInWindow: 1,
MaxEntrySize: 1,
HardMaxCacheSize: 1,
}, &clock)
key := "deadEntryKey"
value := "100"
cache.Set(key, []byte(value))

// when
clock.set(5)
data, resp, err := cache.GetWithInfo(key)
assert.Equal(t, err, ErrEntryIsDead)
assert.Equal(t, Response{EntryStatus: Expired}, resp)
assert.Nil(t, data)
}

type mockedLogger struct {
lastFormat string
lastArgs []interface{}
Expand Down
8 changes: 6 additions & 2 deletions entry_not_found_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@ package bigcache

import "errors"

// ErrEntryNotFound is an error type struct which is returned when entry was not found for provided key
var ErrEntryNotFound = errors.New("Entry not found")
var (
// ErrEntryNotFound is an error type struct which is returned when entry was not found for provided key
ErrEntryNotFound = errors.New("Entry not found")
// ErrEntryIsDead is an error type struct which is returned when entry has past it's life window
ErrEntryIsDead = errors.New("entry is dead")
)
65 changes: 53 additions & 12 deletions shard.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type cacheShard struct {
stats Stats
}

func (s *cacheShard) get(key string, hashedKey uint64) ([]byte, error) {
func (s *cacheShard) getWrappedEntry(key string, hashedKey uint64) ([]byte, error) {
s.lock.RLock()
itemIndex := s.hashmap[hashedKey]

Expand All @@ -36,23 +36,64 @@ func (s *cacheShard) get(key string, hashedKey uint64) ([]byte, error) {
}

wrappedEntry, err := s.entries.Get(int(itemIndex))
s.lock.RUnlock()
if err != nil {
s.lock.RUnlock()
s.miss()
return nil, err
}
if entryKey := readKeyFromEntry(wrappedEntry); key != entryKey {
if s.isVerbose {
s.logger.Printf("Collision detected. Both %q and %q have the same hash %x", key, entryKey, hashedKey)

return wrappedEntry, err
}

func (s *cacheShard) getWithInfo(key string, hashedKey uint64) (entry []byte, resp Response, err error) {
currentTime := uint64(s.clock.epoch())
wrappedEntry, err := s.getWrappedEntry(key, hashedKey)
if err == nil {
s.lock.RLock()
if entryKey := readKeyFromEntry(wrappedEntry); key != entryKey {
if s.isVerbose {
s.logger.Printf("Collision detected. Both %q and %q have the same hash %x", key, entryKey, hashedKey)
}
s.lock.RUnlock()
s.collision()
return entry, resp, ErrEntryNotFound
}

oldestTimeStamp := readTimestampFromEntry(wrappedEntry)
if currentTime-oldestTimeStamp >= s.lifeWindow {
s.lock.RUnlock()
// @TODO: when Expired is non-default value return err as nil as the resp will have proper entry status
resp.EntryStatus = Expired
return entry, resp, ErrEntryIsDead
}
entry := readEntry(wrappedEntry)
s.lock.RUnlock()
s.hit()
return entry, resp, nil
}
// it is nil & error
return wrappedEntry, resp, err
}

func (s *cacheShard) get(key string, hashedKey uint64) ([]byte, error) {
wrappedEntry, err := s.getWrappedEntry(key, hashedKey)
if err == nil {
s.lock.RLock()
if entryKey := readKeyFromEntry(wrappedEntry); key != entryKey {
if s.isVerbose {
s.logger.Printf("Collision detected. Both %q and %q have the same hash %x", key, entryKey, hashedKey)
}
s.lock.RUnlock()
s.collision()
return nil, ErrEntryNotFound
}
entry := readEntry(wrappedEntry)
s.lock.RUnlock()
s.collision()
return nil, ErrEntryNotFound
s.hit()
return entry, nil
}
entry := readEntry(wrappedEntry)
s.lock.RUnlock()
s.hit()
return entry, nil
// it is nil & error
return wrappedEntry, err
}

func (s *cacheShard) set(key string, hashedKey uint64, entry []byte) error {
Expand Down Expand Up @@ -85,7 +126,7 @@ func (s *cacheShard) set(key string, hashedKey uint64, entry []byte) error {
}
}

func (s *cacheShard) del(key string, hashedKey uint64) error {
func (s *cacheShard) del(hashedKey uint64) error {
// Optimistic pre-check using only readlock
s.lock.RLock()
itemIndex := s.hashmap[hashedKey]
Expand Down

0 comments on commit ad141c7

Please sign in to comment.