diff --git a/bigcache.go b/bigcache.go index c2a92c15..11b27fec 100644 --- a/bigcache.go +++ b/bigcache.go @@ -33,6 +33,16 @@ type cacheShard struct { onRemove func(wrappedEntry []byte) } +// EntryInfo holds informations about entry in the cache +type EntryInfo struct { + Key string + Hash uint64 + Timestamp uint64 +} + +// KeysIterator allows to iterate over entries in the cache +type KeysIterator chan EntryInfo + // NewBigCache initialize new instance of BigCache func NewBigCache(config Config) (*BigCache, error) { return newBigCache(config, &systemClock{}) @@ -144,6 +154,38 @@ func (c *BigCache) Set(key string, entry []byte) error { } } +// Keys returns channel to iterate over EntryInfo's from whole cache +func (c *BigCache) Keys() KeysIterator { + ch := make(KeysIterator, 1024) + var wg sync.WaitGroup + wg.Add(c.config.Shards) + + for i := 0; i < c.config.Shards; i++ { + go func(shard *cacheShard) { + defer wg.Done() + shard.lock.RLock() + defer shard.lock.RUnlock() + + for _, index := range shard.hashmap { + if entry, err := shard.entries.Get(int(index)); err == nil { + ch <- EntryInfo{ + Key: readKeyFromEntry(entry), + Hash: readHashFromEntry(entry), + Timestamp: readTimestampFromEntry(entry), + } + } + } + }(c.shards[i]) + } + + go func() { + wg.Wait() + close(ch) + }() + + return ch +} + func (c *BigCache) onEvict(oldestEntry []byte, currentTimestamp uint64, evict func() error) { oldestTimestamp := readTimestampFromEntry(oldestEntry) if currentTimestamp-oldestTimestamp > c.lifeWindow { diff --git a/bigcache_test.go b/bigcache_test.go index 9faa8e47..76b62bca 100644 --- a/bigcache_test.go +++ b/bigcache_test.go @@ -1,6 +1,8 @@ package bigcache import ( + "fmt" + "sync" "testing" "time" @@ -113,6 +115,48 @@ func TestEntryUpdate(t *testing.T) { assert.Equal(t, []byte("value2"), cachedValue) } +func TestKeysIterator(t *testing.T) { + //t.Parallel() + + // given + keysCount := 10000 + cache, _ := NewBigCache(Config{8, 6 * time.Second, 1, 256, false, nil, 0, nil}) + value := []byte("value") + var wg sync.WaitGroup + wg.Add(1) + + for i := 0; i < keysCount; i++ { + cache.Set(fmt.Sprintf("key%d", i), value) + } + + // when + ch := cache.Keys() + keys := make(map[string]struct{}) + + go func() { + loop: + for { + select { + case entryInfo, opened := <-ch: + if !opened { + break loop + } + + keys[entryInfo.Key] = struct{}{} + + case <-time.After(time.Second * 1): + break loop + } + } + + wg.Done() + }() + + // then + wg.Wait() + assert.Equal(t, keysCount, len(keys)) +} + func TestOldestEntryDeletionWhenMaxCacheSizeIsReached(t *testing.T) { t.Parallel()