-
Notifications
You must be signed in to change notification settings - Fork 15
[SeiDB] Fix MemIAVL Race Condition during snapshot reload #56
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
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 |
---|---|---|
|
@@ -29,7 +29,7 @@ type Tree struct { | |
// when true, the get and iterator methods could return a slice pointing to mmaped blob files. | ||
zeroCopy bool | ||
|
||
// sync.RWMutex is used to protect the cache for thread safety | ||
// sync.RWMutex is used to protect the tree for thread safety during snapshot reload | ||
mtx *sync.RWMutex | ||
} | ||
|
||
|
@@ -93,13 +93,14 @@ func (t *Tree) SetInitialVersion(initialVersion int64) error { | |
|
||
// Copy returns a snapshot of the tree which won't be modified by further modifications on the main tree, | ||
// the returned new tree can be accessed concurrently with the main tree. | ||
func (t *Tree) Copy(cacheSize int) *Tree { | ||
func (t *Tree) Copy(_ int) *Tree { | ||
yzang2019 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
t.mtx.RLock() | ||
defer t.mtx.RUnlock() | ||
if _, ok := t.root.(*MemNode); ok { | ||
// protect the existing `MemNode`s from get modified in-place | ||
t.cowVersion = t.version | ||
} | ||
newTree := *t | ||
// cache is not copied along because it's not thread-safe to access | ||
newTree.mtx = &sync.RWMutex{} | ||
return &newTree | ||
} | ||
|
@@ -116,6 +117,8 @@ func (t *Tree) ApplyChangeSet(changeSet iavl.ChangeSet) { | |
} | ||
|
||
func (t *Tree) Set(key, value []byte) { | ||
t.mtx.Lock() | ||
defer t.mtx.Unlock() | ||
if value == nil { | ||
// the value could be nil when replaying changes from write-ahead-log because of protobuf decoding | ||
value = []byte{} | ||
|
@@ -124,6 +127,8 @@ func (t *Tree) Set(key, value []byte) { | |
} | ||
|
||
func (t *Tree) Remove(key []byte) { | ||
t.mtx.Lock() | ||
defer t.mtx.Unlock() | ||
_, t.root, _ = removeRecursive(t.root, key, t.version+1, t.cowVersion) | ||
} | ||
|
||
|
@@ -150,13 +155,17 @@ func (t *Tree) Version() int64 { | |
// RootHash updates the hashes and return the current root hash, | ||
// it clones the persisted node's bytes, so the returned bytes is safe to retain. | ||
func (t *Tree) RootHash() []byte { | ||
t.mtx.RLock() | ||
defer t.mtx.RUnlock() | ||
if t.root == nil { | ||
return emptyHash | ||
} | ||
return t.root.SafeHash() | ||
} | ||
|
||
func (t *Tree) GetWithIndex(key []byte) (int64, []byte) { | ||
t.mtx.RLock() | ||
defer t.mtx.RUnlock() | ||
if t.root == nil { | ||
return 0, nil | ||
} | ||
|
@@ -169,6 +178,8 @@ func (t *Tree) GetWithIndex(key []byte) (int64, []byte) { | |
} | ||
|
||
func (t *Tree) GetByIndex(index int64) ([]byte, []byte) { | ||
t.mtx.RLock() | ||
defer t.mtx.RUnlock() | ||
if index > math.MaxUint32 { | ||
return nil, nil | ||
} | ||
|
@@ -195,12 +206,16 @@ func (t *Tree) Has(key []byte) bool { | |
} | ||
|
||
func (t *Tree) Iterator(start, end []byte, ascending bool) dbm.Iterator { | ||
t.mtx.RLock() | ||
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. Iterator is tricker, it supposed the hold the RLock before the iterator is closed 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. Hmm yeah that's a good point, iterator is a bit tricky, this lock only covers the creation of the iterator, might not be enough. I think for now it's probably fine since we don't have a lot of use cases using iterators in EVM, we definitely can add more locking coverage in the future |
||
defer t.mtx.RUnlock() | ||
return NewIterator(start, end, ascending, t.root, t.zeroCopy) | ||
} | ||
|
||
// ScanPostOrder scans the tree in post-order, and call the callback function on each node. | ||
// If the callback function returns false, the scan will be stopped. | ||
func (t *Tree) ScanPostOrder(callback func(node Node) bool) { | ||
t.mtx.RLock() | ||
defer t.mtx.RUnlock() | ||
if t.root == nil { | ||
return | ||
} | ||
|
@@ -248,6 +263,8 @@ func (t *Tree) Export() *Exporter { | |
} | ||
|
||
func (t *Tree) Close() error { | ||
t.mtx.Lock() | ||
defer t.mtx.Unlock() | ||
var err error | ||
if t.snapshot != nil { | ||
err = t.snapshot.Close() | ||
|
@@ -257,6 +274,23 @@ func (t *Tree) Close() error { | |
return err | ||
} | ||
|
||
// ReplaceWith is used during reload to replace the current tree with the newly loaded snapshot | ||
func (t *Tree) ReplaceWith(other *Tree) error { | ||
t.mtx.Lock() | ||
defer t.mtx.Unlock() | ||
snapshot := t.snapshot | ||
t.version = other.version | ||
t.root = other.root | ||
t.snapshot = other.snapshot | ||
t.initialVersion = other.initialVersion | ||
t.cowVersion = other.cowVersion | ||
t.zeroCopy = other.zeroCopy | ||
if snapshot != nil { | ||
return snapshot.Close() | ||
} | ||
return nil | ||
} | ||
|
||
// nextVersionU32 is compatible with existing golang iavl implementation. | ||
// see: https://github.com/cosmos/iavl/pull/660 | ||
func nextVersionU32(v uint32, initialVersion uint32) uint32 { | ||
|
Uh oh!
There was an error while loading. Please reload this page.