TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Steve Gerbino
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/corosio
8 : //
9 :
10 : #ifndef BOOST_COROSIO_DETAIL_CANCEL_AT_AWAITABLE_HPP
11 : #define BOOST_COROSIO_DETAIL_CANCEL_AT_AWAITABLE_HPP
12 :
13 : #include <boost/corosio/detail/timeout_coro.hpp>
14 : #include <boost/capy/ex/io_env.hpp>
15 :
16 : #include <chrono>
17 : #include <coroutine>
18 : #include <new>
19 : #include <optional>
20 : #include <stop_token>
21 : #include <type_traits>
22 : #include <utility>
23 :
24 : /* Races an inner IoAwaitable against a timer via a shared
25 : stop_source. await_suspend arms the timer by launching a
26 : fire-and-forget timeout_coro, then starts the inner op with
27 : an interposed stop_token. Whichever completes first signals
28 : the stop_source, cancelling the other.
29 :
30 : Parent cancellation is forwarded through a stop_callback
31 : stored in a placement-new buffer (stop_callback is not
32 : movable, but the awaitable must be movable for
33 : transform_awaiter). The buffer is inert during moves
34 : (before await_suspend) and constructed in-place once the
35 : awaitable is pinned on the coroutine frame.
36 :
37 : The timeout_coro can outlive this awaitable — it owns its
38 : env and self-destroys via suspend_never. When Owning is
39 : false the caller-supplied timer must outlive both; when
40 : Owning is true the timer lives in std::optional and is
41 : constructed lazily in await_suspend. */
42 :
43 : namespace boost::corosio::detail {
44 :
45 : /** Awaitable adapter that cancels an inner operation after a deadline.
46 :
47 : Races the inner awaitable against a timer. A shared stop_source
48 : ties them together: whichever completes first cancels the other.
49 : Parent cancellation is forwarded via stop_callback.
50 :
51 : When @p Owning is `false` (default), the caller supplies a timer
52 : reference that must outlive the awaitable. When @p Owning is
53 : `true`, the timer is constructed internally in `await_suspend`
54 : from the execution context in `io_env`.
55 :
56 : @tparam A The inner IoAwaitable type (decayed).
57 : @tparam Timer The timer type (`timer` or `native_timer<B>`).
58 : @tparam Owning When `true`, the awaitable owns its timer.
59 : */
60 : template<typename A, typename Timer, bool Owning = false>
61 : struct cancel_at_awaitable
62 : {
63 : struct stop_forwarder
64 : {
65 : std::stop_source* src_;
66 HIT 2 : void operator()() const noexcept
67 : {
68 2 : src_->request_stop();
69 2 : }
70 : };
71 :
72 : using time_point = std::chrono::steady_clock::time_point;
73 : using stop_cb_type = std::stop_callback<stop_forwarder>;
74 : using timer_storage = std::conditional_t<
75 : Owning, std::optional<Timer>, Timer*>;
76 :
77 : A inner_;
78 : timer_storage timer_;
79 : time_point deadline_;
80 : std::stop_source stop_src_;
81 : capy::io_env inner_env_;
82 : alignas(stop_cb_type) unsigned char cb_buf_[sizeof(stop_cb_type)];
83 : bool cb_active_ = false;
84 :
85 : /// Construct with a caller-supplied timer reference.
86 18 : cancel_at_awaitable(
87 : A&& inner,
88 : Timer& timer,
89 : time_point deadline)
90 : requires (!Owning)
91 18 : : inner_(std::move(inner))
92 18 : , timer_(&timer)
93 18 : , deadline_(deadline)
94 : {
95 18 : }
96 :
97 : /// Construct without a timer (created in `await_suspend`).
98 6 : cancel_at_awaitable(
99 : A&& inner,
100 : time_point deadline)
101 : requires Owning
102 6 : : inner_(std::move(inner))
103 6 : , deadline_(deadline)
104 : {
105 6 : }
106 :
107 48 : ~cancel_at_awaitable()
108 : {
109 48 : destroy_parent_cb();
110 48 : }
111 :
112 : // Only moved before await_suspend, when cb_active_ is false
113 24 : cancel_at_awaitable(cancel_at_awaitable&& o) noexcept(
114 : std::is_nothrow_move_constructible_v<A>)
115 24 : : inner_(std::move(o.inner_))
116 24 : , timer_(std::move(o.timer_))
117 24 : , deadline_(o.deadline_)
118 24 : , stop_src_(std::move(o.stop_src_))
119 : {
120 24 : }
121 :
122 : cancel_at_awaitable(cancel_at_awaitable const&) = delete;
123 : cancel_at_awaitable& operator=(cancel_at_awaitable const&) = delete;
124 : cancel_at_awaitable& operator=(cancel_at_awaitable&&) = delete;
125 :
126 24 : bool await_ready() const noexcept { return false; }
127 :
128 24 : auto await_suspend(
129 : std::coroutine_handle<> h,
130 : capy::io_env const* env)
131 : {
132 : if constexpr (Owning)
133 6 : timer_.emplace(env->executor.context());
134 :
135 24 : timer_->expires_at(deadline_);
136 :
137 : // Launch fire-and-forget timeout (starts suspended)
138 24 : auto timeout = make_timeout(*timer_, stop_src_);
139 48 : timeout.h_.promise().set_env_owned({
140 : env->executor,
141 : stop_src_.get_token(),
142 24 : env->frame_allocator});
143 : // Runs synchronously until timer.wait() suspends
144 24 : timeout.h_.resume();
145 : // timeout goes out of scope; destructor is a no-op,
146 : // the coroutine self-destroys via suspend_never
147 :
148 : // Forward parent cancellation
149 24 : new (cb_buf_) stop_cb_type(
150 24 : env->stop_token, stop_forwarder{&stop_src_});
151 24 : cb_active_ = true;
152 :
153 : // Start the inner op with our interposed stop_token
154 24 : inner_env_ = {
155 : env->executor,
156 : stop_src_.get_token(),
157 24 : env->frame_allocator};
158 48 : return inner_.await_suspend(h, &inner_env_);
159 48 : }
160 :
161 24 : decltype(auto) await_resume()
162 : {
163 : // Cancel whichever is still pending (idempotent)
164 24 : stop_src_.request_stop();
165 24 : destroy_parent_cb();
166 24 : return inner_.await_resume();
167 : }
168 :
169 72 : void destroy_parent_cb() noexcept
170 : {
171 72 : if (cb_active_)
172 : {
173 : std::launder(reinterpret_cast<stop_cb_type*>(
174 24 : cb_buf_))->~stop_cb_type();
175 24 : cb_active_ = false;
176 : }
177 72 : }
178 : };
179 :
180 : } // namespace boost::corosio::detail
181 :
182 : #endif
|