include/boost/corosio/detail/cancel_at_awaitable.hpp

95.8% Lines (46/48) 90.6% List of functions (29/32)
cancel_at_awaitable.hpp
f(x) Functions (32)
Function Calls Lines Blocks
boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::epoll_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::native_timer<boost::corosio::select_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 2x 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% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>::cancel_at_awaitable(boost::corosio::io_timer::wait_awaitable&&, boost::corosio::timer&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >) :88 18x 100.0% 86.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::epoll_t{}>, true>::cancel_at_awaitable(boost::corosio::io_timer::wait_awaitable&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >) :97 3x 100.0% 70.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::select_t{}>, true>::cancel_at_awaitable(boost::corosio::io_timer::wait_awaitable&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >) :97 3x 100.0% 70.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::cancel_at_awaitable(boost::corosio::io_timer::wait_awaitable&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >) :97 6x 100.0% 70.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::epoll_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::native_timer<boost::corosio::select_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 36x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::~cancel_at_awaitable() :104 12x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::epoll_t{}>, true>::cancel_at_awaitable(boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::epoll_t{}>, 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::select_t{}>, true>::cancel_at_awaitable(boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::select_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 18x 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 6x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::epoll_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::native_timer<boost::corosio::select_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 18x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::await_ready() const :123 6x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::epoll_t{}>, true>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :128 3x 86.7% 72.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::select_t{}>, true>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :128 3x 86.7% 72.0% 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 18x 100.0% 86.0% 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 6x 86.7% 72.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::epoll_t{}>, 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::select_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 18x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::await_resume() :175 6x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::epoll_t{}>, true>::destroy_parent_cb() :183 9x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::native_timer<boost::corosio::select_t{}>, true>::destroy_parent_cb() :183 9x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, false>::destroy_parent_cb() :183 54x 100.0% 100.0% boost::corosio::detail::cancel_at_awaitable<boost::corosio::io_timer::wait_awaitable, boost::corosio::timer, true>::destroy_parent_cb() :183 18x 100.0% 100.0%
Line 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 2x void operator()() const noexcept
69 {
70 2x src_->request_stop();
71 2x }
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 18x cancel_at_awaitable(A&& inner, Timer& timer, time_point deadline)
89 requires(!Owning)
90 18x : inner_(std::move(inner))
91 18x , timer_(&timer)
92 18x , deadline_(deadline)
93 {
94 18x }
95
96 /// Construct without a timer (created in `await_suspend`).
97 12x cancel_at_awaitable(A&& inner, time_point deadline)
98 requires Owning
99 12x : inner_(std::move(inner))
100 12x , deadline_(deadline)
101 {
102 12x }
103
104 60x ~cancel_at_awaitable()
105 {
106 60x destroy_parent_cb();
107 60x }
108
109 // Only moved before await_suspend, when cb_active_ is false
110 30x cancel_at_awaitable(cancel_at_awaitable&& o) noexcept(
111 std::is_nothrow_move_constructible_v<A>)
112 30x : inner_(std::move(o.inner_))
113 30x , timer_(std::move(o.timer_))
114 30x , deadline_(o.deadline_)
115 30x , stop_src_(std::move(o.stop_src_))
116 {
117 30x }
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 30x bool await_ready() const noexcept
124 {
125 30x return false;
126 }
127
128 30x 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 12x 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 30x timer_->expires_at(deadline_);
155
156 // Launch fire-and-forget timeout (starts suspended)
157 30x auto timeout = make_timeout(*timer_, stop_src_);
158 60x timeout.h_.promise().set_env_owned(
159 30x {env->executor, stop_src_.get_token(), env->frame_allocator});
160 // Runs synchronously until timer.wait() suspends
161 30x 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 30x new (cb_buf_) stop_cb_type(env->stop_token, stop_forwarder{&stop_src_});
167 30x cb_active_ = true;
168
169 // Start the inner op with our interposed stop_token
170 30x inner_env_ = {
171 30x env->executor, stop_src_.get_token(), env->frame_allocator};
172 60x return inner_.await_suspend(h, &inner_env_);
173 60x }
174
175 30x decltype(auto) await_resume()
176 {
177 // Cancel whichever is still pending (idempotent)
178 30x stop_src_.request_stop();
179 30x destroy_parent_cb();
180 30x return inner_.await_resume();
181 }
182
183 90x void destroy_parent_cb() noexcept
184 {
185 90x if (cb_active_)
186 {
187 30x std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_))
188 30x ->~stop_cb_type();
189 30x cb_active_ = false;
190 }
191 90x }
192 };
193
194 } // namespace boost::corosio::detail
195
196 #endif
197