Merge bitcoin/bitcoin#36148: test: Avoid unsafe memory race in index_reorg_crash shutdown
What changed, and why it matters
This is a fix for a flaky test in Bitcoin Core, not a fix for the Bitcoin network or wallet software itself. The test sometimes crashed under memory-safety checkers because it shut down an index while background validation events were still in flight. The patch drains those pending events before shutdown, similar to what the real shutdown code does. It does not affect live node behavior or user funds.
No production action needed. Developers running CI or the affected tests should verify the tests no longer flake under TSan/ASan. The fix can be treated as a routine test robustness improvement.
Security signals we found
Race condition in test teardown
Use of TSan/ASan-detected undefined behavior in test code
Missing synchronization with validation interface queue before object destruction
Test-only fix, no production code changed
Evidence from the diff
The commit modifies src/test/baseindex_tests.cpp only. It adds calls to SyncWithValidationInterfaceQueue() before destroying index objects in two unit tests (baseindex_no_commit_ahead_of_flush and index_reorg_crash). This drains pending BlockConnected/validation-interface callbacks so the index destructor does not race with in-flight events. A small assertion about the synced state is also added. The change is test-only and mirrors the real Shutdown() sequence in init.cpp.
Changed components
src/test/baseindex_tests.cppbaseindex_no_commit_ahead_of_flush unit testindex_reorg_crash unit testInspect captured patch +11 / −0
### src/test/baseindex_tests.cpp
@@ -84,6 +84,10 @@ BOOST_FIXTURE_TEST_CASE(baseindex_no_commit_ahead_of_flush, TestChain100Setup)
// Reload index to see which block data was actually committed.
BOOST_REQUIRE(index->Init());
BOOST_CHECK_EQUAL(index->GetSummary().best_block_height, expected_commit_height);
+
+ // Drain in-flight validation callbacks before destroying the index.
+ m_node.chain->context()->validation_signals->SyncWithValidationInterfaceQueue();
+ // shutdown sequence (c.f. Shutdown() in init.cpp)
index->Stop();
};
@@ -223,10 +227,17 @@ BOOST_FIXTURE_TEST_CASE(index_reorg_crash, TestChain100Setup)
BOOST_REQUIRE(m_node.chainman->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
}
+ // The index thread is blocked and not done
+ BOOST_CHECK(!index.GetSummary().synced);
+
// Unblock the index thread so it can process the reorg
promise.set_value();
// Wait for the index to reach the new tip
func_wait_until(blocking_height + 2, 5s);
+
+ // Drain unused BlockConnected events, to avoid unsafe memory races during destruction
+ m_node.chain->context()->validation_signals->SyncWithValidationInterfaceQueue();
+ // shutdown sequence (c.f. Shutdown() in init.cpp)
index.Stop();
}
Why this scored 17/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.