-
Notifications
You must be signed in to change notification settings - Fork 37.7k
mining: add early return to waitTipChanged() #31297
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
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31297. ReviewsSee the guideline for information on the review process. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
cc @TheCharlatan life would be siugnifancly better if |
src/node/interfaces.cpp
Outdated
return BlockRef{tip_hash, chainman().ActiveChain().Tip()->nHeight}; | ||
} | ||
} | ||
|
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 wonder if it is clearer to make m_tip_block
nullopt, instead of a magic value? Would be nice to see the diff and pick the better one.
I think the bug here is just that blockTip() is not called on startup so m_tip_block is not set when node starts up, only after it starts up and then a new block get connected. Otherwise the waitTipChanged method is already written to provide desired behavior by waiting until there is any new tip and the new tip is not zero: notifications().m_tip_block_cv.wait_for(lock, timeout, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
return (notifications().m_tip_block != current_tip && notifications().m_tip_block != uint256::ZERO) || chainman().m_interrupt;
}); I don't actually see how the early return But It would seem cleaner to just call blockTip() when the tip is first loaded so no workaround is needed. |
That's how I use it in Sjors#49 when the Template Provider spins up its main thread: void Sv2TemplateProvider::ThreadSv2Handler()
{
// Wait for the node chainstate to be ready if needed.
auto tip{m_mining.waitTipChanged(uint256::ZERO)};
Assert(tip.hash != uint256::ZERO); Update 2024-11-18: just noticed
I tend to agree. |
src/node/interfaces.cpp
Outdated
{ | ||
LOCK(::cs_main); | ||
const uint256 tip_hash{chainman().ActiveChain().Tip()->GetBlockHash()}; | ||
if (current_tip != uint256::ZERO || current_tip != tip_hash) { |
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.
435934f oops, that should have been &&
435934f
to
71a539c
Compare
Pushed a bug fix, but still need to address the main feedback. |
Also, it would be good to add test coverage? |
@maflcko it's implicitly tested by the shutdown test, which is how I found this bug. Not sure how to explicitly test an early return? I could add a comment to the shutdown test that the |
Are you sure it is covered, because the code is reported as uncovered in https://corecheck.dev/bitcoin/bitcoin/pulls/31297? |
@maflcko |
I added a commit to make Also, looking at Lines 1794 to 1806 in 746f93b
So I was probably doing something wrong earlier. I dropped the workaround. An early return only happens if:
If a caller sets I'm still not sure how to add a functional test for this. |
71a539c
to
8a15e46
Compare
return (notifications().m_tip_block && notifications().m_tip_block.value() != current_tip) || chainman().m_interrupt; | ||
}); | ||
// Early return if a tip is connected and already different. | ||
if (!notifications().m_tip_block || notifications().m_tip_block.value() == current_tip) { |
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.
In commit "mining: add early return to waitTipChanged()" (71a539c)
Does this change have any effect? It looks like this is basically changing cv.wait_for(condition)
to if (!condition) cv.wait_for(condition)
which I would expect to be equivalent.
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'm assuming wait_for
won't be triggered until m_tip_block
is set or modified, or the timeout passes. So it doesn't return early.
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.
In other words:
if !tip_changed
wait_for: tip_changed
return new_tip
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.
Are you are seeing a difference in practice? At least according to https://en.cppreference.com/w/cpp/thread/condition_variable/wait_for, wait_for(lock, rel_time, pred)
is equivalent to wait_until(lock, rel_time + now, pred)
and according to https://en.cppreference.com/w/cpp/thread/condition_variable/wait_until that should be equivalent to while(!pred()) wait...
checking the predicate before there is any waiting.
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 thought I did, but given what the documentation says, it indeed shouldn't be needed. Will test again.
re: #31297 (comment)
I think we typically get past that point on line 1795 in I think maybe a good fix for this issue could be to call GetNotifications().blockTip() in It would be nice if we could have a functional test to reproduce the exact problem, but that is hard as long as python code doesn't have an easy way to call the waitTipChanged() method. It would probably be easier to test this after #30437 we have a bitcoin-mine executable that python code could run, and could test various mining methods. |
It's a bit tricky at the moment to test the interaction of multiple pull requests on my stratum v2 branches. I'll test this again later, and most likely close the PR then. See #31297 (comment) Meanwhile I extracted the make |
I tested that I'll open a new PR to try and deal with that. |
See #31346. |
37946c0 Set notifications m_tip_block in LoadChainTip() (Sjors Provoost) Pull request description: Ensure KernelNotifications `m_tip_block` is set even if no new block arrives. Suggested in #31297 (comment) ACKs for top commit: ryanofsky: Code review ACK 37946c0, fixing comment bug caught by @mzumsande in #31346 (comment) in another really helpful clarification mzumsande: Code Review ACK 37946c0 TheCharlatan: ACK 37946c0 Tree-SHA512: 931bf820440a0cdda276f6dbd63f03fdbcdc90b18e7d5e160a74bdd9d0290acc706c35aab15bbdcd6e5e0b77565b3d07ff49b0dcf6551cb83961bae67be5d1bb
81cea5d Ensure m_tip_block is never ZERO (Sjors Provoost) e058544 Make m_tip_block an std::optional (Sjors Provoost) Pull request description: Suggested in #31297 (comment) ACKs for top commit: fjahr: re-ACK 81cea5d tdb3: code review re ACK 81cea5d l0rinc: ACK 81cea5d Tree-SHA512: 31a75ba29e3d567bab32e4e7925a419d9d7a4d2d85ed1c1012116d8d22adc14d31d5b4ce5f6c499c994188dcd26a01cced05be74f94c892fc90ae17a6783a472
It's useful to be able to wait for an active chaintip to appear, by calling
waitTipChanged(uint256::ZERO);
.Unfortunately this doesn't work out of the box, because
notifications().m_tip_block
isZERO
until a new block arrives.Additionally we need an early return before calling
wait_for
.