-
Notifications
You must be signed in to change notification settings - Fork 38.8k
validation: cache tip recency for lock-free IsInitialBlockDownload()
#34253
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
base: master
Are you sure you want to change the base?
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/34253. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please copy-paste 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. LLM Linter (✨ experimental)Possible typos and grammar issues:
2026-01-12 |
3547920 to
a4041df
Compare
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.
Concept ACK (I didn't review the latest approach of #32885, just this one), it would help to be able to call IsIBD() without having to worry about cs_main locking.
src/validation.h
Outdated
| mutable std::atomic_bool m_cached_finished_ibd{false}; | ||
|
|
||
| /** Latch that becomes true once the active tip has met IsTipRecent(). */ | ||
| mutable std::atomic_bool m_cached_tip_recent{false}; |
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 having both m_cached_tip_recent and m_cached_finished_ibd may be more complicated than necessary. Probably the reason for it is that IsIBD() should latch to true also after ImportBlocks() without having to wait for a new block.
Would it work to instead have just one atomic called m_cached_finished_ibd that is only set when LoadingBlocks() is false, ans set it in a function ChainstateManager::MaybeExitIBD() that would be called both from ChainstateManager::SetTip (within the current approach, see also my other comment about that) and at the end of ImportBlocks() when ImportingNow went out of scope`?
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.
Done, thanks
src/validation.cpp
Outdated
| } | ||
|
|
||
| m_chain.SetTip(*pindexDelete->pprev); | ||
| m_chainman.SetTip(m_chain, *pindexDelete->pprev); |
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 find it a bit weird to have a function SetTip on the level of ChainstateManager, that would naturally live on a Chainstate/Chain level.
I saw the justification of coupling the two close to make it harder to "miss related state updates", but I am not really convinced by it: It can still be easily forgotten by just using Chain::SetTip instead, like many tests do, and there are only 3 affected call sites outside of tests.
Maybe it would be simpler to not couple the two and call a separate function ChainstateManager::MaybeExitIBD() at the 3 call sites (DisconnectTip, ConnectTip, LoadChainTip) right after the CChain::SetTip() call.
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.
Done, thanks
a4041df to
cac06c4
Compare
|
Thanks to @mzumsande for the hint to simplify this, added you as coauthor. The new push makes Edit: pushed a more comprehensive test to check the whole matrix of conditions to be sure the behavior doesn't change. |
cac06c4 to
63e822b
Compare
Add a unit test that exercises the `IsInitialBlockDownload()` decision matrix by varying the cached latch, `BlockManager::LoadingBlocks()`, and tip work/recency inputs. This documents the current latching behavior and provides a baseline for later refactors.
Rename and invert the internal IBD latch so the cached value directly matches `IsInitialBlockDownload()` (true while in IBD, then latched to false). This is a behavior-preserving refactor to avoid double negatives.
Factor the chain tip work/recency check out of `ChainstateManager::IsInitialBlockDownload()` into a reusable `CChain::IsTipRecent()` helper, and annotate it as requiring `cs_main` since it's reading mutable state. Also introduce a local `chainman_ref` in the kernel import-blocks wrapper to reduce repetition and keep follow-up diffs small. `IsInitialBlockDownload` returns were also unified to make the followup move clean. Co-authored-by: Patrick Strateman <[email protected]> Co-authored-by: Martin Zumsande <[email protected]>
`ChainstateManager::IsInitialBlockDownload()` is queried on hot paths and previously acquired `cs_main` internally, contributing to lock contention. Cache the IBD status in `m_cached_is_ibd`, and introduce `ChainstateManager::UpdateIBDStatus()` to latch it once block loading has finished and the current chain tip has enough work and is recent. Call the updater after tip updates and after `ImportBlocks()` completes. Since `IsInitialBlockDownload()` no longer updates the cache, drop `mutable` from `m_cached_is_ibd` and only update it from `UpdateIBDStatus()` under `cs_main`. Update the new unit test to showcase the new `UpdateIBDStatus()`. Co-authored-by: Patrick Strateman <[email protected]> Co-authored-by: Martin Zumsande <[email protected]>
63e822b to
557b41a
Compare
Context
This PR is a follow-up to the stale #32885.
Problem
ChainstateManager::IsInitialBlockDownload()currently acquirescs_maininternally, even though most existing call sites already hold the lock. This becomes relevant for proposals like #34054, which would callIsInitialBlockDownload()from the scheduler thread without holdingcs_main, potentially introducing lock contention.Fix
Make
ChainstateManager::IsInitialBlockDownload()lock-free by caching its result in a single atomicm_cached_is_ibd(true while in IBD, latched to false on exit).Move the IBD exit checks out of
IsInitialBlockDownload()(reader-side) into a newChainstateManager::UpdateIBDStatus()(writer-side, called under cs_main).Call UpdateIBDStatus() at strategic points where IBD exit conditions may change, after active chain tip updates in
ConnectTip(),DisconnectTip(), andLoadChainTip(), and afterImportBlocks()returns.With this,
IsInitialBlockDownload()becomes a lock-free atomic read, avoiding internalcs_mainacquisition on hot paths.Testing and Benchmarks
This isn't strictly an optimization - though some usecases might benefit from it -, so rather as a sanity check I ran a reindex-chainstate and an AssumeUTXO load (without background validation).
assumeutxo load | 910000 blocks | dbcache 4500 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | xfs | SSD
2026-01-12 | reindex-chainstate | 931139 blocks | dbcache 4500 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | SSD