Skip to content

Commit ecdb076

Browse files
committed
Fix timer_service crash when use_service races on multi-threaded pool
When multiple pool threads call use_service<timer_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.
1 parent 18c30d2 commit ecdb076

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

include/boost/capy/ex/detail/timer_service.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ class BOOST_CAPY_DECL
4747

4848
explicit timer_service(execution_context& ctx);
4949

50+
// Calls shutdown() to join the background thread.
51+
// Handles the discard path in use_service_impl where
52+
// a duplicate service is deleted without shutdown().
53+
~timer_service();
54+
5055
/** Schedule a callback to fire after a duration.
5156
5257
The callback is invoked on the timer service's background
@@ -79,6 +84,7 @@ class BOOST_CAPY_DECL
7984
void shutdown() override;
8085

8186
private:
87+
void stop_and_join();
8288
struct entry
8389
{
8490
std::chrono::steady_clock::time_point deadline;

src/ex/detail/timer_service.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ timer_service(execution_context& ctx)
2020
(void)ctx;
2121
}
2222

23+
timer_service::
24+
~timer_service()
25+
{
26+
stop_and_join();
27+
}
28+
2329
timer_service::timer_id
2430
timer_service::
2531
schedule_at(
@@ -54,7 +60,7 @@ cancel(timer_id id)
5460

5561
void
5662
timer_service::
57-
shutdown()
63+
stop_and_join()
5864
{
5965
{
6066
std::lock_guard lock(mutex_);
@@ -65,6 +71,13 @@ shutdown()
6571
thread_.join();
6672
}
6773

74+
void
75+
timer_service::
76+
shutdown()
77+
{
78+
stop_and_join();
79+
}
80+
6881
void
6982
timer_service::
7083
run()

test/unit/delay.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,33 @@ struct delay_test
241241
#endif
242242
}
243243

244+
// Test: concurrent delays on a multi-threaded pool
245+
// exercises use_service race and shared timer_service
246+
void
247+
testConcurrentDelays()
248+
{
249+
constexpr int N = 10;
250+
thread_pool pool(4);
251+
std::latch done(N);
252+
253+
auto delay_task = [](int i) -> task<void>
254+
{
255+
co_await delay(10ms * i);
256+
};
257+
258+
for(int i = 0; i < N; ++i)
259+
{
260+
run_async(pool.get_executor(),
261+
[&]() { done.count_down(); },
262+
[&](std::exception_ptr) {
263+
done.count_down();
264+
})(delay_task(i));
265+
}
266+
267+
done.wait();
268+
BOOST_TEST(true);
269+
}
270+
244271
void
245272
run()
246273
{
@@ -251,6 +278,7 @@ struct delay_test
251278
testZeroDuration();
252279
testSequentialDelays();
253280
testDestroyWhileSuspended();
281+
testConcurrentDelays();
254282
}
255283
};
256284

0 commit comments

Comments
 (0)