-
Notifications
You must be signed in to change notification settings - Fork 194
Support Iterator for fastcache #44
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -211,6 +211,11 @@ func (c *Cache) UpdateStats(s *Stats) { | |
| s.InvalidValueHashErrors += atomic.LoadUint64(&c.bigStats.InvalidValueHashErrors) | ||
| } | ||
|
|
||
| // Iterator returns an fastcache iterator to iterate over the entire cache. | ||
| func (c *Cache) Iterator() *Iterator { | ||
| return newIterator(c) | ||
| } | ||
|
|
||
| type bucket struct { | ||
| mu sync.RWMutex | ||
|
|
||
|
|
@@ -413,3 +418,30 @@ func (b *bucket) Del(h uint64) { | |
| delete(b.m, h) | ||
| b.mu.Unlock() | ||
| } | ||
|
|
||
| func (b *bucket) copyKeys() ([][]byte, int) { | ||
| b.mu.RLock() | ||
|
|
||
| keys := make([][]byte, len(b.m)) | ||
| numKeys := 0 | ||
|
|
||
| for _, v := range b.m { | ||
| idx := v & ((1 << bucketSizeBits) - 1) | ||
| gen := v >> bucketSizeBits | ||
|
|
||
| if gen == b.gen && idx < b.idx || gen+1 == b.gen && idx >= b.idx { | ||
| chunkIdx := idx / chunkSize | ||
| chunk := b.chunks[chunkIdx] | ||
|
|
||
| kvLenBuf := chunk[idx : idx+4] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may cause the array to go out of bounds and generate Panic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding this line of code may solve the problem, can you help me take a look? idx = idx % chunkSize There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think FYI, here is the panic log: |
||
| keyLen := (uint64(kvLenBuf[0]) << 8) | uint64(kvLenBuf[1]) | ||
|
|
||
| idx += 4 | ||
| key := chunk[idx : idx+keyLen : idx+keyLen] | ||
| keys[numKeys] = key | ||
| numKeys++ | ||
| } | ||
| } | ||
| b.mu.RUnlock() | ||
| return keys, numKeys | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| package fastcache | ||
|
|
||
| import ( | ||
| "sync" | ||
| ) | ||
|
|
||
| type iteratorError string | ||
|
|
||
| func (e iteratorError) Error() string { | ||
| return string(e) | ||
| } | ||
|
|
||
| // ErrIterationFinished is reported when Value() is called after reached to the end of the iterator | ||
| const ErrIterationFinished = iteratorError("iterator reached the last element, Value() should not be called") | ||
|
|
||
| var emptyEntry = Entry{} | ||
|
|
||
| // Entry represents a key-value pair in fastcache | ||
| type Entry struct { | ||
| key []byte | ||
| value []byte | ||
| } | ||
|
|
||
| // Key returns entry's key | ||
| func (e Entry) Key() []byte { | ||
| return e.key | ||
| } | ||
|
|
||
| // Value returns entry's value | ||
| func (e Entry) Value() []byte { | ||
| return e.value | ||
| } | ||
|
|
||
| func newIterator(c *Cache) *Iterator { | ||
| elements, count := c.buckets[0].copyKeys() | ||
|
|
||
| return &Iterator{ | ||
| cache: c, | ||
| currBucketIdx: 0, | ||
| currKeyIdx: -1, | ||
| currBucketKeys: elements, | ||
| currBucketSize: count, | ||
| } | ||
| } | ||
|
|
||
| // Iterator allows to iterate over entries in the cache | ||
| type Iterator struct { | ||
| mu sync.Mutex | ||
| cache *Cache | ||
| currBucketSize int | ||
| currBucketIdx int | ||
| currBucketKeys [][]byte | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider storing This removes the necessity of copying all the keys from the bucket. |
||
| currKeyIdx int | ||
| currentEntryInfo Entry | ||
|
|
||
| valid bool | ||
| } | ||
|
|
||
| // SetNext moves to the next element and returns true if the value exists. | ||
| func (it *Iterator) SetNext() bool { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently |
||
| it.mu.Lock() | ||
|
|
||
| it.valid = false | ||
| it.currKeyIdx++ | ||
|
|
||
| // In case there are remaining currBucketKeys in the current bucket. | ||
| if it.currBucketSize > it.currKeyIdx { | ||
| it.valid = true | ||
| found := it.setCurrentEntry() | ||
| it.mu.Unlock() | ||
|
|
||
| // if not found, check the next entry | ||
| if !found { | ||
| return it.SetNext() | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| // If we reached the end of a bucket, check the next one for further iteration. | ||
| for i := it.currBucketIdx + 1; i < len(it.cache.buckets); i++ { | ||
| it.currBucketKeys, it.currBucketSize = it.cache.buckets[i].copyKeys() | ||
|
|
||
| // bucket is not an empty one, use it for iteration | ||
| if it.currBucketSize > 0 { | ||
| it.currKeyIdx = 0 | ||
| it.currBucketIdx = i | ||
| it.valid = true | ||
| found := it.setCurrentEntry() | ||
| it.mu.Unlock() | ||
|
|
||
| // if not found, check the next entry | ||
| if !found { | ||
| return it.SetNext() | ||
| } | ||
| return true | ||
| } | ||
| } | ||
| it.mu.Unlock() | ||
| return false | ||
| } | ||
|
|
||
| func (it *Iterator) setCurrentEntry() bool { | ||
| key := it.currBucketKeys[it.currKeyIdx] | ||
| val, found := it.cache.HasGet(nil, key) | ||
|
|
||
| if found { | ||
| it.currentEntryInfo = Entry{ | ||
| key: key, | ||
| value: val, | ||
| } | ||
| } else { | ||
| it.currentEntryInfo = emptyEntry | ||
| } | ||
|
|
||
| return found | ||
| } | ||
|
|
||
| // Value returns the current entry of an iterator. | ||
| func (it *Iterator) Value() (Entry, error) { | ||
| if !it.valid { | ||
| return emptyEntry, ErrIterationFinished | ||
| } | ||
|
|
||
| return it.currentEntryInfo, nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the condition should be:
gen == maxGen && bGen == 1 && idx >= b.idxis the situation where maxGen is reached on the last wrap and the current generation is reset to 1.