init: wake genesis wait after ImportBlocks() returns
What changed, and why it matters
This commit fixes a bug where Bitcoin Core could hang during shutdown if the user started a 'reindex' and then asked the program to quit before it finished loading the very first block. The fix adds a notification so the shutdown request is noticed, plus a test that simulates this exact scenario. It is a reliability/availability fix, not a security vulnerability that an attacker can exploit.
No immediate security action required. Treat as a normal bug-fix/availability improvement. Users running reindex should upgrade to avoid rare shutdown hangs, but there is no remote exploit.
Security signals we found
Denial-of-service/availability issue: graceful shutdown can hang
Fix is in initialization/shutdown synchronization logic
No attacker-controlled input path identified
Test added to prevent regression of shutdown hang
Evidence from the diff
During initialization, AppInitMain spawns a background ‘initload’ thread that runs ImportBlocks() and ActivateBestChain(). The main init thread can wait on kernel_notifications.m_tip_block_cv for genesis activation. If shutdown is requested while ImportBlocks() is still running and before genesis has been activated, ImportBlocks() returns early without ever sending a blockTip notification, so the condition variable is never signaled and the init thread remains blocked. The patch notifies m_tip_block_cv after ImportBlocks() returns, allowing the wait predicate to observe the shutdown flag. A functional test is added that starts a node with -reindex and terminates it after the ‘Reindexing block file blk00000.dat’ log line, exercising the interrupted-import-before-genesis path.
Changed components
src/init.cpp: AppInitMain background init thread and genesis wait synchronizationtest/functional/feature_init.py: startup/shutdown functional testsInspect captured patch +14 / −3
diff --git a/src/init.cpp b/src/init.cpp
index 0e72443c..290f0936 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -2045,10 +2045,16 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
/// \anchor initload
- node.background_init_thread = std::thread(&util::TraceThread, "initload", [=, &chainman, &args, &node] {
+ node.background_init_thread = std::thread(&util::TraceThread, "initload", [=, &chainman, &args, &kernel_notifications, &node] {
ScheduleBatchPriority();
// Import blocks and ActivateBestChain()
ImportBlocks(chainman, vImportFiles);
+ // An interrupted import may return without activating genesis. Wake
+ // the init thread's genesis wait, which is otherwise only notified
+ // on blockTip, and that never fires when the import was interrupted
+ // before activating genesis. This wakeup lets the wait observe the
+ // shutdown request.
+ WITH_LOCK(kernel_notifications.m_tip_block_mutex, kernel_notifications.m_tip_block_cv.notify_all());
WITH_LOCK(::cs_main, chainman.UpdateIBDStatus());
if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogInfo("Stopping after block import");
diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py
index ad20e39c..18faba33 100755
--- a/test/functional/feature_init.py
+++ b/test/functional/feature_init.py
@@ -63,6 +63,7 @@ class InitTest(BitcoinTestFramework):
node.process.terminate()
assert_equal(0, node.process.wait())
+ reindex_log_line = b'Reindexing block file blk00000.dat'
lines_to_terminate_after = [
b'Validating signatures for all blocks',
b'scheduler thread start',
@@ -87,16 +88,20 @@ class InitTest(BitcoinTestFramework):
]
if self.is_wallet_compiled():
lines_to_terminate_after.append(b'Verifying wallet')
+ lines_to_terminate_after.append(reindex_log_line)
for terminate_line in lines_to_terminate_after:
self.log.info(f"Starting node and will terminate after line {terminate_line}")
with node.busy_wait_for_debug_log([terminate_line]):
+ extra_args = [*ALL_INDEX_ARGS]
+ if terminate_line == reindex_log_line:
+ extra_args += ['-reindex']
if platform.system() == 'Windows':
# CREATE_NEW_PROCESS_GROUP is required in order to be able
# to terminate the child without terminating the test.
- node.start(extra_args=ALL_INDEX_ARGS, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
+ node.start(extra_args=extra_args, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
else:
- node.start(extra_args=ALL_INDEX_ARGS)
+ node.start(extra_args=extra_args)
self.log.debug("Terminating node after terminate line was found")
sigterm_node()
Why this scored 30/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.