diff --git a/src/ex/thread_pool.cpp b/src/ex/thread_pool.cpp index 62e85cd62..7327c83b8 100644 --- a/src/ex/thread_pool.cpp +++ b/src/ex/thread_pool.cpp @@ -149,8 +149,10 @@ class thread_pool::impl { std::lock_guard lock(mutex_); push(&c); + // Under the lock so the pool cannot drain, join, and + // destroy the condition variable mid-signal. + work_cv_.notify_one(); } - work_cv_.notify_one(); } void diff --git a/test/unit/ex/thread_pool.cpp b/test/unit/ex/thread_pool.cpp index 25d561f1b..4ba750c08 100644 --- a/test/unit/ex/thread_pool.cpp +++ b/test/unit/ex/thread_pool.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -697,6 +698,36 @@ struct thread_pool_test BOOST_TEST_EQ(result.load(), 42); } + static task + hop_and_count( + thread_pool::executor_type worker_ex, std::atomic& count) + { + count += co_await boost::capy::run(worker_ex)(returns_int(1)); + } + + void + testForeignPostAfterJoin() + { + // The worker pool's thread posts the continuation back into a + // pool that joins and dies immediately after the work drains, + // exercising the window where the poster is still inside + // post() during destruction. + thread_pool worker(1); + auto worker_ex = worker.get_executor(); + std::atomic count{0}; + + for(int i = 0; i < 50; ++i) + { + thread_pool pool(1); + run_async(pool.get_executor())( + hop_and_count(worker_ex, count)); + pool.join(); + } + + BOOST_TEST_EQ(count.load(), 50); + worker.join(); + } + void run() { @@ -726,6 +757,7 @@ struct thread_pool_test testStopCallbackRepeated(); testWorkGuardKeepsPoolAlive(); testJoinWithRunAsync(); + testForeignPostAfterJoin(); } };