Skip to content

init: wake genesis wait after interrupted import - #451

Open
2rdzy wants to merge 1 commit into
bitcoinknots:29.x-knotsfrom
2rdzy:fix-reindex-init-deadlock
Open

2rdzy wants to merge 1 commit into
bitcoinknots:29.x-knotsfrom
2rdzy:fix-reindex-init-deadlock

Conversation

@2rdzy

@2rdzy 2rdzy commented Oct 8, 2026 •

Copy link
Copy Markdown

Summary

Backport of bitcoin#35652 ("init: fix reindex deadlock by waking cv after interrupt"). If shutdown is requested during -reindex while the init thread is waiting for genesis, the node never exits. The initload thread now wakes that wait after the import returns.

Based on v29.4 (3fc0865). The UpdateIBDStatus line that follows ImportBlocks on 29.x-knots comes from the lockless IBD backport and is not part of this change.

Problem

All line numbers are at v29.4.

  • src/init.cpp:1879-1882: the init thread waits on m_tip_block_cv for TipBlock() || ShutdownRequested(node).
  • src/node/blockstorage.cpp:1224-1226: after LoadExternalBlockFile, ImportBlocks returns early when m_interrupt is set, before ActivateBestChain. No blockTip fires, so genesis is never activated.
  • The only existing notify of that cv for shutdown is in node.shutdown_request (src/init.cpp:214-218). The SIGTERM handler (src/init.cpp:410-415) calls g_shutdown directly and does not go through shutdown_request.
  • Result: with SIGTERM during an interrupted reindex, nothing wakes the waiter and startup blocks forever.

Reproduction

  1. Apply the feature_init.py change from this PR (a -reindex start, terminated after Reindexing block file blk00000.dat).
  2. Add a test-only delay (below). The natural window is too small to hit on a 200-block regtest chain.
  3. Run timeout 100 python3 build/test/functional/feature_init.py.
Test-only delay (not part of this PR)
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -1223,2 +1223,3 @@
             LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile);
+            UninterruptibleSleep(std::chrono::seconds{3});
             chainman.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);

Before

20:58:24.705Z Reindexing block file blk00000.dat...
20:58:27.708Z Interrupt requested. Exit ImportBlocks
20:58:27.708Z initload thread exit

The test hit the 100 s timeout (rc=124). bitcoind was still running, with no Shutdown: In progress logged after the interrupt.

Change

@@ -1839,10 +1839,16 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
         vImportFiles.push_back(fs::PathFromString(strFile));
     }
 
-    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());
         if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {

After

Same repro with the delay:

21:00:16.276Z Reindexing block file blk00000.dat...
21:00:19.279Z Interrupt requested. Exit ImportBlocks
21:00:19.279Z Shutdown: In progress...
21:00:19.293Z Shutdown: done

The test passes in 14 s.

Test results

Tested at v29.4 (3fc0865), with and without this change. CMake Release build: wallet, GUI, GUI tests and unit tests on; BDB off.

  • ctest: 138/138 passed, before and after.
  • Functional --extended (324 tests): 253 passed, 65 skipped, 6 failed, before and after.
    • feature_init.py, feature_dbcrash.py, feature_pruning.py and feature_index_prune.py passed.
    • The same 6 tests failed in both full runs: wallet_groups.py --descriptors, feature_assumevalid.py, p2p_invalid_messages.py, rpc_createmultisig.py, wallet_miniscript.py --descriptors and p2p_blockfilters.py. Run on their own, all 6 pass, with and without this change.

Skips:

  • 53 legacy-wallet tests: BDB not compiled.
  • 5 interface_usdt_*: USDT tracepoints not built.
  • interface_zmq.py: zmq not enabled.
  • 4 tests need previous releases.
  • 2 feature_bind_port_*: need routable addresses 1.1.1.1 and 2.2.2.2.

Caveats

  • No deterministic regression test: the committed test exercises the path but cannot reliably hit the race without the test-only delay, so it also passes on the unfixed base.
  • Not tested: RPC stop and GUI shutdown, which go through shutdown_request and already notify.
  • Not run on Windows.

@kwsantiago

Copy link
Copy Markdown

Please rebase onto the oldest commit this applies to rather than the 29.x-knots tip, which the spec assembler cannot merge. v29.4 works, leaving out the UpdateIBDStatus line, which is context from the lockless IBD backport.

An interrupted import may return from ImportBlocks without activating
genesis. The init thread's wait for genesis is only woken by blockTip,
which never fires in that case, so a shutdown request arriving during
-reindex can leave startup blocked forever. Notify m_tip_block_cv after
the import so the wait can observe the shutdown request.

Adapted from bitcoin#35652.
@2rdzy
2rdzy force-pushed the fix-reindex-init-deadlock branch from cb36ddc to c1afd1c Compare October 12, 2026 21:15
@2rdzy

2rdzy commented Oct 12, 2026

Copy link
Copy Markdown
Author

Rebased onto v29.4 without the UpdateIBDStatus context line. Also dropped the assert_equal(0, node.process.wait()) change: it is from bitcoin#34241, not bitcoin#35652, and fails on v29.4 without bitcoin#34224. Re-tested on that base; description updated.

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.

2 participants