Skip to content

Commit ca58a13

Browse files
committed
fix: fix a race condition with cancellation
1 parent a6a2757 commit ca58a13

1 file changed

Lines changed: 34 additions & 19 deletions

File tree

  • libs/internal/include/launchdarkly/async

libs/internal/include/launchdarkly/async/timer.hpp

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,43 @@ Future<bool> Delay(boost::asio::any_io_executor executor,
2929
Promise<bool> promise;
3030
auto future = promise.GetFuture();
3131

32-
// cancel() is dispatched through post() rather than called directly to
33-
// satisfy ASIO's thread-safety requirement: steady_timer operations must
34-
// not be called concurrently from multiple threads.
35-
//
36-
// The callback is registered after async_wait is in flight so that an
37-
// already-cancelled token cancels the in-progress operation rather than
38-
// firing before async_wait has been called.
39-
//
40-
// The CancellationCallback is captured by the async_wait handler so its
41-
// lifetime matches the timer's; it is released (and deregistered) when
42-
// the handler fires.
43-
auto cb = std::make_shared<CancellationCallback>(
44-
std::move(token), [timer, executor] {
45-
boost::asio::post(executor, [timer] { timer->cancel(); });
46-
});
47-
48-
timer->async_wait([p = std::move(promise), timer,
49-
cb](boost::system::error_code code) mutable {
50-
cb.reset();
32+
// This code is tricky because there are a few constraints that conflict.
33+
// 1. We need to make sure timer->cancel isn't called _before_
34+
// timer->async_wait, or else it'll just be ignored.
35+
// 2. The cancellation_callback has to be created _before_
36+
// timer->async_wait, because it has to be captured by async_wait's
37+
// handler, because it is an RAII type, and once it is destroyed, it
38+
// deregisters itself. It has to stay alive as long as the timer needs to
39+
// be cancellable.
40+
41+
Promise<std::monostate> timer_started_promise;
42+
Future<std::monostate> timer_started_future =
43+
timer_started_promise.GetFuture();
44+
45+
auto cancel_timer = [timer, executor,
46+
timer_started_future =
47+
std::move(timer_started_future)]() mutable {
48+
timer_started_future.Then(
49+
[timer](auto const&) -> std::monostate {
50+
timer->cancel();
51+
return {};
52+
},
53+
[executor](Continuation<void()> f) {
54+
boost::asio::post(executor, f);
55+
});
56+
};
57+
58+
auto cancellation_callback =
59+
std::make_shared<CancellationCallback>(std::move(token), cancel_timer);
60+
61+
timer->async_wait([p = std::move(promise), timer, cancellation_callback](
62+
boost::system::error_code code) mutable {
63+
cancellation_callback.reset();
5164
p.Resolve(code != boost::asio::error::operation_aborted);
5265
});
5366

67+
timer_started_promise.Resolve({});
68+
5469
return future;
5570
}
5671

0 commit comments

Comments
 (0)