-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Improve page deletion from multipage directories (RIPD-1353) #1923
Conversation
The |
dirRemove ( | ||
Keylet const& directory, | ||
std::uint64_t page, | ||
Keylet const& key, |
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.
Why are there two of these?
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.
One takes a Keylet
but Sometimes we don't have a Keylet
available, so we offer the other version which accepts a uint256
. We could have required callers to construct the Keylet
but I wanted to minimize changes for this round.
I will be removing uses of the old API along with the amendment to enable sorting of owner directories, so that will go away.
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.
It was never the intention of Keylet
to be used as a function parameter
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.
Sorry, that was misleading. Keylet
as function parameter is fine. There is no computation performed in the Keylet
constructor.
Thanks @seelabs. I forgot to push a FOLD commit that addressed this. It's coming. |
// Called when the owner count changes | ||
// This is required to support PaymentSandbox | ||
void | ||
ApplyView::adjustOwnerCountHook ( |
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.
There was no need to put this in the .cpp file it was more concise in the header. This adds additional lines with no corresponding benefit. Same for the other empty function.
@@ -563,7 +564,7 @@ void removeUnfundedOffers (ApplyView& view, std::vector<uint256> const& offers, | |||
{ | |||
// offer is unfunded | |||
offerDelete (view, sleOffer, viewJ); | |||
if (++removed == 1000) | |||
if (++removed == unfundedOfferRemoveLimit) |
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.
Paranoia, consider:
if(++removed >= unfundedOfferRemoveLimit)
@param describe callback to add required entries to a new page | ||
*/ | ||
/** @{ */ | ||
std::pair<bool, std::uint64_t> |
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.
The return value needs to be documented
the directory itself. | ||
*/ | ||
/** @{ */ | ||
bool |
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.
The return value needs to be documented
node = peek (keylet::page(directory, page)); | ||
|
||
if (!node) | ||
LogicError ("Directory chain: root back-pointer broken."); |
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.
Informational: We are creating code that is guaranteed not to be included in coverage reports.
The irony that I am the one who introduced this idiom is not lost on me.
return false; | ||
|
||
{ | ||
auto entries = node->getFieldV256(sfIndexes); |
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.
Does this make a copy?
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.
It does. We can get away with it with a bit of ugliness:
auto entries = dynamic_cast<STVector256&>(node->getField(sfIndexes));
auto const dir = Dir(*env.current(), | ||
keylet::ownerDir(bob)); | ||
|
||
auto iter = std::begin(dir); |
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.
Frivolous, dir.begin()
is idiomatic.
} // test | ||
} // ripple | ||
} | ||
} |
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.
Solid looking test
Looks good, minor style nits but mergeable. |
if (indexes.size () < dirNodeMaxEntries) | ||
{ | ||
// If we're not maintaining strict order, then | ||
// sort entries lexicographically. |
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 comment is misleading. We don't sort the entries, we insert at the correct position if the vector was already sorted.
LogicError ("dirInsert: double insertion"); | ||
|
||
v.insert (pos, key); | ||
indexes = v; |
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.
We can save two copies of the vector if we add an insert
member to STVector
. We'd save the copy on line 96 (it would becore auto const& v = ...
), and the copy here. Since STVector
already has an erase
member insert
seems OK.
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.
It also has a push_back
so insert
seems reasonable.
|
||
// Insert the new key: | ||
indexes.clear(); | ||
indexes.push_back (key); |
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.
Nit: I'd move this code down closer to where it's used, around line 136.
if (entries.end () == it) | ||
return false; | ||
|
||
// We always preserve the relative order when we remove. |
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 think I need to understand why we're starting to care about order in non-book directories (I've forgotten).
If we don't need to preserve order (owner directories) there's an optimization that saves some copying: copy the last element to the current element then resize the vector.
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.
We had this optimization in the existing code: we want to have the ability to sort pages going forward.
update(next); | ||
|
||
// The page is no longer linked. Delete it. | ||
erase(node); |
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.
At this point we need to check if there are two pages left and the last page is empty. If we don't, we won't handle the [EMPTY] -> [Just removed last element] -> [EMPTY] scenario. I'm guessing the unit tests pass because the last page is no longer allowed to be empty. It it were (i.e. an existing ledger), then I would expect the unit test to fail.
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've addressed this in 4da5382
// deleted, and, if so, whether the entire directory | ||
// can now be removed. | ||
auto prevPage = node->getFieldU64(sfIndexPrevious); | ||
auto nextPage = node->getFieldU64(sfIndexNext); |
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.
Nit: const
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.
Can't: we have this further down:
// Make sure our local values reflect the
// updated information:
nextPage = currPage;
prevPage = currPage;
JLOG (j.trace()) << "dirAdd:" << | ||
" dir=" << to_string (dir.key) << | ||
" uLedgerIndex=" << to_string (uLedgerIndex); | ||
bool existed = static_cast<bool>(view.peek (dir)); |
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.
Nit: const
uNodeDir = sleRoot->getFieldU64 (sfIndexPrevious); // Get index to last directory node. | ||
static | ||
boost::optional<std::uint64_t> | ||
findPage ( |
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 called findPage
, but it only searches the first 20 pages and we have to look at the code to see that. I'm worried someone is going to reuses this and it's going to bite us. Suggestions (any are fine with me):
- Put the code back inside
dirDelete
, either with a lambda or just embed the code - Add a parameter to the function that controls how many pages to search
- Add a comment documenting that it only searches 20 pages
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.
findPage
has been integrated directly into dirDelete
// Emulate the old semantics: if uNodeDir isn't hard | ||
// and fast and there's no page number, search for the | ||
// item: | ||
if (bSoft && uNodeDir == 0) |
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 code searches the first 20 pages of a directory, but only when uNodeDir == 0
. The code this replaces would search from the current page up to the 20th page (i.e. old code would search even if uNodeDir
was not zero).
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.
Good eyes. However, I believe that all the use cases specified 0
when bSoft == true
, so the code correctly replicates the semantics.
As you mention in your commit message, this is tx breaking, so it can't be merged without an amendment. |
Updating to address comments. Thanks. |
Looks good so far. I'll wait for the final update before I approve it. |
} | ||
|
||
return result.first; | ||
sleTicket->setFieldU64(sfOwnerNode, result.second); |
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.
Why not
(*sleTicket)[sfOwnerNode] = result.second;
?
std::function<void(std::shared_ptr<SLE> const&)> describe); | ||
/** @} */ | ||
|
||
/** Removes an entry from a directory |
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.
Use imperative voice, e.g. "Remove an entry from a directory"
// Save some space by not specifying the value 0 since | ||
// it's the default. | ||
if (page != 1) | ||
node->setFieldU64 (sfIndexPrevious, page - 1); |
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.
Can we write (*node)[sfIndexPrevious] = page - 1
?
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.
Yes. Will fix.
Current coverage is 65.37% (diff: 77.82%)@@ develop #1923 diff @@
==========================================
Files 698 699 +1
Lines 49745 49755 +10
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
- Hits 32568 32526 -42
- Misses 17177 17229 +52
Partials 0 0
|
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.
Left a couple nits, but looks good. I think this is ready for a thumbs up once it's on an amendment.
return result.first; | ||
(*sleTicket)[sfOwnerNode] = result.second; | ||
|
||
// If we succeeded, the new entry counts agains the |
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.
Typo: agains -> against
if (!strictOrder) | ||
{ | ||
// We can't use std::lower_bound here because | ||
// existing pages may not be fully sorted. |
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.
"Not fully sorted" implies there is some order to to page, but we can't make any guarantees about the order at all. The pages are just "not sorted".
if (pos != indexes.end() && key == *pos) | ||
LogicError ("dirInsert: double insertion"); | ||
|
||
indexes.insert (pos, key); |
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.
Someone is going to look at this code and wonder why it's being inserted in this order. However, there is no functional benefit now. We are inserting in this order because in the future we plan on introducing sorting to certain directories, correct?
I'd at least add a comment explaining why we are inserting this way.
An amendment is needed for this, so I'm going to close it for now, then rebase, add the amendment and resubmit it. |
This pull request does not change how page entries are sorted or how pages are selected. An amendment will be introduced in the future.