From c5c38947627d3d9645387a1ac13b6657c9f2eb97 Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Thu, 12 Mar 2026 09:47:12 -0600 Subject: [PATCH] Fix timer_service crash when use_service races on multi-threaded pool When multiple pool threads call use_service() concurrently, the double-checked lock in use_service_impl can create a duplicate that is immediately deleted. Without a destructor, the std::thread member is destroyed non-joined, calling std::terminate(). Add ~timer_service() that calls shutdown() to join the background thread, and a concurrent delay test that reproduces the bug. --- .../boost/capy/ex/detail/timer_service.hpp | 6 ++++ src/ex/detail/timer_service.cpp | 15 +++++++++- test/unit/delay.cpp | 28 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/include/boost/capy/ex/detail/timer_service.hpp b/include/boost/capy/ex/detail/timer_service.hpp index b29410e3..6b93ffbf 100644 --- a/include/boost/capy/ex/detail/timer_service.hpp +++ b/include/boost/capy/ex/detail/timer_service.hpp @@ -47,6 +47,11 @@ class BOOST_CAPY_DECL explicit timer_service(execution_context& ctx); + // Calls shutdown() to join the background thread. + // Handles the discard path in use_service_impl where + // a duplicate service is deleted without shutdown(). + ~timer_service(); + /** Schedule a callback to fire after a duration. The callback is invoked on the timer service's background @@ -79,6 +84,7 @@ class BOOST_CAPY_DECL void shutdown() override; private: + void stop_and_join(); struct entry { std::chrono::steady_clock::time_point deadline; diff --git a/src/ex/detail/timer_service.cpp b/src/ex/detail/timer_service.cpp index 75f34b4e..995a6d3e 100644 --- a/src/ex/detail/timer_service.cpp +++ b/src/ex/detail/timer_service.cpp @@ -20,6 +20,12 @@ timer_service(execution_context& ctx) (void)ctx; } +timer_service:: +~timer_service() +{ + stop_and_join(); +} + timer_service::timer_id timer_service:: schedule_at( @@ -54,7 +60,7 @@ cancel(timer_id id) void timer_service:: -shutdown() +stop_and_join() { { std::lock_guard lock(mutex_); @@ -65,6 +71,13 @@ shutdown() thread_.join(); } +void +timer_service:: +shutdown() +{ + stop_and_join(); +} + void timer_service:: run() diff --git a/test/unit/delay.cpp b/test/unit/delay.cpp index 1a46d2f1..010672b8 100644 --- a/test/unit/delay.cpp +++ b/test/unit/delay.cpp @@ -241,6 +241,33 @@ struct delay_test #endif } + // Test: concurrent delays on a multi-threaded pool + // exercises use_service race and shared timer_service + void + testConcurrentDelays() + { + constexpr int N = 10; + thread_pool pool(4); + std::latch done(N); + + auto delay_task = [](int i) -> task + { + co_await delay(10ms * i); + }; + + for(int i = 0; i < N; ++i) + { + run_async(pool.get_executor(), + [&]() { done.count_down(); }, + [&](std::exception_ptr) { + done.count_down(); + })(delay_task(i)); + } + + done.wait(); + BOOST_TEST(true); + } + void run() { @@ -251,6 +278,7 @@ struct delay_test testZeroDuration(); testSequentialDelays(); testDestroyWhileSuspended(); + testConcurrentDelays(); } };