include/boost/corosio/detail/cancel_at_awaitable.hpp

95.7% Lines (45/47) 91.7% List of functions (22/24) 86.7% Branches (13/15)
cancel_at_awaitable.hpp
f(x) Functions (24)
Function Calls Lines Branches Blocks
boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::iocp_t{}>, true>::stop_forwarder::operator()() const :68 0 0.0% 0.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>::stop_forwarder::operator()() const :68 1x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::stop_forwarder::operator()() const :68 0 0.0% 0.0% _ZN5boost7corosio6detail19cancel_at_awaitableINS0_8io_timer14wait_awaitableENS0_5timerELb0EEC1EOS4_RS5_NSt6chrono10time_pointINS9_3_V212steady_clockENS9_8durationIxSt5ratioILx1ELx1000000000EEEEEEQntT1_ :88 9x 100.0% 100.0% 85.7% _ZN5boost7corosio6detail19cancel_at_awaitableINS0_8io_timer14wait_awaitableENS0_12native_timerIXtlNS0_6iocp_tEEEEELb1EEC1EOS4_NSt6chrono10time_pointINSA_3_V212steady_clockENSA_8durationIxSt5ratioILx1ELx1000000000EEEEEEQT1_ :97 3x 100.0% 100.0% 70.0% _ZN5boost7corosio6detail19cancel_at_awaitableINS0_8io_timer14wait_awaitableENS0_5timerELb1EEC1EOS4_NSt6chrono10time_pointINS8_3_V212steady_clockENS8_8durationIxSt5ratioILx1ELx1000000000EEEEEEQT1_ :97 3x 100.0% 100.0% 70.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::iocp_t{}>, true>::~cancel_at_awaitable() :104 6x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>::~cancel_at_awaitable() :104 18x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::~cancel_at_awaitable() :104 6x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::iocp_t{}>, true>::cancel_at_awaitable(boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::iocp_t{}>, true>&&) :110 3x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>::cancel_at_awaitable(boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>&&) :110 9x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::cancel_at_awaitable(boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>&&) :110 3x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::iocp_t{}>, true>::await_ready() const :123 3x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>::await_ready() const :123 9x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::await_ready() const :123 3x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::iocp_t{}>, true>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :128 3x 85.7% 71.4% 71.9% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :128 9x 100.0% 100.0% 86.4% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :128 3x 85.7% 71.4% 71.9% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::iocp_t{}>, true>::await_resume() :175 3x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>::await_resume() :175 9x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::await_resume() :175 3x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::iocp_t{}>, true>::destroy_parent_cb() :183 9x 100.0% 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>::destroy_parent_cb() :183 27x 100.0% 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::destroy_parent_cb() :183 9x 100.0% 100.0% 100.0%
Line Branch TLA Hits 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/corosio/detail/except.hpp>
15 #include <boost/capy/ex/io_env.hpp>
16
17 #include <chrono>
18 #include <coroutine>
19 #include <new>
20 #include <optional>
21 #include <stdexcept>
22 #include <stop_token>
23 #include <type_traits>
24 #include <utility>
25
26 /* Races an inner IoAwaitable against a timer via a shared
27 stop_source. await_suspend arms the timer by launching a
28 fire-and-forget timeout_coro, then starts the inner op with
29 an interposed stop_token. Whichever completes first signals
30 the stop_source, cancelling the other.
31
32 Parent cancellation is forwarded through a stop_callback
33 stored in a placement-new buffer (stop_callback is not
34 movable, but the awaitable must be movable for
35 transform_awaiter). The buffer is inert during moves
36 (before await_suspend) and constructed in-place once the
37 awaitable is pinned on the coroutine frame.
38
39 The timeout_coro can outlive this awaitable — it owns its
40 env and self-destroys via suspend_never. When Owning is
41 false the caller-supplied timer must outlive both; when
42 Owning is true the timer lives in std::optional and is
43 constructed lazily in await_suspend. */
44
45 namespace boost::corosio::detail {
46
47 /** Awaitable adapter that cancels an inner operation after a deadline.
48
49 Races the inner awaitable against a timer. A shared stop_source
50 ties them together: whichever completes first cancels the other.
51 Parent cancellation is forwarded via stop_callback.
52
53 When @p Owning is `false` (default), the caller supplies a timer
54 reference that must outlive the awaitable. When @p Owning is
55 `true`, the timer is constructed internally in `await_suspend`
56 from the execution context in `io_env`.
57
58 @tparam A The inner IoAwaitable type (decayed).
59 @tparam Timer The timer type (`timer` or `native_timer<B>`).
60 @tparam Owning When `true`, the awaitable owns its timer.
61 */
62 template<typename A, typename Timer, bool Owning = false>
63 struct cancel_at_awaitable
64 {
65 struct stop_forwarder
66 {
67 std::stop_source* src_;
68 1x void operator()() const noexcept
69 {
70 1x src_->request_stop();
71 1x }
72 };
73
74 using time_point = std::chrono::steady_clock::time_point;
75 using stop_cb_type = std::stop_callback<stop_forwarder>;
76 using timer_storage =
77 std::conditional_t<Owning, std::optional<Timer>, Timer*>;
78
79 A inner_;
80 timer_storage timer_;
81 time_point deadline_;
82 std::stop_source stop_src_;
83 capy::io_env inner_env_;
84 alignas(stop_cb_type) unsigned char cb_buf_[sizeof(stop_cb_type)];
85 bool cb_active_ = false;
86
87 /// Construct with a caller-supplied timer reference.
88 9x cancel_at_awaitable(A&& inner, Timer& timer, time_point deadline)
89 requires(!Owning)
90 9x : inner_(std::move(inner))
91 9x , timer_(&timer)
92
1/1
✓ Branch 4 → 5 taken 9 times.
9x , deadline_(deadline)
93 {
94 9x }
95
96 /// Construct without a timer (created in `await_suspend`).
97 6x cancel_at_awaitable(A&& inner, time_point deadline)
98 requires Owning
99 6x : inner_(std::move(inner))
100
1/1
✓ Branch 5 → 6 taken 6 times.
6x , deadline_(deadline)
101 {
102 6x }
103
104 30x ~cancel_at_awaitable()
105 {
106 30x destroy_parent_cb();
107 30x }
108
109 // Only moved before await_suspend, when cb_active_ is false
110 15x cancel_at_awaitable(cancel_at_awaitable&& o) noexcept(
111 std::is_nothrow_move_constructible_v<A>)
112 15x : inner_(std::move(o.inner_))
113 15x , timer_(std::move(o.timer_))
114 15x , deadline_(o.deadline_)
115 15x , stop_src_(std::move(o.stop_src_))
116 {
117 15x }
118
119 cancel_at_awaitable(cancel_at_awaitable const&) = delete;
120 cancel_at_awaitable& operator=(cancel_at_awaitable const&) = delete;
121 cancel_at_awaitable& operator=(cancel_at_awaitable&&) = delete;
122
123 15x bool await_ready() const noexcept
124 {
125 15x return false;
126 }
127
128 15x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
129 {
130 if constexpr (Owning)
131 {
132 // The deadline timer is built here from the awaiting
133 // coroutine's executor context, the first point at which it
134 // is known. await_suspend is driven through a noexcept
135 // wrapper, so a failure cannot be surfaced as a catchable
136 // exception. An executor whose context is not an io_context
137 // cannot supply a timer service; silently running the
138 // operation with no deadline would be a worse failure than
139 // aborting, so translate the service-lookup error into a
140 // clear precondition diagnostic. This terminates by design
141 // (a usage error) rather than dropping the requested timeout.
142 try
143 {
144
1/1
✓ Branch 3 → 4 taken 6 times.
6x timer_.emplace(env->executor.context());
145 }
146 catch (std::logic_error const&)
147 {
148 throw_logic_error(
149 "cancel_after/cancel_at requires an "
150 "io_context-backed executor");
151 }
152 }
153
154
2/2
✓ Branch 2 → 3 taken 9 times.
✓ Branch 5 → 6 taken 6 times.
15x timer_->expires_at(deadline_);
155
156 // Launch fire-and-forget timeout (starts suspended)
157
2/2
✓ Branch 4 → 5 taken 9 times.
✓ Branch 8 → 9 taken 6 times.
15x auto timeout = make_timeout(*timer_, stop_src_);
158 30x timeout.h_.promise().set_env_owned(
159 15x {env->executor, stop_src_.get_token(), env->frame_allocator});
160 // Runs synchronously until timer.wait() suspends
161
2/2
✓ Branch 10 → 11 taken 9 times.
✓ Branch 14 → 15 taken 6 times.
15x timeout.h_.resume();
162 // timeout goes out of scope; destructor is a no-op,
163 // the coroutine self-destroys via suspend_never
164
165 // Forward parent cancellation
166 15x new (cb_buf_) stop_cb_type(env->stop_token, stop_forwarder{&stop_src_});
167 15x cb_active_ = true;
168
169 // Start the inner op with our interposed stop_token
170 inner_env_ = {
171 15x env->executor, stop_src_.get_token(), env->frame_allocator};
172
2/2
✓ Branch 16 → 17 taken 9 times.
✓ Branch 20 → 21 taken 6 times.
30x return inner_.await_suspend(h, &inner_env_);
173 30x }
174
175 15x decltype(auto) await_resume()
176 {
177 // Cancel whichever is still pending (idempotent)
178 15x stop_src_.request_stop();
179 15x destroy_parent_cb();
180 15x return inner_.await_resume();
181 }
182
183 45x void destroy_parent_cb() noexcept
184 {
185
2/2
✓ Branch 2 → 3 taken 15 times.
✓ Branch 2 → 6 taken 30 times.
45x if (cb_active_)
186 {
187 15x std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_))
188 15x ->~stop_cb_type();
189 15x cb_active_ = false;
190 }
191 45x }
192 };
193
194 } // namespace boost::corosio::detail
195
196 #endif
197