test: ensure Stop() thread helps drain the queue
What changed, and why it matters
This commit adds a new automated test for Bitcoin Core's internal thread pool. It checks that when the thread pool is stopped while tasks are still waiting, the thread that calls Stop() helps finish those remaining tasks. There is no change to production code and no security fix or vulnerability is described.
No security action needed. Treat as routine test coverage improvement. If reviewing the related ThreadPool::Stop() implementation, consider whether the behavior tested is intentional and documented.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds BOOST_AUTO_TEST_CASE(stop_active_wait_drains_queue) to src/test/threadpool_tests.cpp. It fills all worker threads with blocking tasks, queues 20 additional tasks, then calls ThreadPool::Stop(). The test asserts that the calling (main) thread executes all 20 queued tasks, confirming Stop() participates in draining the work queue. No ThreadPool implementation code is modified.
Changed components
src/test/threadpool_tests.cppInspect captured patch +37 / −0
diff --git a/src/test/threadpool_tests.cpp b/src/test/threadpool_tests.cpp
index 4855b334..c7878c35 100644
--- a/src/test/threadpool_tests.cpp
+++ b/src/test/threadpool_tests.cpp
@@ -39,6 +39,7 @@ struct ThreadPoolFixture {
// 10) Ensure Interrupt() prevents further submissions.
// 11) Start() must not cause a deadlock when called during Stop().
// 12) Ensure queued tasks complete after Interrupt().
+// 13) Ensure the Stop() calling thread helps drain the queue.
BOOST_FIXTURE_TEST_SUITE(threadpool_tests, ThreadPoolFixture)
#define WAIT_FOR(futures) \
@@ -380,4 +381,40 @@ BOOST_AUTO_TEST_CASE(queued_tasks_complete_after_interrupt)
WAIT_FOR(blocking_tasks);
}
+// Test 13, ensure the Stop() calling thread helps drain the queue
+BOOST_AUTO_TEST_CASE(stop_active_wait_drains_queue)
+{
+ ThreadPool threadPool(POOL_NAME);
+ threadPool.Start(NUM_WORKERS_DEFAULT);
+
+ std::counting_semaphore<> blocker(0);
+ const auto blocking_tasks = BlockWorkers(threadPool, blocker, NUM_WORKERS_DEFAULT);
+
+ auto main_thread_id = std::this_thread::get_id();
+ std::atomic<int> main_thread_tasks{0};
+ const size_t num_tasks = 20;
+ for (size_t i = 0; i < num_tasks; i++) {
+ (void)Submit(threadPool, [&main_thread_tasks, main_thread_id]() {
+ if (std::this_thread::get_id() == main_thread_id)
+ main_thread_tasks.fetch_add(1, std::memory_order_relaxed);
+ });
+ }
+ BOOST_CHECK_EQUAL(threadPool.WorkQueueSize(), num_tasks);
+
+ // Delay release so Stop() drain all tasks from the calling thread
+ std::thread unblocker([&blocker, &threadPool]() {
+ while (threadPool.WorkQueueSize() > 0) {
+ std::this_thread::yield();
+ }
+ blocker.release(NUM_WORKERS_DEFAULT);
+ });
+
+ threadPool.Stop();
+ unblocker.join();
+
+ // Check the main thread processed all tasks
+ BOOST_CHECK_EQUAL(main_thread_tasks.load(), num_tasks);
+ WAIT_FOR(blocking_tasks);
+}
+
BOOST_AUTO_TEST_SUITE_END()
Why this scored 12/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.