Skip to content
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

Closed
wants to merge 7 commits into from
Closed

Improve page deletion from multipage directories (RIPD-1353) #1923

wants to merge 7 commits into from

Conversation

nbougalis
Copy link
Contributor

This pull request does not change how page entries are sorted or how pages are selected. An amendment will be introduced in the future.

@seelabs
Copy link
Collaborator

seelabs commented Dec 6, 2016

#include <ripple/dedger/Directory.h> in View.cpp to fix the non-unity builds.

The AccountInfo unit test is hitting an assert on line 311 of Indexes.cpp. The root.type was ltANY, the assert expects ltDIR_NODE. I haven't dug into it further.

dirRemove (
Keylet const& directory,
std::uint64_t page,
Keylet const& key,
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor

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.

@nbougalis
Copy link
Contributor Author

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 (
Copy link
Contributor

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)
Copy link
Contributor

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>
Copy link
Contributor

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
Copy link
Contributor

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.");
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

@nbougalis nbougalis Dec 14, 2016

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);
Copy link
Contributor

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
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid looking test

@vinniefalco
Copy link
Contributor

Looks good, minor style nits but mergeable.
👍

if (indexes.size () < dirNodeMaxEntries)
{
// If we're not maintaining strict order, then
// sort entries lexicographically.
Copy link
Collaborator

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;
Copy link
Collaborator

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 insertmember 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.

Copy link
Contributor Author

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);
Copy link
Collaborator

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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);
Copy link
Collaborator

@seelabs seelabs Dec 9, 2016

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.

Copy link
Contributor Author

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: const

Copy link
Contributor Author

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));
Copy link
Collaborator

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 (
Copy link
Collaborator

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):

  1. Put the code back inside dirDelete, either with a lambda or just embed the code
  2. Add a parameter to the function that controls how many pages to search
  3. Add a comment documenting that it only searches 20 pages

Copy link
Contributor Author

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)
Copy link
Collaborator

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).

Copy link
Contributor Author

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.

@seelabs
Copy link
Collaborator

seelabs commented Dec 9, 2016

As you mention in your commit message, this is tx breaking, so it can't be merged without an amendment.

@nbougalis
Copy link
Contributor Author

Updating to address comments. Thanks.

@JoelKatz
Copy link
Collaborator

Looks good so far. I'll wait for the final update before I approve it.

}

return result.first;
sleTicket->setFieldU64(sfOwnerNode, result.second);
Copy link
Contributor

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
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Will fix.

@codecov-io
Copy link

Current coverage is 65.37% (diff: 77.82%)

Merging #1923 into develop will decrease coverage by 0.09%

@@            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          

Powered by Codecov. Last update 9e960ff...33a58af

Copy link
Collaborator

@seelabs seelabs left a 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
Copy link
Collaborator

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.
Copy link
Collaborator

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);
Copy link
Collaborator

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.

@nbougalis
Copy link
Contributor Author

An amendment is needed for this, so I'm going to close it for now, then rebase, add the amendment and resubmit it.

@nbougalis nbougalis closed this Dec 20, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants