-
Notifications
You must be signed in to change notification settings - Fork 79
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
Fixes several mutation tracking issues with leaf nodes. #13
Merged
Merged
Changes from 3 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3906746
Fixes broken mergeChild paths for leaf nodes.
slackpad 4f2b475
Gets rid of extra indirection when tracking channels.
slackpad af97fad
Fixes leaf tracking on writeable nodes in the cache.
slackpad 6983227
Adds some comments to the unit test.
slackpad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -605,6 +605,31 @@ func TestMergeChildVisibility(t *testing.T) { | |
} | ||
} | ||
|
||
// isClosed returns true if the given channel is closed. | ||
func isClosed(ch chan struct{}) bool { | ||
select { | ||
case <-ch: | ||
return true | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
// hasAnyClosedMutateCh scans the given tree and returns true if there are any | ||
// closed mutate channels on any nodes or leaves. | ||
func hasAnyClosedMutateCh(r *Tree) bool { | ||
for iter := r.root.rawIterator(); iter.Front() != nil; iter.Next() { | ||
n := iter.Front() | ||
if isClosed(n.mutateCh) { | ||
return true | ||
} | ||
if n.isLeaf() && isClosed(n.leaf.mutateCh) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func TestTrackMutate_SeekPrefixWatch(t *testing.T) { | ||
for i := 0; i < 3; i++ { | ||
r := New() | ||
|
@@ -652,6 +677,9 @@ func TestTrackMutate_SeekPrefixWatch(t *testing.T) { | |
r = txn.CommitOnly() | ||
txn.slowNotify() | ||
} | ||
if hasAnyClosedMutateCh(r) { | ||
t.Fatalf("bad") | ||
} | ||
|
||
// Verify root and parent triggered, and leaf affected | ||
select { | ||
|
@@ -706,6 +734,9 @@ func TestTrackMutate_SeekPrefixWatch(t *testing.T) { | |
r = txn.CommitOnly() | ||
txn.slowNotify() | ||
} | ||
if hasAnyClosedMutateCh(r) { | ||
t.Fatalf("bad") | ||
} | ||
|
||
// Verify root and parent triggered, and leaf affected | ||
select { | ||
|
@@ -791,6 +822,9 @@ func TestTrackMutate_GetWatch(t *testing.T) { | |
r = txn.CommitOnly() | ||
txn.slowNotify() | ||
} | ||
if hasAnyClosedMutateCh(r) { | ||
t.Fatalf("bad") | ||
} | ||
|
||
// Verify root and parent triggered, not leaf affected | ||
select { | ||
|
@@ -839,6 +873,9 @@ func TestTrackMutate_GetWatch(t *testing.T) { | |
r = txn.CommitOnly() | ||
txn.slowNotify() | ||
} | ||
if hasAnyClosedMutateCh(r) { | ||
t.Fatalf("bad") | ||
} | ||
|
||
select { | ||
case <-rootWatch: | ||
|
@@ -894,6 +931,9 @@ func TestTrackMutate_GetWatch(t *testing.T) { | |
r = txn.CommitOnly() | ||
txn.slowNotify() | ||
} | ||
if hasAnyClosedMutateCh(r) { | ||
t.Fatalf("bad") | ||
} | ||
|
||
// Verify root and parent triggered, not leaf affected | ||
select { | ||
|
@@ -942,6 +982,9 @@ func TestTrackMutate_GetWatch(t *testing.T) { | |
r = txn.CommitOnly() | ||
txn.slowNotify() | ||
} | ||
if hasAnyClosedMutateCh(r) { | ||
t.Fatalf("bad") | ||
} | ||
|
||
select { | ||
case <-rootWatch: | ||
|
@@ -1058,6 +1101,11 @@ func TestTrackMutate_HugeTxn(t *testing.T) { | |
// Now do the trigger. | ||
txn.Notify() | ||
|
||
// Make sure no closed channels escaped the transaction. | ||
if hasAnyClosedMutateCh(r) { | ||
t.Fatalf("bad") | ||
} | ||
|
||
// Verify the watches fired as expected. | ||
select { | ||
case <-rootWatch: | ||
|
@@ -1090,3 +1138,141 @@ func TestTrackMutate_HugeTxn(t *testing.T) { | |
t.Fatalf("bad") | ||
} | ||
} | ||
|
||
func TestTrackMutate_mergeChild(t *testing.T) { | ||
// This case does a delete of the "acb" leaf, which causes the "aca" | ||
// leaf to get merged with the old "ac" node: | ||
// | ||
// [root] [root] | ||
// |a |a | ||
// [node] [node] | ||
// b/ \c b/ \c | ||
// (ab) [node] (ab) (aca) | ||
// a/ \b | ||
// (aca) (acb) | ||
// | ||
for i := 0; i < 3; i++ { | ||
r := New() | ||
r, _, _ = r.Insert([]byte("ab"), nil) | ||
r, _, _ = r.Insert([]byte("aca"), nil) | ||
r, _, _ = r.Insert([]byte("acb"), nil) | ||
snapIter := r.root.rawIterator() | ||
|
||
// Run through all notification methods as there were bugs in | ||
// both that affected these operations. The slowNotify path | ||
// would detect copied but otherwise identical leaves as changed | ||
// and wrongly close channels. The normal path would fail to | ||
// notify on a child node that had been merged. | ||
txn := r.Txn() | ||
txn.TrackMutate(true) | ||
txn.Delete([]byte("acb")) | ||
switch i { | ||
case 0: | ||
r = txn.Commit() | ||
case 1: | ||
r = txn.CommitOnly() | ||
txn.Notify() | ||
default: | ||
r = txn.CommitOnly() | ||
txn.slowNotify() | ||
} | ||
if hasAnyClosedMutateCh(r) { | ||
t.Fatalf("bad") | ||
} | ||
|
||
// Run through the old tree and make sure the exact channels we | ||
// expected were closed. | ||
for ; snapIter.Front() != nil; snapIter.Next() { | ||
n := snapIter.Front() | ||
path := snapIter.Path() | ||
switch path { | ||
case "", "a", "ac": | ||
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. Explanatory comments would go a long way :) |
||
if !isClosed(n.mutateCh) || n.leaf != nil { | ||
t.Fatalf("bad") | ||
} | ||
case "ab": | ||
if isClosed(n.mutateCh) || isClosed(n.leaf.mutateCh) { | ||
t.Fatalf("bad") | ||
} | ||
case "aca": | ||
if !isClosed(n.mutateCh) || isClosed(n.leaf.mutateCh) { | ||
t.Fatalf("bad") | ||
} | ||
case "acb": | ||
if !isClosed(n.mutateCh) || !isClosed(n.leaf.mutateCh) { | ||
t.Fatalf("bad") | ||
} | ||
default: | ||
t.Fatalf("bad: %s", path) | ||
} | ||
} | ||
} | ||
} | ||
|
||
func TestTrackMutate_cachedNodeChange(t *testing.T) { | ||
// This case does a delete of the "acb" leaf, which causes the "aca" | ||
// leaf to get merged with the old "ac" node: | ||
// | ||
// [root] [root] | ||
// |a |a | ||
// [node] [node] | ||
// b/ \c b/ \c | ||
// (ab) [node] (ab) (aca) | ||
// a/ \b | ||
// (aca) (acb) | ||
// | ||
// Then it makes a modification to the "aca" leaf on a node that will | ||
// be in the cache, so this makes sure that the leaf watch fires. | ||
for i := 0; i < 3; i++ { | ||
r := New() | ||
r, _, _ = r.Insert([]byte("ab"), nil) | ||
r, _, _ = r.Insert([]byte("aca"), nil) | ||
r, _, _ = r.Insert([]byte("acb"), nil) | ||
snapIter := r.root.rawIterator() | ||
|
||
txn := r.Txn() | ||
txn.TrackMutate(true) | ||
txn.Delete([]byte("acb")) | ||
txn.Insert([]byte("aca"), nil) | ||
switch i { | ||
case 0: | ||
r = txn.Commit() | ||
case 1: | ||
r = txn.CommitOnly() | ||
txn.Notify() | ||
default: | ||
r = txn.CommitOnly() | ||
txn.slowNotify() | ||
} | ||
if hasAnyClosedMutateCh(r) { | ||
t.Fatalf("bad") | ||
} | ||
|
||
// Run through the old tree and make sure the exact channels we | ||
// expected were closed. | ||
for ; snapIter.Front() != nil; snapIter.Next() { | ||
n := snapIter.Front() | ||
path := snapIter.Path() | ||
switch path { | ||
case "", "a", "ac": | ||
if !isClosed(n.mutateCh) || n.leaf != nil { | ||
t.Fatalf("bad") | ||
} | ||
case "ab": | ||
if isClosed(n.mutateCh) || isClosed(n.leaf.mutateCh) { | ||
t.Fatalf("bad") | ||
} | ||
case "aca": | ||
if !isClosed(n.mutateCh) || !isClosed(n.leaf.mutateCh) { | ||
t.Fatalf("bad") | ||
} | ||
case "acb": | ||
if !isClosed(n.mutateCh) || !isClosed(n.leaf.mutateCh) { | ||
t.Fatalf("bad") | ||
} | ||
default: | ||
t.Fatalf("bad: %s", path) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This is a great idea!