Skip to content

Commit cc54edb

Browse files
authored
Merge pull request #44 from sei-protocol/PebbleDBIteratorFix
[SeiDB] Fix PebbleDB Iterator Issue
2 parents 4c82dd2 + afd3a9b commit cc54edb

File tree

3 files changed

+81
-11
lines changed

3 files changed

+81
-11
lines changed

ss/pebbledb/comparator.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77

88
"github.com/cockroachdb/pebble"
9+
"github.com/sei-protocol/sei-db/common/utils"
910
)
1011

1112
// MVCCComparer returns a PebbleDB Comparer with encoding and decoding routines
@@ -137,20 +138,25 @@ func (f mvccKeyFormatter) Format(s fmt.State, verb rune) {
137138

138139
// SplitMVCCKey accepts an MVCC key and returns the "user" key, the MVCC version,
139140
// and a boolean indicating if the provided key is an MVCC key.
141+
//
142+
// Note, internally, we must make a copy of the provided mvccKey argument, which
143+
// typically comes from the Key() method as it's not safe.
140144
func SplitMVCCKey(mvccKey []byte) (key, version []byte, ok bool) {
141145
if len(mvccKey) == 0 {
142146
return nil, nil, false
143147
}
144148

145-
n := len(mvccKey) - 1
146-
tsLen := int(mvccKey[n])
149+
mvccKeyCopy := utils.Clone(mvccKey)
150+
151+
n := len(mvccKeyCopy) - 1
152+
tsLen := int(mvccKeyCopy[n])
147153
if n < tsLen {
148154
return nil, nil, false
149155
}
150156

151-
key = mvccKey[:n-tsLen]
157+
key = mvccKeyCopy[:n-tsLen]
152158
if tsLen > 0 {
153-
version = mvccKey[n-tsLen+1 : len(mvccKey)-1]
159+
version = mvccKeyCopy[n-tsLen+1 : len(mvccKeyCopy)-1]
154160
}
155161

156162
return key, version, true

ss/pebbledb/iterator.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,15 @@ func (itr *iterator) Value() []byte {
116116
}
117117

118118
func (itr *iterator) Next() {
119+
currKey, _, ok := SplitMVCCKey(itr.source.Key())
120+
if !ok {
121+
// XXX: This should not happen as that would indicate we have a malformed
122+
// MVCC key.
123+
panic(fmt.Sprintf("invalid PebbleDB MVCC key: %s", itr.source.Key()))
124+
}
125+
119126
var next bool
120127
if itr.reverse {
121-
currKey, _, ok := SplitMVCCKey(itr.source.Key())
122-
if !ok {
123-
// XXX: This should not happen as that would indicate we have a malformed
124-
// MVCC key.
125-
panic(fmt.Sprintf("invalid PebbleDB MVCC key: %s", itr.source.Key()))
126-
}
127-
128128
// Since PebbleDB has no PrevPrefix API, we must manually seek to the next
129129
// key that is lexicographically less than the current key.
130130
next = itr.source.SeekLT(MVCCEncode(currKey, 0))
@@ -154,6 +154,25 @@ func (itr *iterator) Next() {
154154
// append the current iterator key to the prefix and seek to that key.
155155
itr.valid = itr.source.SeekLT(MVCCEncode(nextKey, itr.version+1))
156156

157+
tmpKey, _, ok := SplitMVCCKey(itr.source.Key())
158+
if !ok {
159+
// XXX: This should not happen as that would indicate we have a malformed
160+
// MVCC key.
161+
itr.valid = false
162+
return
163+
}
164+
165+
// There exists cases where the SeekLT() call moved us back to the same key
166+
// we started at, so we must move to next key, i.e. two keys forward.
167+
if bytes.Equal(tmpKey, currKey) {
168+
if itr.source.NextPrefix() {
169+
itr.Next()
170+
} else {
171+
itr.valid = false
172+
return
173+
}
174+
}
175+
157176
// The cursor might now be pointing at a key/value pair that is tombstoned.
158177
// If so, we must move the cursor.
159178
if itr.valid && itr.cursorTombstoned() {

ss/test/storage_test_suite.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,51 @@ func (s *StorageTestSuite) TestDatabaseIteratorMultiVersion() {
369369
s.Require().NoError(itr.Error())
370370
}
371371

372+
// Tests bug where iterator loops continuously
373+
func (s *StorageTestSuite) TestDatabaseIteratorLooping() {
374+
db, err := s.NewDB(s.T().TempDir())
375+
s.Require().NoError(err)
376+
defer db.Close()
377+
378+
// Forward Iteration
379+
// Less than iterator version
380+
s.Require().NoError(DBApplyChangeset(db, 58827506, storeKey1, [][]byte{[]byte("keyC")}, [][]byte{[]byte("value003")}))
381+
// Both below are greater
382+
s.Require().NoError(DBApplyChangeset(db, 58833605, storeKey1, [][]byte{[]byte("keyC")}, [][]byte{[]byte("value004")}))
383+
s.Require().NoError(DBApplyChangeset(db, 58833606, storeKey1, [][]byte{[]byte("keyD")}, [][]byte{[]byte("value006")}))
384+
s.Require().NoError(DBApplyChangeset(db, 58827507, storeKey1, [][]byte{[]byte("keyE")}, [][]byte{[]byte("value006")}))
385+
s.Require().NoError(DBApplyChangeset(db, 58827508, storeKey1, [][]byte{[]byte("keyF")}, [][]byte{[]byte("value007")}))
386+
// Another case of a loop
387+
s.Require().NoError(DBApplyChangeset(db, 58827509, storeKey1, [][]byte{[]byte("keyG")}, [][]byte{[]byte("value008")}))
388+
s.Require().NoError(DBApplyChangeset(db, 58831545, storeKey1, [][]byte{[]byte("keyG")}, [][]byte{[]byte("value009")}))
389+
s.Require().NoError(DBApplyChangeset(db, 58831565, storeKey1, [][]byte{[]byte("keyH")}, [][]byte{[]byte("value010")}))
390+
391+
itr, err := db.Iterator(storeKey1, 58831525, []byte("keyA"), nil)
392+
s.Require().NoError(err)
393+
394+
defer itr.Close()
395+
396+
count := 0
397+
for ; itr.Valid(); itr.Next() {
398+
count++
399+
}
400+
401+
s.Require().Equal(4, count)
402+
403+
// Reverse Iteration
404+
itr, err = db.Iterator(storeKey1, 58831525, nil, []byte("keyZ"))
405+
s.Require().NoError(err)
406+
407+
defer itr.Close()
408+
409+
count = 0
410+
for ; itr.Valid(); itr.Next() {
411+
count++
412+
}
413+
414+
s.Require().Equal(4, count)
415+
}
416+
372417
func (s *StorageTestSuite) TestDatabaseIteratorNoDomain() {
373418
db, err := s.NewDB(s.T().TempDir())
374419
s.Require().NoError(err)

0 commit comments

Comments
 (0)