100.00% Lines (229/229)
100.00% Functions (53/53)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | // | 1 | // | |||||
| 2 | // Copyright (c) 2026 Michael Vandeberg | 2 | // Copyright (c) 2026 Michael Vandeberg | |||||
| 3 | // | 3 | // | |||||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | 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) | 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||||
| 6 | // | 6 | // | |||||
| 7 | // Official repository: https://github.com/cppalliance/capy | 7 | // Official repository: https://github.com/cppalliance/capy | |||||
| 8 | // | 8 | // | |||||
| 9 | 9 | |||||||
| 10 | #ifndef BOOST_CAPY_QUITTER_HPP | 10 | #ifndef BOOST_CAPY_QUITTER_HPP | |||||
| 11 | #define BOOST_CAPY_QUITTER_HPP | 11 | #define BOOST_CAPY_QUITTER_HPP | |||||
| 12 | 12 | |||||||
| 13 | - | #include <boost/capy/detail/stop_requested_exception.hpp> | ||||||
| 14 | - | #include <boost/capy/cond.hpp> | ||||||
| 15 | - | #include <boost/capy/results.hpp> | ||||||
| 16 | #include <boost/capy/detail/config.hpp> | 13 | #include <boost/capy/detail/config.hpp> | |||||
| 17 | #include <boost/capy/concept/executor.hpp> | 14 | #include <boost/capy/concept/executor.hpp> | |||||
| 18 | #include <boost/capy/concept/context_awaitable.hpp> | 15 | #include <boost/capy/concept/context_awaitable.hpp> | |||||
| 16 | + | #include <boost/capy/cond.hpp> | ||||||
| 17 | + | #include <boost/capy/continuation.hpp> | ||||||
| 18 | + | #include <boost/capy/error.hpp> | ||||||
| 19 | + | #include <boost/capy/ex/abandon.hpp> | ||||||
| 19 | #include <boost/capy/ex/context_awaitable_promise_base.hpp> | 20 | #include <boost/capy/ex/context_awaitable_promise_base.hpp> | |||||
| 20 | #include <boost/capy/ex/await_context.hpp> | 21 | #include <boost/capy/ex/await_context.hpp> | |||||
| 22 | + | #include <boost/capy/ex/frame_alloc_mixin.hpp> | ||||||
| 21 | #include <boost/capy/ex/frame_allocator.hpp> | 23 | #include <boost/capy/ex/frame_allocator.hpp> | |||||
| 22 | #include <boost/capy/detail/await_suspend_helper.hpp> | 24 | #include <boost/capy/detail/await_suspend_helper.hpp> | |||||
| 25 | + | #include <boost/capy/detail/quitter_stopped.hpp> | ||||||
| 26 | + | #include <boost/capy/results.hpp> | ||||||
| 23 | 27 | |||||||
| 28 | + | #include <coroutine> | ||||||
| 24 | #include <exception> | 29 | #include <exception> | |||||
| 25 | #include <optional> | 30 | #include <optional> | |||||
| 26 | - | #include <tuple> | 31 | + | #include <system_error> | |||
| 27 | #include <type_traits> | 32 | #include <type_traits> | |||||
| 28 | #include <utility> | 33 | #include <utility> | |||||
| 29 | 34 | |||||||
| 30 | /* Stop-aware coroutine task. | 35 | /* Stop-aware coroutine task. | |||||
| 31 | 36 | |||||||
| 32 | - | quitter<T> is identical to task<T> except that a stop request ends | 37 | + | quitter<T> is task<T>, except that a stop request ends its frames | |||
| 33 | - | the body without the body checking an error code. The promise's | 38 | + | by abandonment instead of resuming them. Nothing in the body runs | |||
| 34 | - | transform_awaiter checks the stop token before it touches an awaited | 39 | + | after the stop, so nothing can intercept it, and no exception is | |||
| 35 | - | operation, and again when an operation that completed | 40 | + | thrown inside the quitter. | |||
| 36 | - | asynchronously resumes the body. A pending stop there throws a | ||||||
| 37 | - | sentinel exception that unwinds through RAII destructors to | ||||||
| 38 | - | final_suspend, and the parent sees a "stopped" completion. An | ||||||
| 39 | - | operation that completes synchronously always has its await_resume | ||||||
| 40 | - | called, because its await_ready or await_suspend may already have | ||||||
| 41 | - | acquired something. A results<> it returns holding cond::canceled | ||||||
| 42 | - | while the stop is pending acquired nothing, so the stop throws there; | ||||||
| 43 | - | any other result reaches the body, and the stop takes effect at the | ||||||
| 44 | - | next co_await. | ||||||
| 45 | 41 | |||||||
| 46 | - | See doc/quitter.md for the full design rationale. */ | 42 | + | Segment. A quitter awaited by anything other than a quitter opens a | |||
| 43 | + | segment in its await_suspend. The segment is one coroutine frame, | ||||||
| 44 | + | quitter_segment, that is both the segment's owner (its chain_owner is | ||||||
| 45 | + | the await_context::owner every frame in the segment sees) and the | ||||||
| 46 | + | gate. A quitter awaited by a quitter joins the parent's segment. A | ||||||
| 47 | + | quitter started directly by a launcher (run_async, run(ex)) runs in | ||||||
| 48 | + | the launcher's segment; it still gets a gate, and the launcher's | ||||||
| 49 | + | chain_owner is the one the gate abandons. The launcher creates the | ||||||
| 50 | + | gate with launch_handle() and resumes the gate, not the quitter, so | ||||||
| 51 | + | the gate checks the stop before the body starts. A launcher that | ||||||
| 52 | + | resumes the quitter's own handle instead gets a gate when the body | ||||||
| 53 | + | starts, with no such check. | ||||||
| 54 | + | |||||||
| 55 | + | Gate. A quitter frame never hands its own handle to an operation. Its | ||||||
| 56 | + | transform_awaiter records the frame as the gate's target and passes | ||||||
| 57 | + | the gate's handle instead, so an operation completes into the gate. | ||||||
| 58 | + | The gate then resumes the target, or, if a stop was requested, | ||||||
| 59 | + | abandons the segment with chain_owner::abandon_for_stop. The | ||||||
| 60 | + | operation has finished with the frame by then (the corosio rule: no | ||||||
| 61 | + | frame is destroyed while an operation holds its continuation). | ||||||
| 62 | + | |||||||
| 63 | + | Synchronous completions. The stop is checked before the | ||||||
| 64 | + | inner await_ready, so a pending stop starts no work. An inner | ||||||
| 65 | + | awaitable that completes without going through the gate (ready, | ||||||
| 66 | + | await_suspend returning false or the gate's own handle) always has | ||||||
| 67 | + | its await_resume called, so anything it acquired is handed over; the | ||||||
| 68 | + | stop takes effect at the next suspension. The exception is a | ||||||
| 69 | + | results<> holding cond::canceled while our stop is pending: the | ||||||
| 70 | + | inner awaitable saw the stop first and acquired nothing (async_mutex, | ||||||
| 71 | + | async_event and async_waker all answer a stop that lands between our | ||||||
| 72 | + | check and theirs this way), so the segment is abandoned there. That | ||||||
| 73 | + | result is taken early and kept in the awaiter, because await_resume | ||||||
| 74 | + | runs in the resumed body, too late to abandon. | ||||||
| 75 | + | |||||||
| 76 | + | Unwind. When the segment's owner is resumed after an abandon, it | ||||||
| 77 | + | releases the opening quitter's handle and destroys the top frame, | ||||||
| 78 | + | which destroys the frames below it. After a stop it then resumes the | ||||||
| 79 | + | parent, whose co_await yields the stopped value (stopped_value<T>). | ||||||
| 80 | + | After a teardown it abandons the parent, as run's boundary_trampoline | ||||||
| 81 | + | does. A segment frame destroyed instead of resumed (posted to a | ||||||
| 82 | + | closed executor) unwinds as teardown from its destructor. | ||||||
| 83 | + | |||||||
| 84 | + | Why the gate is never resumed while it runs. Its body is | ||||||
| 85 | + | `for(;;) co_await hop{}`, and every decision is made in | ||||||
| 86 | + | hop::await_suspend, after the frame has suspended. Its handle is | ||||||
| 87 | + | reachable only from the one operation a quitter frame of the segment | ||||||
| 88 | + | is waiting on, and from the owner's post, which happens only when no | ||||||
| 89 | + | operation holds it. Once await_suspend has handed the handle on, by | ||||||
| 90 | + | returning it, resuming a target inline or posting, it touches nothing | ||||||
| 91 | + | of the frame again, because another thread may already be running or | ||||||
| 92 | + | destroying it. */ | ||||||
| 47 | 93 | |||||||
| 48 | namespace boost { | 94 | namespace boost { | |||||
| 49 | namespace capy { | 95 | namespace capy { | |||||
| 50 | 96 | |||||||
| 97 | + | template<typename T> | ||||||
| 98 | + | struct quitter; | ||||||
| 99 | + | |||||||
| 51 | namespace detail { | 100 | namespace detail { | |||||
| 52 | - | // Reuse the same return-value storage as task<T>. | ||||||
| 53 | - | // task_return_base is defined in task.hpp, but quitter needs its own | ||||||
| 54 | - | // copy to avoid a header dependency on task.hpp. | ||||||
| 55 | 101 | |||||||
| 56 | template<typename T> | 102 | template<typename T> | |||||
| 57 | struct quitter_return_base | 103 | struct quitter_return_base | |||||
| 58 | { | 104 | { | |||||
| 59 | std::optional<T> result_; | 105 | std::optional<T> result_; | |||||
| 60 | 106 | |||||||
| HITCBC | 61 | 11 | void return_value(T value) | 107 | 5245 | void return_value(T value) | ||
| 62 | { | 108 | { | |||||
| HITCBC | 63 | 11 | result_ = std::move(value); | 109 | 5245 | result_ = std::move(value); | ||
| HITCBC | 64 | 11 | } | 110 | 5245 | } | ||
| 65 | 111 | |||||||
| HITCBC | 66 | 5 | T&& result() noexcept | 112 | 5 | T&& result() noexcept | ||
| 67 | { | 113 | { | |||||
| HITCBC | 68 | 5 | return std::move(*result_); | 114 | 5 | return std::move(*result_); | ||
| 69 | } | 115 | } | |||||
| 70 | }; | 116 | }; | |||||
| 71 | 117 | |||||||
| 72 | template<> | 118 | template<> | |||||
| 73 | struct quitter_return_base<void> | 119 | struct quitter_return_base<void> | |||||
| 74 | { | 120 | { | |||||
| HITCBC | 75 | 8 | void return_void() | 121 | 8 | void return_void() | ||
| 76 | { | 122 | { | |||||
| HITCBC | 77 | 8 | } | 123 | 8 | } | ||
| 78 | }; | 124 | }; | |||||
| 79 | 125 | |||||||
| 126 | + | /* What a stopped quitter<T> delivers to its parent. | ||||||
| 127 | + | |||||||
| 128 | + | results<Ts...>: canceled, with each payload value-initialized. | ||||||
| 129 | + | std::error_code: canceled. | ||||||
| 130 | + | Any other T, void included: quitter_stopped, a std::system_error | ||||||
| 131 | + | with canceled, thrown at the boundary. A void return would read as | ||||||
| 132 | + | success. */ | ||||||
| 133 | + | template<class T> | ||||||
| 134 | + | struct stopped_value | ||||||
| 135 | + | { | ||||||
| HITGNC | 136 | + | 29 | static T get() | ||||
| 137 | + | { | ||||||
| 138 | + | #if defined(__cpp_exceptions) | ||||||
| HITGNC | 139 | + | 29 | throw quitter_stopped(); | ||||
| 140 | + | #else | ||||||
| 141 | + | // sizeof(T*): T may be void. | ||||||
| 142 | + | static_assert(sizeof(T*) == 0, | ||||||
| 143 | + | "a stopped quitter<T> for a T other than results or " | ||||||
| 144 | + | "std::error_code reports the stop by throwing, " | ||||||
| 145 | + | "which needs exceptions"); | ||||||
| 146 | + | #endif | ||||||
| 147 | + | } | ||||||
| 148 | + | }; | ||||||
| 149 | + | |||||||
| 150 | + | template<class... Ts> | ||||||
| 151 | + | struct stopped_value<results<Ts...>> | ||||||
| 152 | + | { | ||||||
| 153 | + | static_assert( | ||||||
| 154 | + | (std::is_default_constructible_v<Ts> && ...), | ||||||
| 155 | + | "a stopped quitter<results<Ts...>> value-initializes every Ts"); | ||||||
| 156 | + | |||||||
| HITGNC | 157 | + | 4806 | static results<Ts...> get() | ||||
| 158 | + | { | ||||||
| HITGNC | 159 | + | 4806 | return results<Ts...>(make_error_code(error::canceled), Ts{}...); | ||||
| 160 | + | } | ||||||
| 161 | + | }; | ||||||
| 162 | + | |||||||
| 163 | + | template<> | ||||||
| 164 | + | struct stopped_value<std::error_code> | ||||||
| 165 | + | { | ||||||
| HITGNC | 166 | + | 1 | static std::error_code get() noexcept | ||||
| 167 | + | { | ||||||
| HITGNC | 168 | + | 1 | return make_error_code(error::canceled); | ||||
| 169 | + | } | ||||||
| 170 | + | }; | ||||||
| 171 | + | |||||||
| 172 | + | template<class T> | ||||||
| 173 | + | struct is_quitter : std::false_type {}; | ||||||
| 174 | + | |||||||
| 175 | + | template<class T> | ||||||
| 176 | + | struct is_quitter<quitter<T>> : std::true_type {}; | ||||||
| 177 | + | |||||||
| 178 | + | // A stop that can be carried out: requested, with an owner to abandon. | ||||||
| 179 | + | // With no owner a quitter cannot stop, and runs on as a task would; a | ||||||
| 180 | + | // launcher that runs quitters must bind a chain_owner. | ||||||
| 181 | + | inline bool | ||||||
| HITGNC | 182 | + | 20303 | quitter_stop_pending(await_context const* env) noexcept | ||||
| 183 | + | { | ||||||
| HITGNC | 184 | + | 20303 | if(!env->stop_token.stop_requested()) | ||||
| HITGNC | 185 | + | 15467 | return false; | ||||
| HITGNC | 186 | + | 4836 | BOOST_CAPY_ASSERT(env->owner && | ||||
| 187 | + | "a quitter's environment needs an owner to stop"); | ||||||
| HITGNC | 188 | + | 4836 | return env->owner != nullptr; | ||||
| 189 | + | } | ||||||
| 190 | + | |||||||
| 191 | + | struct quitter_opener; | ||||||
| 192 | + | |||||||
| 193 | + | // One per segment: the owner and the gate in one coroutine frame. | ||||||
| 194 | + | struct quitter_segment | ||||||
| 195 | + | { | ||||||
| 196 | + | struct promise_type; | ||||||
| 197 | + | |||||||
| 198 | + | std::coroutine_handle<promise_type> h_; | ||||||
| 199 | + | }; | ||||||
| 200 | + | |||||||
| 201 | + | // The part of a quitter that opened a segment, without its T. | ||||||
| 202 | + | struct quitter_opener | ||||||
| 203 | + | { | ||||||
| 204 | + | // Takes the quitter's frame handle, leaving the quitter empty. | ||||||
| 205 | + | std::coroutine_handle<> (*release_top_)(quitter_opener&) noexcept = | ||||||
| 206 | + | nullptr; | ||||||
| 207 | + | quitter_segment seg_{}; | ||||||
| 208 | + | bool stopped_ = false; | ||||||
| 209 | + | // Set while a parent is suspended awaiting the quitter. | ||||||
| 210 | + | bool lent_ = false; | ||||||
| 211 | + | }; | ||||||
| 212 | + | |||||||
| 213 | + | // Not the chain's frame allocator: after a teardown this frame is freed | ||||||
| 214 | + | // once the parent has been abandoned, when that allocator may be gone. | ||||||
| 215 | + | // run's boundary_trampoline does the same. | ||||||
| 216 | + | struct quitter_segment::promise_type | ||||||
| 217 | + | : process_frame_mixin | ||||||
| 218 | + | { | ||||||
| 219 | + | // The segment's owner, when a quitter opened the segment. | ||||||
| 220 | + | chain_owner owner_; | ||||||
| 221 | + | // The environment of the segment's frames, when a quitter opened it. | ||||||
| 222 | + | await_context env_; | ||||||
| 223 | + | // The environment the frames run in: env_, or the launcher's. | ||||||
| 224 | + | await_context const* frame_env_ = nullptr; | ||||||
| 225 | + | // The awaiting coroutine, and its own owner for a teardown. | ||||||
| 226 | + | continuation parent_; | ||||||
| 227 | + | // The quitter frame waiting in the gate. | ||||||
| 228 | + | std::coroutine_handle<> target_; | ||||||
| 229 | + | // The quitter that opened the segment; null for a gate only. | ||||||
| 230 | + | quitter_opener* opener_ = nullptr; | ||||||
| 231 | + | // An unwind is still owed. Cleared when the top frame completes, | ||||||
| 232 | + | // when the unwind runs, and by the quitter when it destroys this. | ||||||
| 233 | + | bool active_ = false; | ||||||
| 234 | + | |||||||
| HITGNC | 235 | + | 10090 | ~promise_type() | ||||
| 236 | + | { | ||||||
| 237 | + | // Destroyed instead of resumed: a closed executor discarded the | ||||||
| 238 | + | // owner's post. That is teardown, whatever the cause. | ||||||
| HITGNC | 239 | + | 10090 | if(active_) | ||||
| 240 | + | { | ||||||
| HITGNC | 241 | + | 1 | active_ = false; | ||||
| 242 | + | // This frame is going away; the quitter must not destroy it | ||||||
| 243 | + | // again when the parent's unwind reaches it. | ||||||
| HITGNC | 244 | + | 1 | opener_->seg_.h_ = nullptr; | ||||
| HITGNC | 245 | + | 1 | opener_->release_top_(*opener_).destroy(); | ||||
| HITGNC | 246 | + | 1 | auto parent = parent_; | ||||
| HITGNC | 247 | + | 1 | capy::abandon(parent); | ||||
| 248 | + | } | ||||||
| HITGNC | 249 | + | 10090 | } | ||||
| 250 | + | |||||||
| HITGNC | 251 | + | 10090 | quitter_segment get_return_object() noexcept | ||||
| 252 | + | { | ||||||
| 253 | + | return quitter_segment{ | ||||||
| HITGNC | 254 | + | 10090 | std::coroutine_handle<promise_type>::from_promise(*this)}; | ||||
| 255 | + | } | ||||||
| 256 | + | |||||||
| HITGNC | 257 | + | 10090 | std::suspend_always initial_suspend() noexcept { return {}; } | ||||
| 258 | + | std::suspend_always final_suspend() noexcept { return {}; } // LCOV_EXCL_LINE the body never ends | ||||||
| 259 | + | void return_void() noexcept {} // LCOV_EXCL_LINE the body never ends | ||||||
| 260 | + | void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE the body never throws | ||||||
| 261 | + | |||||||
| HITGNC | 262 | + | 10118 | std::coroutine_handle<> handle() noexcept | ||||
| 263 | + | { | ||||||
| HITGNC | 264 | + | 10118 | return std::coroutine_handle<promise_type>::from_promise(*this); | ||||
| 265 | + | } | ||||||
| 266 | + | |||||||
| 267 | + | // Abandon the segment for a stop. The caller holds the waiting | ||||||
| 268 | + | // frame and touches nothing of it afterwards. | ||||||
| HITGNC | 269 | + | 4834 | void stop() noexcept | ||||
| 270 | + | { | ||||||
| HITGNC | 271 | + | 4834 | frame_env_->owner->abandon_for_stop(); | ||||
| HITGNC | 272 | + | 4834 | } | ||||
| 273 | + | |||||||
| HITGNC | 274 | + | 14909 | std::coroutine_handle<> decide() noexcept | ||||
| 275 | + | { | ||||||
| HITGNC | 276 | + | 14909 | if(opener_ && owner_.abandoned()) | ||||
| 277 | + | { | ||||||
| HITGNC | 278 | + | 4803 | active_ = false; | ||||
| HITGNC | 279 | + | 4803 | auto* op = opener_; | ||||
| HITGNC | 280 | + | 4803 | op->release_top_(*op).destroy(); | ||||
| HITGNC | 281 | + | 4803 | if(owner_.stopped()) | ||||
| 282 | + | { | ||||||
| HITGNC | 283 | + | 4801 | op->stopped_ = true; | ||||
| HITGNC | 284 | + | 4801 | return parent_.h; | ||||
| 285 | + | } | ||||||
| HITGNC | 286 | + | 2 | auto parent = parent_; | ||||
| HITGNC | 287 | + | 2 | capy::abandon(parent); | ||||
| HITGNC | 288 | + | 2 | return std::noop_coroutine(); | ||||
| 289 | + | } | ||||||
| HITGNC | 290 | + | 10106 | if(quitter_stop_pending(frame_env_)) | ||||
| 291 | + | { | ||||||
| HITGNC | 292 | + | 4825 | stop(); | ||||
| HITGNC | 293 | + | 4825 | return std::noop_coroutine(); | ||||
| 294 | + | } | ||||||
| HITGNC | 295 | + | 5281 | return target_; | ||||
| 296 | + | } | ||||||
| 297 | + | }; | ||||||
| 298 | + | |||||||
| 299 | + | struct quitter_hop | ||||||
| 300 | + | { | ||||||
| HITGNC | 301 | + | 14909 | bool await_ready() const noexcept { return false; } | ||||
| 302 | + | |||||||
| HITGNC | 303 | + | 14909 | auto await_suspend( | ||||
| 304 | + | std::coroutine_handle<quitter_segment::promise_type> h) noexcept | ||||||
| 305 | + | { | ||||||
| HITGNC | 306 | + | 14909 | return detail::symmetric_transfer(h.promise().decide()); | ||||
| 307 | + | } | ||||||
| 308 | + | |||||||
| HITGNC | 309 | + | 4824 | void await_resume() const noexcept {} | ||||
| 310 | + | }; | ||||||
| 311 | + | |||||||
| 312 | + | inline quitter_segment | ||||||
| HITGNC | 313 | + | 10090 | make_quitter_segment() | ||||
| 314 | + | { | ||||||
| 315 | + | for(;;) | ||||||
| 316 | + | co_await quitter_hop{}; | ||||||
| HITGNC | 317 | + | 20180 | } | ||||
| 318 | + | |||||||
| 80 | } // namespace detail | 319 | } // namespace detail | |||||
| 81 | 320 | |||||||
| 82 | - | /** Defers a coroutine body until awaited, then unwinds it early on a stop request. | 321 | + | /** Defers a coroutine body until awaited, and ends it on a stop request without resuming it. | |||
| 83 | 322 | |||||||
| 84 | - | When the stop token is triggered, the body is unwound at a | 323 | + | A `quitter` is a @ref task whose author has chosen that a stop | |||
| 85 | - | `co_await` instead of resuming, and RAII destructors run normally. | 324 | + | request ends it. When the stop token fires, the quitter's frames are | |||
| 86 | - | Await-effects below says at which one. The parent observes a "stopped" | 325 | + | not resumed: they are destroyed, from the top down, as an abandoned | |||
| 87 | - | completion via @ref promise_type::stopped. | 326 | + | chain is (see @ref abandon). Only destructors run. Nothing in the | |||
| 327 | + | body can observe or intercept the stop, not even `catch(...)`, and no | ||||||
| 328 | + | exception is thrown inside the quitter. The stop is reported to the | ||||||
| 329 | + | first coroutine above that is not a quitter, as a value: see | ||||||
| 330 | + | Await-returns. | ||||||
| 88 | 331 | |||||||
| 89 | - | Everything else — frame allocation, environment propagation, | 332 | + | A quitter stops only quitter frames. A @ref task below a quitter | |||
| 90 | - | symmetric transfer, move semantics — is identical to @ref task. | 333 | + | receives the stop request as usual, and returns through its own code | |||
| 334 | + | before the quitter is destroyed. A task above a quitter receives the | ||||||
| 335 | + | stopped value from `co_await`. A quitter awaited by a quitter is | ||||||
| 336 | + | stopped together with it. | ||||||
| 337 | + | |||||||
| 338 | + | Everything else (frame allocation, environment propagation, | ||||||
| 339 | + | symmetric transfer, move semantics) is as for @ref task. | ||||||
| 91 | 340 | |||||||
| 92 | @par Await-effects | 341 | @par Await-effects | |||||
| 93 | 342 | |||||||
| 94 | - | Let `q` be a `quitter<T>`. `co_await q` always suspends the awaiting | 343 | + | Let `q` be a `quitter<T>`. If the stop token in the awaiting | |||
| 95 | - | coroutine, then transfers control directly into the quitter's | 344 | + | environment has already been triggered, `co_await q` does not start | |||
| 96 | - | coroutine body on the current thread; no executor operation is | 345 | + | the body, and await-returns the stopped value. Otherwise it suspends | |||
| 97 | - | posted. The quitter records the caller's environment (executor, stop | 346 | + | the awaiting coroutine and transfers control directly into the | |||
| 98 | - | token, and frame allocator) by pointer rather than copying it. It | 347 | + | quitter's body on the current thread; no executor operation is | |||
| 99 | - | propagates that environment to every `co_await` inside the body. | 348 | + | posted. The quitter propagates the caller's executor, stop token and | |||
| 349 | + | frame allocator to every `co_await` inside the body. | ||||||
| 100 | 350 | |||||||
| 101 | - | Unlike @ref task, the stop token is checked before the body's first | 351 | + | At every `co_await` in the body, the stop token is checked before | |||
| 102 | - | statement, before each awaited operation is started, and when an | 352 | + | the awaited operation is started. An operation that completes | |||
| 103 | - | operation that completed asynchronously resumes the body. If a stop | 353 | + | asynchronously does not resume the body directly: the stop token is | |||
| 104 | - | request is pending at those points, the body is not resumed. An | 354 | + | checked again first. If either check finds a stop, the body is not | |||
| 105 | - | internal sentinel exception unwinds it instead, so RAII destructors | 355 | + | resumed. The quitter's frames are destroyed on the executor, and | |||
| 106 | - | run, and the coroutine completes as stopped. | 356 | + | the coroutine that awaited the quitter is resumed with the stopped | |||
| 357 | + | value. When `run_async` started the quitter, it reports the stop to | ||||||
| 358 | + | its handlers instead, as Await-returns describes. | ||||||
| 107 | 359 | |||||||
| 108 | - | An awaited operation that completes synchronously always has its | 360 | + | An operation that completes without suspending always has | |||
| 109 | - | `await_resume` called, even if a stop request arrives at the same | 361 | + | its `await_resume` called, because its `await_ready` or | |||
| 110 | - | time. Its `await_ready` or `await_suspend` may already have acquired | 362 | + | `await_suspend` may already have acquired a resource, such as | |||
| 111 | - | a resource, such as `async_mutex`'s lock, that only its | 363 | + | `async_mutex`'s lock, that only its `await_resume` hands over. If | |||
| 112 | - | `await_resume` hands over. If that result is a @ref results holding | 364 | + | that result is a @ref results holding `cond::canceled` while a stop | |||
| 113 | - | `cond::canceled` while a stop is pending, the operation saw the stop | 365 | + | is pending, the operation saw the stop first and acquired nothing, | |||
| 114 | - | first and acquired nothing, so the body is unwound there. Any other | 366 | + | so the quitter is stopped at that `co_await`. Any other result is | |||
| 115 | - | result is handed to the body, and the stop takes effect at the next | 367 | + | handed to the body, and the stop takes effect at the next | |||
| 116 | - | `co_await`. An operation whose result is not a @ref results can | 368 | + | suspension. An operation whose result is not a @ref results can | |||
| 117 | therefore hand the body one result it produced after seeing the | 369 | therefore hand the body one result it produced after seeing the | |||||
| 118 | stop. | 370 | stop. | |||||
| 119 | 371 | |||||||
| 120 | - | The body runs until it returns, exits via an exception, or is unwound | 372 | + | When the body returns or exits via an exception, control transfers | |||
| 121 | - | by a stop request. Control then transfers directly back to the | 373 | + | directly back to the awaiting coroutine. | |||
| 122 | - | awaiting coroutine, again without an executor operation. | ||||||
| 123 | 374 | |||||||
| 124 | @par Await-returns | 375 | @par Await-returns | |||||
| 376 | + | |||||||
| 125 | The value the body passed to `co_return`, moved out of the quitter, | 377 | The value the body passed to `co_return`, moved out of the quitter, | |||||
| 126 | - | or nothing when `T` is `void`. | 378 | + | or nothing when `T` is `void`. If the body exits via an unhandled | |||
| 379 | + | exception, that exception is rethrown instead. | ||||||
| 127 | 380 | |||||||
| 128 | - | If the body exits via an unhandled exception, that exception is | 381 | + | If the quitter was stopped, the stopped value instead: | |||
| 129 | - | rethrown instead. | ||||||
| 130 | 382 | |||||||
| 131 | - | If the coroutine completed as stopped, the internal sentinel | 383 | + | @li for `results<Ts...>`: `make_error_code(error::canceled)` with | |||
| 132 | - | exception is thrown instead of await-returning. Awaiting a stopped | 384 | + | each `Ts` value-initialized; it compares equal to | |||
| 133 | - | `quitter` from another `quitter` therefore stops that one too. A | 385 | + | `cond::canceled`. Every `Ts` must be default-constructible. | |||
| 134 | - | @ref task awaiting it sees the sentinel as an unhandled exception in | 386 | + | @li for `std::error_code`: `make_error_code(error::canceled)`. | |||
| 135 | - | its own body. When a quitter is started by `run_async`, a stopped | 387 | + | @li for any other `T`, `void` included: no value; an exception of | |||
| 136 | - | completion reaches the error handler as the sentinel | 388 | + | an unspecified type derived from `std::system_error`, whose | |||
| 137 | - | `std::exception_ptr`, not the value handler. | 389 | + | `code()` compares equal to `cond::canceled`, is thrown. A `void` | |||
| 390 | + | return would read as success. Such a `quitter<T>` cannot be used | ||||||
| 391 | + | where exceptions are disabled: use `quitter<results<>>` there. | ||||||
| 392 | + | |||||||
| 393 | + | `co_await run(ex)(q)`, and the other @ref run overloads, yield the | ||||||
| 394 | + | same stopped value. A quitter started by `run_async` reports a stop | ||||||
| 395 | + | the same way. For `results<Ts...>` and `std::error_code`, the value | ||||||
| 396 | + | handler receives the stopped value. For any other `T`, the error | ||||||
| 397 | + | handler receives the exception. With no error handler, no handler | ||||||
| 398 | + | is called and the stop is discarded. This also holds when the | ||||||
| 399 | + | exception escapes a @ref task that awaited the quitter: the task is | ||||||
| 400 | + | unwound, and with no error handler `run_async` discards the stop. | ||||||
| 401 | + | Any other exception, a `std::system_error` with `cond::canceled` | ||||||
| 402 | + | included, still calls `std::terminate`. That includes a | ||||||
| 403 | + | `std::system_error` copy of the stop that a task throws. | ||||||
| 138 | 404 | |||||||
| 139 | @par Await-postcondition | 405 | @par Await-postcondition | |||||
| 140 | - | The quitter's coroutine has run to completion and is suspended at its | 406 | + | The quitter's coroutine has completed, has been destroyed by a | |||
| 141 | - | final suspend point; the body's RAII destructors have run. Exactly | 407 | + | stop, or, when the stop was requested before it was awaited, was | |||
| 142 | - | one of the following holds: the body returned a value; the body | 408 | + | never started. A quitter must not be awaited twice. | |||
| 143 | - | exited via an exception; or `handle().promise().stopped()` returns | ||||||
| 144 | - | `true`. When the body returned a value, the await moved it out, so a | ||||||
| 145 | - | quitter must not be awaited twice. | ||||||
| 146 | 409 | |||||||
| 147 | @par Remarks | 410 | @par Remarks | |||||
| 148 | - | Supports _ContextAwaitable cancellation_. | 411 | + | Supports _ContextAwaitable cancellation_. A quitter stops through the | |||
| 412 | + | owner of its segment (@ref await_context::owner). A launcher that | ||||||
| 413 | + | starts a quitter in an environment with no owner cannot stop it: a | ||||||
| 414 | + | debug build asserts, and otherwise the quitter runs on as a | ||||||
| 415 | + | @ref task would. A stop that arrives while | ||||||
| 416 | + | the execution context is being shut down may end the quitter as a | ||||||
| 417 | + | teardown instead, with nothing delivered. Stop the work, wait for it | ||||||
| 418 | + | to finish, and only then destroy the context. | ||||||
| 149 | 419 | |||||||
| 150 | - | @tparam T The result type. Use `quitter<>` for `quitter<void>`. | 420 | + | A stop destroys quitter frames that are suspended at a `co_await`. | |||
| 421 | + | GCC 15.2, and GCC 16 before 2026-07-02, at `-O0`, miscompile such a | ||||||
| 422 | + | destruction when the `co_await` initializes a structured binding | ||||||
| 423 | + | with an element that is not trivially destructible: the destructor | ||||||
| 424 | + | of the never-constructed binding runs (GCC PR c++/124584). Bind the | ||||||
| 425 | + | result to a named variable, and destructure it after the | ||||||
| 426 | + | `co_await`: | ||||||
| 151 | 427 | |||||||
| 152 | - | @see @ref task, @ref ContextRunnable, @ref ContextAwaitable | 428 | + | @code | |||
| 429 | + | auto r = co_await mtx.scoped_lock(); | ||||||
| 430 | + | auto& [ec, guard] = r; | ||||||
| 431 | + | @endcode | ||||||
| 432 | + | |||||||
| 433 | + | @tparam T The result type. Use `quitter<>` for `quitter<void>`. | ||||||
| 434 | + | |||||||
| 435 | + | @see @ref task, @ref ContextRunnable, @ref ContextAwaitable, | ||||||
| 436 | + | @ref chain_owner | ||||||
| 153 | */ | 437 | */ | |||||
| 154 | template<typename T = void> | 438 | template<typename T = void> | |||||
| 155 | struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE | 439 | struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE | |||||
| 156 | quitter | 440 | quitter | |||||
| 441 | + | : private detail::quitter_opener | ||||||
| 157 | { | 442 | { | |||||
| 158 | - | /** Stores `quitter<T>`'s result and unwinds the body when the stop token fires. | 443 | + | /** Stores `quitter<T>`'s result and routes its awaits through the segment's gate. | |||
| 159 | 444 | |||||||
| 160 | This is the promise object the compiler associates with a | 445 | This is the promise object the compiler associates with a | |||||
| 161 | `quitter<T>` coroutine. It satisfies the coroutine promise | 446 | `quitter<T>` coroutine. It satisfies the coroutine promise | |||||
| 162 | requirements and participates in the I/O awaitable protocol via | 447 | requirements and participates in the I/O awaitable protocol via | |||||
| 163 | @ref context_awaitable_promise_base. Unlike @ref task::promise_type, | 448 | @ref context_awaitable_promise_base. Unlike @ref task::promise_type, | |||||
| 164 | its `transform_awaitable` checks the stop token before each | 449 | its `transform_awaitable` checks the stop token before each | |||||
| 165 | - | awaited result reaches the body. A pending stop request throws an | 450 | + | awaited operation starts. It hands the operation the segment's | |||
| 166 | - | internal sentinel exception that unwinds to a "stopped" | 451 | + | gate as the continuation, not this coroutine. It is part of | |||
| 167 | - | completion. It is part of the coroutine machinery and is not | 452 | + | the coroutine machinery and is not intended to be used directly | |||
| 168 | - | intended to be used directly by callers. | 453 | + | by callers. | |||
| 169 | 454 | |||||||
| 170 | Result storage and `return_value`/`return_void` are provided by | 455 | Result storage and `return_value`/`return_void` are provided by | |||||
| 171 | `detail::quitter_return_base<T>`. | 456 | `detail::quitter_return_base<T>`. | |||||
| 172 | 457 | |||||||
| 173 | @see @ref context_awaitable_promise_base, @ref ContextRunnable | 458 | @see @ref context_awaitable_promise_base, @ref ContextRunnable | |||||
| 174 | */ | 459 | */ | |||||
| 175 | struct promise_type | 460 | struct promise_type | |||||
| 176 | : context_awaitable_promise_base<promise_type> | 461 | : context_awaitable_promise_base<promise_type> | |||||
| 177 | , detail::quitter_return_base<T> | 462 | , detail::quitter_return_base<T> | |||||
| 178 | { | 463 | { | |||||
| 179 | private: | 464 | private: | |||||
| 180 | friend quitter; | 465 | friend quitter; | |||||
| 181 | 466 | |||||||
| 182 | - | enum class completion { running, value, exception, stopped }; | 467 | + | template<class> | |||
| 468 | + | friend struct quitter; | ||||||
| 183 | 469 | |||||||
| 184 | - | union { std::exception_ptr ep_; }; | 470 | + | std::exception_ptr ep_; | |||
| 185 | - | completion state_; | ||||||
| 186 | 471 | |||||||
| 187 | - | public: | 472 | + | // The gate of the segment this frame runs in. | |||
| 188 | - | /// Construct the promise in the running state. | 473 | + | detail::quitter_segment::promise_type* seg_ = nullptr; | |||
| ECB | 189 | - | 45 | promise_type() noexcept | 474 | + | ||
| ECB | 190 | - | 45 | : state_(completion::running) | 475 | + | // The gate this frame owns, when a launcher started it. | |
| 476 | + | detail::quitter_segment own_gate_{}; | ||||||
| 477 | + | |||||||
| 478 | + | // This frame opened its segment. | ||||||
| 479 | + | bool top_ = false; | ||||||
| 480 | + | |||||||
| HITGNC | 481 | + | 50 | void open_gate() | ||||
| 191 | { | 482 | { | |||||
| HITGNC | 483 | + | 50 | own_gate_ = detail::make_quitter_segment(); | ||||
| HITGNC | 484 | + | 50 | seg_ = &own_gate_.h_.promise(); | ||||
| HITGNC | 485 | + | 50 | seg_->frame_env_ = this->environment(); | ||||
| HITCBC | 192 | 45 | } | 486 | 50 | } | ||
| 193 | 487 | |||||||
| 194 | - | /// Destroy the promise, releasing any stored exception. | 488 | + | public: | |||
| 489 | + | /// Construct the promise. | ||||||
| HITGNC | 490 | + | 10204 | promise_type() noexcept = default; | ||||
| 491 | + | |||||||
| 492 | + | /// Destroy the promise, and the gate it owns, if any. | ||||||
| HITCBC | 195 | 45 | ~promise_type() | 493 | 10204 | ~promise_type() | ||
| 196 | { | 494 | { | |||||
| HITCBC | 197 | - | 45 | if(state_ == completion::exception || | 495 | + | 10204 | if(own_gate_.h_) |
| HITCBC | 198 | - | 41 | state_ == completion::stopped) | 496 | + | 50 | own_gate_.h_.destroy(); |
| DCB | 199 | - | 23 | ep_.~exception_ptr(); | ||||
| HITCBC | 200 | 45 | } | 497 | 10204 | } | ||
| 201 | 498 | |||||||
| 202 | - | /** Return a non-null exception_ptr when the coroutine threw | 499 | + | /** Return the exception the body exited with, if any. | |||
| 203 | - | or was stopped. | ||||||
| 204 | - | |||||||
| 205 | - | Stopped quitters report the sentinel | ||||||
| 206 | - | stop_requested_exception so that run_async routes to | ||||||
| 207 | - | the error handler instead of accessing a non-existent | ||||||
| 208 | - | result. | ||||||
| 209 | 500 | |||||||
| 210 | @return The stored exception if the coroutine exited via an | 501 | @return The stored exception if the coroutine exited via an | |||||
| 211 | - | exception or was stopped, otherwise a null | 502 | + | exception, otherwise a null `std::exception_ptr`. | |||
| 212 | - | `std::exception_ptr`. | ||||||
| 213 | */ | 503 | */ | |||||
| HITCBC | 214 | 38 | std::exception_ptr exception() const noexcept | 504 | 18 | std::exception_ptr exception() const noexcept | ||
| 215 | { | 505 | { | |||||
| HITCBC | 216 | - | 38 | if(state_ == completion::exception || | 506 | + | 18 | return ep_; |
| DCB | 217 | - | 32 | state_ == completion::stopped) | ||||
| DCB | 218 | - | 26 | return ep_; | ||||
| DCB | 219 | - | 12 | return {}; | ||||
| 220 | } | 507 | } | |||||
| 221 | 508 | |||||||
| 222 | - | /** True when the coroutine was stopped via the stop token. | 509 | + | /** Return what a stopped `quitter<T>` delivers. | |||
| 223 | 510 | |||||||
| 224 | - | @return `true` if the body was unwound by a stop request; | 511 | + | Launchers that start a quitter directly call this when its | |||
| 225 | - | `false` if it returned a value or exited via any other | 512 | + | segment was abandoned for a stop: see | |||
| 226 | - | exception. | 513 | + | @ref chain_owner::stopped. Its result, or the exception it | |||
| 514 | + | throws, is reported in place of the coroutine's own result. | ||||||
| 515 | + | |||||||
| 516 | + | @return `make_error_code(error::canceled)` with value-initialized | ||||||
| 517 | + | payloads for `results<Ts...>`, or | ||||||
| 518 | + | `make_error_code(error::canceled)` for `std::error_code`. | ||||||
| 519 | + | |||||||
| 520 | + | @throws std::system_error For any other `T`, `void` included, | ||||||
| 521 | + | with a `code()` that compares equal to `cond::canceled`. | ||||||
| 227 | */ | 522 | */ | |||||
| HITCBC | 228 | - | 12 | bool stopped() const noexcept | 523 | + | 4836 | static T stopped_result() |
| 229 | { | 524 | { | |||||
| HITCBC | 230 | - | 12 | return state_ == completion::stopped; | 525 | + | 4836 | return detail::stopped_value<T>::get(); |
| 526 | + | } | ||||||
| 527 | + | |||||||
| 528 | + | /** Return the handle a launcher resumes to start the quitter. | ||||||
| 529 | + | |||||||
| 530 | + | Launchers that start a quitter directly resume this handle, | ||||||
| 531 | + | not the coroutine's own, so the stop is checked when the body | ||||||
| 532 | + | would start rather than when the launcher was called. The | ||||||
| 533 | + | handle is the quitter's gate, which either starts the body | ||||||
| 534 | + | or, if a stop was requested by then, abandons the segment for | ||||||
| 535 | + | the stop: see @ref chain_owner::abandon_for_stop. | ||||||
| 536 | + | |||||||
| 537 | + | @par Preconditions | ||||||
| 538 | + | The coroutine has not started, and its environment is set. | ||||||
| 539 | + | |||||||
| 540 | + | @return The handle of the quitter's gate. | ||||||
| 541 | + | |||||||
| 542 | + | @throws std::bad_alloc If the gate cannot be allocated. | ||||||
| 543 | + | */ | ||||||
| HITGNC | 544 | + | 48 | std::coroutine_handle<> launch_handle() | ||||
| 545 | + | { | ||||||
| HITGNC | 546 | + | 48 | open_gate(); | ||||
| HITGNC | 547 | + | 48 | seg_->target_ = | ||||
| HITGNC | 548 | + | 48 | std::coroutine_handle<promise_type>::from_promise(*this); | ||||
| HITGNC | 549 | + | 48 | return seg_->handle(); | ||||
| 231 | } | 550 | } | |||||
| 232 | 551 | |||||||
| 233 | /** Return the owning `quitter` for this coroutine. | 552 | /** Return the owning `quitter` for this coroutine. | |||||
| 234 | 553 | |||||||
| 235 | Called by the compiler to produce the object returned to the | 554 | Called by the compiler to produce the object returned to the | |||||
| 236 | caller when the coroutine is created. | 555 | caller when the coroutine is created. | |||||
| 237 | 556 | |||||||
| 238 | @return A `quitter` owning the coroutine frame. | 557 | @return A `quitter` owning the coroutine frame. | |||||
| 239 | */ | 558 | */ | |||||
| HITCBC | 240 | 45 | quitter get_return_object() | 559 | 10204 | quitter get_return_object() | ||
| 241 | { | 560 | { | |||||
| 242 | return quitter{ | 561 | return quitter{ | |||||
| HITCBC | 243 | 45 | std::coroutine_handle<promise_type>::from_promise(*this)}; | 562 | 10204 | std::coroutine_handle<promise_type>::from_promise(*this)}; | ||
| 244 | } | 563 | } | |||||
| 245 | 564 | |||||||
| 246 | /** Return the initial-suspend awaiter. | 565 | /** Return the initial-suspend awaiter. | |||||
| 247 | 566 | |||||||
| 248 | The coroutine always suspends at the initial suspend point, | 567 | The coroutine always suspends at the initial suspend point, | |||||
| 249 | - | so the body does not start until the quitter is awaited. When | 568 | + | so the body does not start until the quitter is awaited or | |||
| 250 | - | the body is resumed, the awaiter restores the thread-local | 569 | + | started. When the body is resumed, the awaiter restores the | |||
| 251 | - | frame allocator. It then throws the internal sentinel | 570 | + | thread-local frame allocator. A quitter started directly by a | |||
| 252 | - | exception if stop is already requested, so the body never | 571 | + | launcher that did not call @ref launch_handle creates its | |||
| 253 | - | runs and the coroutine completes as stopped. | 572 | + | gate at that point. | |||
| 254 | 573 | |||||||
| 255 | @return An awaiter that suspends unconditionally. | 574 | @return An awaiter that suspends unconditionally. | |||||
| 256 | */ | 575 | */ | |||||
| HITCBC | 257 | 45 | auto initial_suspend() noexcept | 576 | 10204 | auto initial_suspend() noexcept | ||
| 258 | { | 577 | { | |||||
| 259 | struct awaiter | 578 | struct awaiter | |||||
| 260 | { | 579 | { | |||||
| 261 | promise_type* p_; | 580 | promise_type* p_; | |||||
| 262 | 581 | |||||||
| HITCBC | 263 | 45 | bool await_ready() const noexcept | 582 | 10204 | bool await_ready() const noexcept | ||
| 264 | { | 583 | { | |||||
| HITCBC | 265 | 45 | return false; | 584 | 10204 | return false; | ||
| 266 | } | 585 | } | |||||
| 267 | 586 | |||||||
| HITCBC | 268 | 45 | void await_suspend(std::coroutine_handle<>) const noexcept | 587 | 10204 | void await_suspend(std::coroutine_handle<>) const noexcept | ||
| 269 | { | 588 | { | |||||
| HITCBC | 270 | 45 | } | 589 | 10204 | } | ||
| 271 | 590 | |||||||
| 272 | - | // Potentially-throwing: checks the stop token before | 591 | + | // Allocates the gate of a quitter that a launcher started | |||
| 273 | - | // the coroutine body executes its first statement. | 592 | + | // without launch_handle(); a failure is reported as the | |||
| 593 | + | // body's exception. | ||||||
| HITCBC | 274 | 43 | void await_resume() const | 594 | 10193 | void await_resume() const | ||
| 275 | { | 595 | { | |||||
| HITCBC | 276 | 43 | set_current_frame_allocator( | 596 | 10193 | set_current_frame_allocator( | ||
| HITCBC | 277 | 43 | p_->environment()->frame_allocator); | 597 | 10193 | p_->environment()->frame_allocator); | ||
| HITCBC | 278 | - | 43 | if(p_->environment()->stop_token.stop_requested()) | 598 | + | 10193 | if(!p_->seg_) |
| HITCBC | 279 | - | 3 | throw detail::stop_requested_exception{}; | 599 | + | 2 | p_->open_gate(); |
| HITCBC | 280 | 40 | } | 600 | 10193 | } | ||
| 281 | }; | 601 | }; | |||||
| HITCBC | 282 | 45 | return awaiter{this}; | 602 | 10204 | return awaiter{this}; | ||
| 283 | } | 603 | } | |||||
| 284 | 604 | |||||||
| 285 | /** Return the final-suspend awaiter. | 605 | /** Return the final-suspend awaiter. | |||||
| 286 | 606 | |||||||
| 287 | The coroutine always suspends at the final suspend point. The | 607 | The coroutine always suspends at the final suspend point. The | |||||
| 288 | awaiter's `await_suspend` performs symmetric transfer to the | 608 | awaiter's `await_suspend` performs symmetric transfer to the | |||||
| 289 | stored continuation, resuming the awaiting coroutine. | 609 | stored continuation, resuming the awaiting coroutine. | |||||
| 290 | 610 | |||||||
| 291 | @return An awaiter that suspends and transfers to the | 611 | @return An awaiter that suspends and transfers to the | |||||
| 292 | continuation. | 612 | continuation. | |||||
| 293 | */ | 613 | */ | |||||
| HITCBC | 294 | 42 | auto final_suspend() noexcept | 614 | 5257 | auto final_suspend() noexcept | ||
| 295 | { | 615 | { | |||||
| 296 | struct awaiter | 616 | struct awaiter | |||||
| 297 | { | 617 | { | |||||
| 298 | promise_type* p_; | 618 | promise_type* p_; | |||||
| 299 | 619 | |||||||
| HITCBC | 300 | 42 | bool await_ready() const noexcept | 620 | 5257 | bool await_ready() const noexcept | ||
| 301 | { | 621 | { | |||||
| HITCBC | 302 | 42 | return false; | 622 | 5257 | return false; | ||
| 303 | } | 623 | } | |||||
| 304 | 624 | |||||||
| HITCBC | 305 | 42 | std::coroutine_handle<> await_suspend( | 625 | 5257 | std::coroutine_handle<> await_suspend( | ||
| 306 | std::coroutine_handle<>) const noexcept | 626 | std::coroutine_handle<>) const noexcept | |||||
| 307 | { | 627 | { | |||||
| 628 | + | // The top frame completed: its segment owes no | ||||||
| 629 | + | // unwind. | ||||||
| HITGNC | 630 | + | 5257 | if(p_->top_) | ||||
| HITGNC | 631 | + | 5236 | p_->seg_->active_ = false; | ||||
| HITCBC | 308 | 42 | return p_->continuation(); | 632 | 5257 | return p_->continuation(); | ||
| 309 | } | 633 | } | |||||
| 310 | 634 | |||||||
| 311 | void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed | 635 | void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed | |||||
| 312 | }; | 636 | }; | |||||
| HITCBC | 313 | 42 | return awaiter{this}; | 637 | 5257 | return awaiter{this}; | ||
| 314 | } | 638 | } | |||||
| 315 | 639 | |||||||
| 316 | /** Capture the in-flight exception from the coroutine body. | 640 | /** Capture the in-flight exception from the coroutine body. | |||||
| 317 | 641 | |||||||
| 318 | Called by the compiler when the coroutine body exits via an | 642 | Called by the compiler when the coroutine body exits via an | |||||
| 319 | - | unhandled exception. The internal stop sentinel is recorded as | 643 | + | unhandled exception. The exception is rethrown when the | |||
| 320 | - | a stopped completion; any other exception is recorded as an | 644 | + | quitter is awaited, or routed to the error handler when it is | |||
| 321 | - | exception completion. The stored exception is surfaced (or | 645 | + | run. | |||
| 322 | - | routed to the error handler) when the quitter is awaited or run. | ||||||
| 323 | */ | 646 | */ | |||||
| HITCBC | 324 | - | 23 | void unhandled_exception() | 647 | + | 4 | void unhandled_exception() noexcept |
| 325 | { | 648 | { | |||||
| HITGIC | 326 | - | try | 649 | + | 4 | ep_ = std::current_exception(); | |
| 327 | - | { | ||||||
| DCB | 328 | - | 23 | throw; | ||||
| 329 | - | } | ||||||
| DCB | 330 | - | 23 | catch(detail::stop_requested_exception const&) | ||||
| 331 | - | { | ||||||
| 332 | - | // Store the exception_ptr so that run_async's | ||||||
| 333 | - | // invoke_impl routes to the error handler | ||||||
| 334 | - | // instead of accessing a non-existent result. | ||||||
| DCB | 335 | - | 19 | new (&ep_) std::exception_ptr( | ||||
| 336 | - | std::current_exception()); | ||||||
| DCB | 337 | - | 19 | state_ = completion::stopped; | ||||
| 338 | - | } | ||||||
| DCB | 339 | - | 4 | catch(...) | ||||
| 340 | - | { | ||||||
| DCB | 341 | - | 4 | new (&ep_) std::exception_ptr( | ||||
| 342 | - | std::current_exception()); | ||||||
| DCB | 343 | - | 4 | state_ = completion::exception; | ||||
| 344 | - | } | ||||||
| HITCBC | 345 | 23 | } | 650 | 4 | } | ||
| 346 | - | //------------------------------------------------------ | ||||||
| 347 | - | // transform_awaitable — the key difference from task<T> | ||||||
| 348 | - | //------------------------------------------------------ | ||||||
| 349 | - | |||||||
| 350 | 651 | |||||||
| 351 | /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable. | 652 | /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable. | |||||
| 352 | 653 | |||||||
| 353 | Forwards the environment to the inner awaitable's | 654 | Forwards the environment to the inner awaitable's | |||||
| 354 | - | environment-taking `await_suspend` and restores the | 655 | + | environment-taking `await_suspend`, and restores the | |||
| 355 | thread-local frame allocator before the body resumes. Unlike | 656 | thread-local frame allocator before the body resumes. Unlike | |||||
| 356 | - | `task`'s, it also checks the stop token. A stop pending before | 657 | + | `task`'s, it checks the stop token before the inner awaitable | |||
| 357 | - | the wrapped awaitable is touched, or on resumption after an | 658 | + | is touched. It passes the inner awaitable the segment's gate | |||
| 358 | - | asynchronous completion, throws the internal sentinel. The | 659 | + | as the continuation, not this coroutine. | |||
| 359 | - | body then unwinds before it observes the operation's result. | ||||||
| 360 | - | A synchronous completion is always handed over through the | ||||||
| 361 | - | wrapped awaitable's `await_resume`. | ||||||
| 362 | 660 | |||||||
| 363 | @tparam Awaitable The awaitable being transformed. | 661 | @tparam Awaitable The awaitable being transformed. | |||||
| 364 | */ | 662 | */ | |||||
| 365 | template<class Awaitable> | 663 | template<class Awaitable> | |||||
| 366 | struct transform_awaiter | 664 | struct transform_awaiter | |||||
| 367 | { | 665 | { | |||||
| 368 | /// The wrapped awaitable, decayed and stored by value. | 666 | /// The wrapped awaitable, decayed and stored by value. | |||||
| 369 | std::decay_t<Awaitable> a_; | 667 | std::decay_t<Awaitable> a_; | |||||
| 370 | 668 | |||||||
| 371 | /// The promise of the coroutine performing the `co_await`. | 669 | /// The promise of the coroutine performing the `co_await`. | |||||
| 372 | promise_type* p_; | 670 | promise_type* p_; | |||||
| 373 | 671 | |||||||
| 374 | - | /// True when a stop was pending at the `co_await`, so the | 672 | + | /// The wrapped awaitable's await-result type. | |||
| 375 | - | /// wrapped awaitable was never touched. | 673 | + | using result_type = decltype( | |||
| 376 | - | bool skipped_ = false; | 674 | + | std::declval<std::decay_t<Awaitable>&>().await_resume()); | |||
| 377 | 675 | |||||||
| 378 | - | /// True when the wrapped awaitable completed synchronously, so | 676 | + | /// True when the await-result is a @ref results, which is | |||
| 379 | - | /// its `await_resume` must run to hand over what it acquired. | 677 | + | /// checked for a canceled stop after a synchronous completion. | |||
| 380 | - | bool completed_ = false; | 678 | + | static constexpr bool checks_canceled = | |||
| 679 | + | detail::is_results_v<result_type>; | ||||||
| 680 | + | |||||||
| 681 | + | /// True when the checks that may call the wrapped awaitable's | ||||||
| 682 | + | /// `await_resume` early cannot throw. | ||||||
| 683 | + | static constexpr bool checks_noexcept = | ||||||
| 684 | + | !checks_canceled || noexcept( | ||||||
| 685 | + | std::declval<std::decay_t<Awaitable>&>().await_resume()); | ||||||
| 686 | + | |||||||
| 687 | + | /// Empty stand-in for @ref result_ when nothing is checked. | ||||||
| 688 | + | struct no_result {}; | ||||||
| 689 | + | |||||||
| 690 | + | /// The result of a synchronous completion, taken early by | ||||||
| 691 | + | /// @ref stops_on_sync_completion and handed over by | ||||||
| 692 | + | /// @ref await_resume. | ||||||
| 693 | + | std::conditional_t<checks_canceled, | ||||||
| 694 | + | std::optional<result_type>, no_result> result_{}; | ||||||
| 695 | + | |||||||
| 696 | + | /// True when the stop takes effect at this `co_await` without | ||||||
| 697 | + | /// suspending: a stop was pending before the wrapped awaitable | ||||||
| 698 | + | /// was touched, or it completed synchronously with a result | ||||||
| 699 | + | /// canceled by that stop. | ||||||
| 700 | + | bool stop_here_ = false; | ||||||
| 701 | + | |||||||
| 702 | + | /** Take the result of a synchronous completion, and report | ||||||
| 703 | + | whether the stop takes effect here instead. | ||||||
| 704 | + | |||||||
| 705 | + | The wrapped awaitable's `await_resume` is always called, | ||||||
| 706 | + | because its `await_ready` or `await_suspend` may already | ||||||
| 707 | + | have acquired something that only `await_resume` hands | ||||||
| 708 | + | over. A @ref results holding `cond::canceled` while our | ||||||
| 709 | + | stop is pending saw the stop first and acquired nothing, | ||||||
| 710 | + | so the quitter can be abandoned. Any other result is kept | ||||||
| 711 | + | for @ref await_resume. A result that is not a | ||||||
| 712 | + | @ref results is not taken early. | ||||||
| 713 | + | |||||||
| 714 | + | @return `true` if the quitter is abandoned at this | ||||||
| 715 | + | `co_await`. | ||||||
| 716 | + | */ | ||||||
| HITGNC | 717 | + | 14 | bool stops_on_sync_completion() noexcept(checks_noexcept) | ||||
| 718 | + | { | ||||||
| 719 | + | if constexpr (checks_canceled) | ||||||
| 720 | + | { | ||||||
| HITGNC | 721 | + | 9 | result_.emplace(a_.await_resume()); | ||||
| 722 | + | // The stop check first: comparing with | ||||||
| 723 | + | // cond::canceled goes through the error category's | ||||||
| 724 | + | // virtual calls, and a stop is rarely pending. | ||||||
| HITGNC | 725 | + | 14 | return detail::quitter_stop_pending(p_->environment()) && | ||||
| HITGNC | 726 | + | 14 | std::get<0>(*result_) == cond::canceled; | ||||
| 727 | + | } | ||||||
| 728 | + | else | ||||||
| 729 | + | { | ||||||
| HITGNC | 730 | + | 5 | return false; | ||||
| 731 | + | } | ||||||
| 732 | + | } | ||||||
| 381 | 733 | |||||||
| 382 | /** Report whether the `co_await` can continue without | 734 | /** Report whether the `co_await` can continue without | |||||
| 383 | suspending. | 735 | suspending. | |||||
| 384 | 736 | |||||||
| 385 | The stop token is checked first. If a stop is pending, the | 737 | The stop token is checked first. If a stop is pending, the | |||||
| 386 | - | wrapped awaitable is never touched: this returns `true`, and | 738 | + | wrapped awaitable is never touched: this returns `false`, | |||
| 387 | - | @ref await_resume throws. Otherwise the wrapped awaitable's | 739 | + | and @ref await_suspend ends the quitter. Otherwise the | |||
| 388 | - | own `await_ready` decides. A `true` result there means it | 740 | + | wrapped awaitable's own `await_ready` decides. A `true` | |||
| 389 | - | completed synchronously, so @ref await_resume calls its | 741 | + | result means it completed synchronously. Its | |||
| 390 | - | `await_resume` even if a stop arrives afterwards. That hands | 742 | + | `await_resume` then hands over its result, even if a stop | |||
| 391 | - | over anything it acquired, such as `async_mutex`'s lock. | 743 | + | arrives afterwards, unless that result is a @ref results | |||
| 744 | + | canceled by the stop (see @ref stops_on_sync_completion). | ||||||
| 392 | 745 | |||||||
| 393 | Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, | 746 | Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, | |||||
| 394 | this always returns `false`, and @ref await_suspend does both | 747 | this always returns `false`, and @ref await_suspend does both | |||||
| 395 | steps instead. See `detail::await_ready_then_suspend`. | 748 | steps instead. See `detail::await_ready_then_suspend`. | |||||
| 396 | 749 | |||||||
| 397 | - | @return `true` if no suspension is needed. Always `false` | 750 | + | @return `true` if no suspension is needed. | |||
| 398 | - | where the workaround is on. | ||||||
| 399 | */ | 751 | */ | |||||
| HITCBC | 400 | - | 31 | bool await_ready() noexcept | 752 | + | 10080 | bool await_ready() noexcept(checks_noexcept) |
| 401 | { | 753 | { | |||||
| 402 | #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND | 754 | #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND | |||||
| 403 | return false; | 755 | return false; | |||||
| 404 | #else | 756 | #else | |||||
| HITCBC | 405 | - | 31 | if(p_->environment()->stop_token.stop_requested()) | 757 | + | 10080 | if(detail::quitter_stop_pending(p_->environment())) |
| 406 | { | 758 | { | |||||
| HITCBC | 407 | - | 1 | skipped_ = true; | 759 | + | 5 | stop_here_ = true; |
| HITCBC | 408 | - | 1 | return true; | 760 | + | 5 | return false; |
| 409 | } | 761 | } | |||||
| HITCBC | 410 | - | 30 | completed_ = a_.await_ready(); | 762 | + | 10075 | if(!a_.await_ready()) |
| HITCBC | 411 | - | 30 | return completed_; | 763 | + | 10070 | return false; |
| HITGNC | 764 | + | 5 | stop_here_ = stops_on_sync_completion(); | ||||
| HITGNC | 765 | + | 5 | return !stop_here_; | ||||
| 412 | #endif | 766 | #endif | |||||
| 413 | } | 767 | } | |||||
| 414 | 768 | |||||||
| 415 | - | /** Restore the frame allocator, check for stop, then resume the | 769 | + | /** Restore the frame allocator, then return the wrapped | |||
| 416 | - | wrapped awaitable. | 770 | + | awaitable's result. | |||
| 417 | - | |||||||
| 418 | - | Reinstalls the thread-local frame allocator from the stored | ||||||
| 419 | - | environment. The internal sentinel exception is then thrown | ||||||
| 420 | - | in three cases: a stop was pending before the wrapped | ||||||
| 421 | - | awaitable was touched; a stop is pending now and the | ||||||
| 422 | - | wrapped awaitable completed asynchronously; or a stop is | ||||||
| 423 | - | pending now and the wrapped awaitable's result is a | ||||||
| 424 | - | @ref results holding `cond::canceled`. The body therefore | ||||||
| 425 | - | never observes that operation's result, and unwinds through | ||||||
| 426 | - | its RAII destructors to a stopped completion. A wrapped | ||||||
| 427 | - | awaitable that completed synchronously always has its | ||||||
| 428 | - | `await_resume` called, and any other result it gives is | ||||||
| 429 | - | returned, with the stop taking effect at the next | ||||||
| 430 | - | `co_await`. This is the one place `quitter` differs from | ||||||
| 431 | - | @ref task::promise_type::transform_awaiter. | ||||||
| 432 | 771 | |||||||
| 433 | - | After an asynchronous completion with a stop pending, the | 772 | + | The body is resumed only when the wrapped awaitable | |||
| 434 | - | operation's result is dropped. Releasing anything it held is | 773 | + | completed synchronously, or completed through the gate | |||
| 435 | - | then the wrapped awaiter's destructor's job: `async_mutex` | 774 | + | with no stop requested. | |||
| 436 | - | releases a lock it granted, and `async_waker` drops the | ||||||
| 437 | - | wakeup. The body is unwinding, so nothing observes the loss. | ||||||
| 438 | 775 | |||||||
| 439 | @return The wrapped awaitable's await-result, forwarded | 776 | @return The wrapped awaitable's await-result, forwarded | |||||
| 440 | - | unchanged, when no sentinel is thrown. | 777 | + | unchanged, or the result already taken from it. | |||
| 441 | - | |||||||
| 442 | - | @par Exception Safety | ||||||
| 443 | - | Throws the library's internal stop sentinel in the three | ||||||
| 444 | - | cases above. The wrapped awaitable's `await_resume` is not | ||||||
| 445 | - | called in the first two; in the third, its canceled result | ||||||
| 446 | - | is dropped. | ||||||
| 447 | */ | 778 | */ | |||||
| HITCBC | 448 | 30 | decltype(auto) await_resume() | 779 | 5248 | decltype(auto) await_resume() | ||
| 449 | { | 780 | { | |||||
| HITCBC | 450 | 30 | set_current_frame_allocator( | 781 | 5248 | set_current_frame_allocator( | ||
| HITCBC | 451 | 30 | p_->environment()->frame_allocator); | 782 | 5248 | p_->environment()->frame_allocator); | ||
| ECB | 452 | - | 59 | if(skipped_ || | 783 | + | if constexpr (checks_canceled) | |
| DCB | 453 | - | 50 | (!completed_ && | ||||
| DCB | 454 | - | 21 | p_->environment()->stop_token.stop_requested())) | ||||
| DCB | 455 | - | 16 | throw detail::stop_requested_exception{}; | ||||
| 456 | - | using R = decltype(a_.await_resume()); | ||||||
| 457 | - | if constexpr (detail::is_results_v<R>) | ||||||
| 458 | - | { | ||||||
| 459 | - | // A result canceled by our own stop acquired | ||||||
| 460 | - | // nothing, so the stop can take effect here. | ||||||
| DCB | 461 | - | 5 | R r = a_.await_resume(); | ||||
| DCB | 462 | - | 7 | if(std::get<0>(r) == cond::canceled && | ||||
| DCB | 463 | - | 2 | p_->environment()->stop_token.stop_requested()) | ||||
| DCB | 464 | - | 1 | throw detail::stop_requested_exception{}; | ||||
| DCB | 465 | - | 8 | return r; | ||||
| DCB | 466 | - | 3 | } | ||||
| 467 | - | else | ||||||
| 468 | { | 784 | { | |||||
| HITCBC | 469 | - | 9 | return a_.await_resume(); | 785 | + | 5237 | if(result_) |
| HITGNC | 786 | + | 6 | return result_type(std::move(*result_)); | ||||
| 470 | } | 787 | } | |||||
| HITGNC | 788 | + | 5242 | return a_.await_resume(); | ||||
| 471 | } | 789 | } | |||||
| 472 | 790 | |||||||
| 473 | - | /** Suspend by calling the wrapped awaitable with the | 791 | + | /** Start the wrapped awaitable with the gate as its | |||
| 474 | - | environment. | 792 | + | continuation, or end the quitter on a pending stop. | |||
| 475 | 793 | |||||||
| 476 | - | This is the plain `await_suspend` the compiler calls for the | 794 | + | If a stop is pending, the segment is abandoned for the | |||
| 477 | - | nested `co_await`. It forwards to the wrapped awaitable's | 795 | + | stop and this coroutine stays suspended until it is | |||
| 478 | - | @ref ContextAwaitable overload, supplying the promise's stored | 796 | + | destroyed. Otherwise this coroutine becomes the gate's | |||
| 479 | - | environment as the second argument. It keeps the wrapped | 797 | + | target, and the wrapped awaitable's @ref ContextAwaitable | |||
| 480 | - | awaitable's suspension decision, whatever form it takes. | 798 | + | overload is called with the gate's handle and the | |||
| 481 | - | When that decision is to continue without suspending, the | 799 | + | promise's environment. If that call completes without | |||
| 482 | - | wrapped awaitable completed synchronously, and | 800 | + | suspending (returning `false`, or the gate's own handle), | |||
| 483 | - | @ref await_resume will call its `await_resume`. Returning | 801 | + | this coroutine continues to @ref await_resume without | |||
| 484 | - | `h` itself counts as continuing without suspending. | 802 | + | passing through the gate, unless its result is a | |||
| 803 | + | @ref results canceled by the stop (see | ||||||
| 804 | + | @ref stops_on_sync_completion). | ||||||
| 485 | 805 | |||||||
| 486 | @param h The coroutine performing the `co_await`. | 806 | @param h The coroutine performing the `co_await`. | |||||
| 487 | 807 | |||||||
| 488 | - | @return Whatever the wrapped awaitable's `await_suspend` | 808 | + | @return Where the workaround is off, the handle to resume: | |||
| 489 | - | returns. When that is a `std::coroutine_handle<>`, the | 809 | + | `h` to continue, a handle the wrapped awaitable returned, | |||
| 490 | - | handle is routed through `detail::symmetric_transfer`. | 810 | + | or `std::noop_coroutine()`. Where | |||
| 491 | - | Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, | 811 | + | `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, `false` | |||
| 492 | - | this function instead returns `bool`. It first checks the | 812 | + | to continue and `true` to stay suspended; the stop check | |||
| 493 | - | stop token, as @ref await_ready does where the workaround is | 813 | + | and the wrapped awaitable's `await_ready` both run here, | |||
| 494 | - | off, and returns `false` without touching the wrapped | 814 | + | through `detail::await_ready_then_suspend`. | |||
| 495 | - | awaitable if a stop is pending. Otherwise it returns the | ||||||
| 496 | - | result of `detail::await_ready_then_suspend`. Where the | ||||||
| 497 | - | workaround is off, a returned handle is passed back | ||||||
| 498 | - | unchanged for symmetric transfer. | ||||||
| 499 | */ | 815 | */ | |||||
| 500 | template<class Promise> | 816 | template<class Promise> | |||||
| HITCBC | 501 | 25 | auto await_suspend( | 817 | 10076 | auto await_suspend( | ||
| 502 | - | std::coroutine_handle<Promise> h) noexcept | 818 | + | std::coroutine_handle<Promise> h) noexcept(checks_noexcept) | |||
| 503 | { | 819 | { | |||||
| 504 | - | // completed_ is written only where nothing else can have | 820 | + | // After the wrapped awaitable's await_suspend, the gate | |||
| 505 | - | // resumed this frame: a frame that really suspended may | 821 | + | // may already have run and destroyed this frame on | |||
| 506 | - | // already be running, or gone, on another thread. | 822 | + | // another thread: only locals are used from then on, | |||
| 823 | + | // except after a synchronous completion, which leaves | ||||||
| 824 | + | // the frame to us. | ||||||
| HITGNC | 825 | + | 10076 | auto* seg = p_->seg_; | ||||
| HITGNC | 826 | + | 10076 | auto const* env = p_->environment(); | ||||
| 507 | #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND | 827 | #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND | |||||
| 508 | - | if(p_->environment()->stop_token.stop_requested()) | 828 | + | if(detail::quitter_stop_pending(env)) | |||
| 509 | { | 829 | { | |||||
| 510 | - | skipped_ = true; | 830 | + | seg->stop(); | |||
| 511 | - | return false; | 831 | + | return true; | |||
| 512 | } | 832 | } | |||||
| 513 | - | return detail::await_ready_then_suspend( | 833 | + | seg->target_ = h; | |||
| 514 | - | a_, h, p_->environment(), &completed_); | 834 | + | if(detail::await_ready_then_suspend( | |||
| 835 | + | a_, seg->handle(), env)) | ||||||
| 836 | + | return true; | ||||||
| 837 | + | if(stops_on_sync_completion()) | ||||||
| 838 | + | { | ||||||
| 839 | + | seg->stop(); | ||||||
| 840 | + | return true; | ||||||
| 841 | + | } | ||||||
| 842 | + | return false; | ||||||
| 515 | #else | 843 | #else | |||||
| HITGIC | 516 | - | using R = decltype( | 844 | + | 10076 | if(stop_here_) | |
| 517 | - | a_.await_suspend(h, p_->environment())); | ||||||
| 518 | - | if constexpr (std::is_same_v< | ||||||
| 519 | - | R, std::coroutine_handle<>>) | ||||||
| 520 | { | 845 | { | |||||
| HITGIC | 521 | - | std::coroutine_handle<> next = | 846 | + | 6 | seg->stop(); | |
| HITCBC | 522 | - | 22 | a_.await_suspend(h, p_->environment()); | 847 | + | 6 | return std::coroutine_handle<>(std::noop_coroutine()); |
| 523 | - | // Returning h means no one else can resume this | ||||||
| 524 | - | // frame: a synchronous completion. | ||||||
| 525 | - | // GCC falsely warns that writing completed_ overruns the noop | ||||||
| 526 | - | // coroutine's frame: when the wrapped await_suspend returns | ||||||
| 527 | - | // std::noop_coroutine(), it assumes the branch below can be taken | ||||||
| 528 | - | // and treats this frame as the noop frame. A noop handle never | ||||||
| 529 | - | // equals h, so the branch is dead on that path. | ||||||
| 530 | - | #if defined(__GNUC__) && !defined(__clang__) | ||||||
| 531 | - | #pragma GCC diagnostic push | ||||||
| 532 | - | #pragma GCC diagnostic ignored "-Warray-bounds" | ||||||
| 533 | - | #endif | ||||||
| DCB | 534 | - | 22 | if(next == h) | ||||
| DCB | 535 | - | 1 | completed_ = true; | ||||
| 536 | - | #if defined(__GNUC__) && !defined(__clang__) | ||||||
| 537 | - | #pragma GCC diagnostic pop | ||||||
| 538 | - | #endif | ||||||
| DCB | 539 | - | 22 | return detail::symmetric_transfer(next); | ||||
| 540 | } | 848 | } | |||||
| HITGIC | 541 | - | else if constexpr (std::is_same_v<R, bool>) | 849 | + | 10070 | seg->target_ = h; | |
| HITGNC | 850 | + | 10070 | std::coroutine_handle<> gate = seg->handle(); | ||||
| 851 | + | using R = decltype(a_.await_suspend(gate, env)); | ||||||
| 852 | + | if constexpr (std::is_same_v<R, bool>) | ||||||
| 542 | { | 853 | { | |||||
| HITCBC | 543 | - | 3 | if(!a_.await_suspend(h, p_->environment())) | 854 | + | 5 | if(!a_.await_suspend(gate, env)) |
| HITGIC | 544 | - | { | 855 | + | 4 | return continue_or_stop(h); | |
| HITCBC | 545 | - | 2 | completed_ = true; | 856 | + | 1 | return std::coroutine_handle<>(std::noop_coroutine()); |
| ECB | 546 | - | 2 | return false; | 857 | + | } | |
| 547 | - | } | 858 | + | else if constexpr (std::is_void_v<R>) | |||
| ECB | 548 | - | 1 | return true; | 859 | + | { | |
| 860 | + | a_.await_suspend(gate, env); | ||||||
| 861 | + | return std::coroutine_handle<>(std::noop_coroutine()); | ||||||
| 549 | } | 862 | } | |||||
| 550 | else | 863 | else | |||||
| 551 | { | 864 | { | |||||
| HITGIC | 552 | - | return a_.await_suspend( | 865 | + | 10065 | std::coroutine_handle<> r = a_.await_suspend(gate, env); | |
| HITGIC | 553 | - | h, p_->environment()); | 866 | + | 10065 | if(r == gate) | |
| HITGNC | 867 | + | 5 | return continue_or_stop(h); | ||||
| HITGNC | 868 | + | 10060 | return r; | ||||
| 554 | } | 869 | } | |||||
| 555 | #endif | 870 | #endif | |||||
| 556 | } | 871 | } | |||||
| 872 | + | |||||||
| 873 | + | #if !BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND | ||||||
| 874 | + | /** After a synchronous completion in @ref await_suspend, | ||||||
| 875 | + | continue to @ref await_resume, or end the quitter. | ||||||
| 876 | + | |||||||
| 877 | + | @param h The coroutine performing the `co_await`. | ||||||
| 878 | + | |||||||
| 879 | + | @return `h`, or `std::noop_coroutine()` when the quitter is | ||||||
| 880 | + | abandoned here (see @ref stops_on_sync_completion). | ||||||
| 881 | + | */ | ||||||
| 882 | + | std::coroutine_handle<> | ||||||
| HITGNC | 883 | + | 9 | continue_or_stop(std::coroutine_handle<> h) | ||||
| 884 | + | noexcept(checks_noexcept) | ||||||
| 885 | + | { | ||||||
| HITGNC | 886 | + | 9 | if(!stops_on_sync_completion()) | ||||
| HITGNC | 887 | + | 7 | return h; | ||||
| HITGNC | 888 | + | 2 | p_->seg_->stop(); | ||||
| HITGNC | 889 | + | 2 | return std::noop_coroutine(); | ||||
| 890 | + | } | ||||||
| 891 | + | #endif | ||||||
| 892 | + | }; | ||||||
| 893 | + | |||||||
| 894 | + | /** Awaiter for a nested `co_await` of a quitter. | ||||||
| 895 | + | |||||||
| 896 | + | The awaited quitter joins this coroutine's segment instead of | ||||||
| 897 | + | opening one: it runs in the same environment and gate, and a | ||||||
| 898 | + | stop ends both together. Its completion transfers straight | ||||||
| 899 | + | back to this coroutine. | ||||||
| 900 | + | |||||||
| 901 | + | @tparam Q The awaited quitter type. | ||||||
| 902 | + | */ | ||||||
| 903 | + | template<class Q> | ||||||
| 904 | + | struct join_awaiter | ||||||
| 905 | + | { | ||||||
| 906 | + | /// The awaited quitter, stored by value. | ||||||
| 907 | + | Q q_; | ||||||
| 908 | + | |||||||
| 909 | + | /// The promise of the coroutine performing the `co_await`. | ||||||
| 910 | + | promise_type* p_; | ||||||
| 911 | + | |||||||
| 912 | + | /// Return `false`; the joined quitter has not started. | ||||||
| HITGNC | 913 | + | 108 | bool await_ready() const noexcept | ||||
| 914 | + | { | ||||||
| HITGNC | 915 | + | 108 | return false; | ||||
| 916 | + | } | ||||||
| 917 | + | |||||||
| 918 | + | /** Start the joined quitter, or end the segment on a pending | ||||||
| 919 | + | stop. | ||||||
| 920 | + | |||||||
| 921 | + | @param h The coroutine performing the `co_await`. | ||||||
| 922 | + | |||||||
| 923 | + | @return The joined quitter's handle, for symmetric | ||||||
| 924 | + | transfer, or `std::noop_coroutine()` after a stop. Where | ||||||
| 925 | + | the workaround is on, `void`. | ||||||
| 926 | + | */ | ||||||
| 927 | + | template<class Promise> | ||||||
| HITGNC | 928 | + | 108 | auto await_suspend(std::coroutine_handle<Promise> h) noexcept | ||||
| 929 | + | { | ||||||
| HITGNC | 930 | + | 108 | auto const* env = p_->environment(); | ||||
| HITGNC | 931 | + | 108 | if(detail::quitter_stop_pending(env)) | ||||
| 932 | + | { | ||||||
| HITGNC | 933 | + | 1 | p_->seg_->stop(); | ||||
| HITGNC | 934 | + | 1 | return detail::symmetric_transfer( | ||||
| HITGNC | 935 | + | 2 | std::noop_coroutine()); | ||||
| 936 | + | } | ||||||
| HITGNC | 937 | + | 107 | return detail::symmetric_transfer( | ||||
| HITGNC | 938 | + | 214 | q_.join(h, p_->seg_, env)); | ||||
| 939 | + | } | ||||||
| 940 | + | |||||||
| 941 | + | /** Restore the frame allocator, then return the joined | ||||||
| 942 | + | quitter's result. | ||||||
| 943 | + | |||||||
| 944 | + | @return The joined quitter's await-result. | ||||||
| 945 | + | */ | ||||||
| HITGNC | 946 | + | 4 | decltype(auto) await_resume() | ||||
| 947 | + | { | ||||||
| HITGNC | 948 | + | 4 | set_current_frame_allocator( | ||||
| HITGNC | 949 | + | 4 | p_->environment()->frame_allocator); | ||||
| HITGNC | 950 | + | 4 | return q_.await_resume(); | ||||
| 951 | + | } | ||||||
| 557 | }; | 952 | }; | |||||
| 558 | 953 | |||||||
| 559 | /** Transform a nested awaitable before `co_await`. | 954 | /** Transform a nested awaitable before `co_await`. | |||||
| 560 | 955 | |||||||
| 561 | - | Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the | 956 | + | A quitter is wrapped in a @ref join_awaiter. Any other | |||
| 562 | - | coroutine's environment is propagated into it and the stop | 957 | + | @ref ContextAwaitable is wrapped in a @ref transform_awaiter. | |||
| 563 | - | token is checked on resumption. A diagnostic is emitted if the | 958 | + | A diagnostic is emitted for anything else. | |||
| 564 | - | awaitable does not satisfy @ref ContextAwaitable. | ||||||
| 565 | 959 | |||||||
| 566 | @param a The awaitable expression from `co_await a`. | 960 | @param a The awaitable expression from `co_await a`. | |||||
| 567 | 961 | |||||||
| 568 | - | @return A @ref transform_awaiter wrapping `a`. | 962 | + | @return The awaiter wrapping `a`. | |||
| 569 | */ | 963 | */ | |||||
| 570 | template<class Awaitable> | 964 | template<class Awaitable> | |||||
| HITCBC | 571 | 31 | auto transform_awaitable(Awaitable&& a) | 965 | 10188 | auto transform_awaitable(Awaitable&& a) | ||
| 572 | { | 966 | { | |||||
| 573 | using A = std::decay_t<Awaitable>; | 967 | using A = std::decay_t<Awaitable>; | |||||
| 574 | - | if constexpr (ContextAwaitable<A>) | 968 | + | if constexpr (detail::is_quitter<A>::value) | |||
| 969 | + | { | ||||||
| HITGNC | 970 | + | 216 | return join_awaiter<A>{std::forward<Awaitable>(a), this}; | ||||
| 971 | + | } | ||||||
| 972 | + | else if constexpr (ContextAwaitable<A>) | ||||||
| 575 | { | 973 | { | |||||
| 576 | return transform_awaiter<Awaitable>{ | 974 | return transform_awaiter<Awaitable>{ | |||||
| HITCBC | 577 | 51 | std::forward<Awaitable>(a), this}; | 975 | 20147 | std::forward<Awaitable>(a), this}; | ||
| 578 | } | 976 | } | |||||
| 579 | else | 977 | else | |||||
| 580 | { | 978 | { | |||||
| 581 | static_assert(sizeof(A) == 0, | 979 | static_assert(sizeof(A) == 0, | |||||
| 582 | "requires ContextAwaitable"); | 980 | "requires ContextAwaitable"); | |||||
| 583 | } | 981 | } | |||||
| HITCBC | 584 | 20 | } | 982 | 10169 | } | ||
| 585 | }; | 983 | }; | |||||
| 586 | 984 | |||||||
| 587 | /** Handle to the owned coroutine frame. | 985 | /** Handle to the owned coroutine frame. | |||||
| 588 | 986 | |||||||
| 589 | - | Null when the quitter is empty (for example after a move or after | 987 | + | Null when the quitter is empty (for example after a move, after | |||
| 590 | - | @ref release). Prefer @ref handle to read this; the member is | 988 | + | @ref release, or once a stop has destroyed the frame). Prefer | |||
| 591 | - | public for use by the coroutine machinery. | 989 | + | @ref handle to read this; the member is public for use by the | |||
| 990 | + | coroutine machinery. | ||||||
| 592 | */ | 991 | */ | |||||
| 593 | std::coroutine_handle<promise_type> h_; | 992 | std::coroutine_handle<promise_type> h_; | |||||
| 594 | 993 | |||||||
| 595 | /** Destroy the quitter and its coroutine frame if owned. | 994 | /** Destroy the quitter and its coroutine frame if owned. | |||||
| 596 | 995 | |||||||
| 597 | @par Preconditions | 996 | @par Preconditions | |||||
| 598 | The frame is not lent: no parent is suspended awaiting it, | 997 | The frame is not lent: no parent is suspended awaiting it, | |||||
| 599 | unless its chain is being unwound after an @ref abandon. | 998 | unless its chain is being unwound after an @ref abandon. | |||||
| 600 | Destroying a lent frame otherwise calls `std::terminate`; end | 999 | Destroying a lent frame otherwise calls `std::terminate`; end | |||||
| 601 | a running chain by requesting stop, or by destroying its | 1000 | a running chain by requesting stop, or by destroying its | |||||
| 602 | execution context. | 1001 | execution context. | |||||
| 603 | */ | 1002 | */ | |||||
| HITCBC | 604 | 83 | ~quitter() | 1003 | 30437 | ~quitter() | ||
| 605 | { | 1004 | { | |||||
| HITCBC | 606 | 83 | destroy_frame(); | 1005 | 30437 | destroy_frame(); | ||
| HITCBC | 607 | 83 | } | 1006 | 30437 | } | ||
| 608 | 1007 | |||||||
| 609 | /** Return false; quitters are never immediately ready. | 1008 | /** Return false; quitters are never immediately ready. | |||||
| 610 | 1009 | |||||||
| 611 | A quitter is lazy and has not started when it is awaited, so the | 1010 | A quitter is lazy and has not started when it is awaited, so the | |||||
| 612 | awaiting coroutine always suspends. | 1011 | awaiting coroutine always suspends. | |||||
| 613 | 1012 | |||||||
| 614 | @return `false`. | 1013 | @return `false`. | |||||
| 615 | */ | 1014 | */ | |||||
| HITCBC | 616 | 16 | bool await_ready() const noexcept | 1015 | 10051 | bool await_ready() const noexcept | ||
| 617 | { | 1016 | { | |||||
| HITCBC | 618 | 16 | return false; | 1017 | 10051 | return false; | ||
| 619 | } | 1018 | } | |||||
| 620 | 1019 | |||||||
| 621 | - | /** Return the result, rethrow exception, or propagate stop. | 1020 | + | /** Return the result, rethrow the body's exception, or deliver the stopped value. | |||
| 622 | - | |||||||
| 623 | - | When stopped, throws stop_requested_exception so that a | ||||||
| 624 | - | parent quitter also stops. A parent task<T> sees this | ||||||
| 625 | - | as an unhandled exception — by design. | ||||||
| 626 | 1021 | |||||||
| 627 | @return The result value for non-void `T`, moved out of the | 1022 | @return The result value for non-void `T`, moved out of the | |||||
| 628 | - | quitter; otherwise `void`. | 1023 | + | quitter, or the stopped value if the quitter was stopped. | |||
| 629 | 1024 | |||||||
| 630 | @par Exception Safety | 1025 | @par Exception Safety | |||||
| 631 | - | If the coroutine was stopped, the library's internal stop sentinel | 1026 | + | If the body exited via an exception, that exception is rethrown. | |||
| 632 | - | is thrown. If the body exited via any other exception, that | 1027 | + | If the quitter was stopped and `T` is not `results<Ts...>` or | |||
| 633 | - | exception is rethrown. | 1028 | + | `std::error_code`, a `std::system_error` whose `code()` | |||
| 1029 | + | compares equal to `cond::canceled` is thrown. | ||||||
| 634 | */ | 1030 | */ | |||||
| HITCBC | 635 | - | 12 | auto await_resume() | 1031 | + | 10045 | T await_resume() |
| 636 | { | 1032 | { | |||||
| HITCBC | 637 | 12 | lent_ = false; | 1033 | 10045 | lent_ = false; | ||
| HITCBC | 638 | - | 12 | if(h_.promise().stopped()) | 1034 | + | 10045 | if(stopped_) |
| HITCBC | 639 | - | 6 | throw detail::stop_requested_exception{}; | 1035 | + | 4805 | return promise_type::stopped_result(); |
| HITCBC | 640 | - | 6 | if(h_.promise().state_ == promise_type::completion::exception) | 1036 | + | 5240 | auto& p = h_.promise(); |
| HITCBC | 641 | - | 1 | std::rethrow_exception(h_.promise().ep_); | 1037 | + | 5240 | if(p.ep_) |
| HITGNC | 1038 | + | 1 | std::rethrow_exception(p.ep_); | ||||
| 642 | if constexpr (! std::is_void_v<T>) | 1039 | if constexpr (! std::is_void_v<T>) | |||||
| HITCBC | 643 | - | 4 | return std::move(*h_.promise().result_); | 1040 | + | 5238 | return std::move(*p.result_); |
| 644 | - | else | ||||||
| DCB | 645 | - | 1 | return; | ||||
| 646 | } | 1041 | } | |||||
| 647 | 1042 | |||||||
| 648 | - | /** Start execution with the caller's context. | 1043 | + | /** Open a segment and start the quitter with the caller's context. | |||
| 649 | 1044 | |||||||
| 650 | - | Stores `cont` as the continuation to resume on completion. | 1045 | + | If the stop token in `env` has already been triggered, the body | |||
| 651 | - | Stores `env` as the execution environment propagated to nested | 1046 | + | is not started. `cont` is returned, so the caller resumes at once | |||
| 652 | - | `co_await` expressions. Then transfers control into the quitter's | 1047 | + | with the stopped value. | |||
| 653 | - | coroutine body via the returned handle. | 1048 | + | ||||
| 1049 | + | Otherwise the quitter opens a segment of its own. The segment's | ||||||
| 1050 | + | owner destroys the quitter's frames if the segment is abandoned. | ||||||
| 1051 | + | Its gate is what the quitter's awaits complete through. The body | ||||||
| 1052 | + | runs with `env`'s executor, stop token and frame allocator, and | ||||||
| 1053 | + | the segment's owner. Control then transfers into the body. | ||||||
| 654 | 1054 | |||||||
| 655 | @param cont The awaiting coroutine to resume when the quitter | 1055 | @param cont The awaiting coroutine to resume when the quitter | |||||
| 656 | - | completes. | 1056 | + | completes or is stopped. | |||
| 657 | 1057 | |||||||
| 658 | - | @param env The execution environment (executor, stop token, and | 1058 | + | @param env The execution environment. It must outlive the | |||
| 659 | - | frame allocator). It must outlive the quitter. | 1059 | + | quitter. | |||
| 660 | 1060 | |||||||
| 661 | - | @return The quitter's coroutine handle, for symmetric transfer. | 1061 | + | @return The quitter's coroutine handle for symmetric transfer, or | |||
| 1062 | + | `cont`. | ||||||
| 1063 | + | |||||||
| 1064 | + | @throws std::bad_alloc If the segment's frame cannot be | ||||||
| 1065 | + | allocated. When the awaiting coroutine is a @ref task, its | ||||||
| 1066 | + | awaiter's `await_suspend` is `noexcept`, so `std::terminate` is | ||||||
| 1067 | + | called instead. | ||||||
| 662 | */ | 1068 | */ | |||||
| HITCBC | 663 | 16 | std::coroutine_handle<> await_suspend( | 1069 | 10044 | std::coroutine_handle<> await_suspend( | ||
| 664 | std::coroutine_handle<> cont, | 1070 | std::coroutine_handle<> cont, | |||||
| 665 | await_context const* env) | 1071 | await_context const* env) | |||||
| 666 | { | 1072 | { | |||||
| HITCBC | 667 | - | 16 | h_.promise().set_continuation(cont); | 1073 | + | 10044 | if(env->stop_token.stop_requested()) |
| ECB | 668 | - | 16 | h_.promise().set_environment(env); | 1074 | + | { | |
| HITGNC | 1075 | + | 4 | stopped_ = true; | ||||
| HITGNC | 1076 | + | 4 | return cont; | ||||
| 1077 | + | } | ||||||
| HITGNC | 1078 | + | 10040 | seg_ = detail::make_quitter_segment(); | ||||
| HITGNC | 1079 | + | 10040 | auto& sp = seg_.h_.promise(); | ||||
| HITGNC | 1080 | + | 10040 | sp.owner_.bind(seg_.h_, env->executor); | ||||
| HITGNC | 1081 | + | 10040 | sp.env_ = *env; | ||||
| HITGNC | 1082 | + | 10040 | sp.env_.owner = &sp.owner_; | ||||
| HITGNC | 1083 | + | 10040 | sp.frame_env_ = &sp.env_; | ||||
| HITGNC | 1084 | + | 10040 | sp.parent_.h = cont; | ||||
| HITGNC | 1085 | + | 10040 | sp.parent_.owner = env->owner; | ||||
| HITGNC | 1086 | + | 10040 | sp.opener_ = this; | ||||
| HITGNC | 1087 | + | 10040 | sp.active_ = true; | ||||
| HITGNC | 1088 | + | 10040 | auto& p = h_.promise(); | ||||
| HITGNC | 1089 | + | 10040 | p.seg_ = &sp; | ||||
| HITGNC | 1090 | + | 10040 | p.top_ = true; | ||||
| HITGNC | 1091 | + | 10040 | p.set_environment(&sp.env_); | ||||
| HITGNC | 1092 | + | 10040 | p.set_continuation(cont); | ||||
| HITCBC | 669 | 16 | lent_ = true; | 1093 | 10040 | lent_ = true; | ||
| HITCBC | 670 | 16 | return h_; | 1094 | 10040 | return h_; | ||
| 671 | } | 1095 | } | |||||
| 672 | 1096 | |||||||
| 673 | /** Return the coroutine handle. | 1097 | /** Return the coroutine handle. | |||||
| 674 | 1098 | |||||||
| 675 | @note Do not call `destroy()` on the returned handle while the | 1099 | @note Do not call `destroy()` on the returned handle while the | |||||
| 676 | quitter is being awaited: the awaiting frame still holds it and | 1100 | quitter is being awaited: the awaiting frame still holds it and | |||||
| 677 | destroys it again, which has undefined behavior. A holder that | 1101 | destroys it again, which has undefined behavior. A holder that | |||||
| 678 | gives the coroutine up passes its continuation to @ref abandon | 1102 | gives the coroutine up passes its continuation to @ref abandon | |||||
| 679 | instead. (Destroying or | 1103 | instead. (Destroying or | |||||
| 680 | move-assigning over the quitter object itself while its frame is | 1104 | move-assigning over the quitter object itself while its frame is | |||||
| 681 | lent is a different operation, and calls `std::terminate`.) | 1105 | lent is a different operation, and calls `std::terminate`.) | |||||
| 682 | The quitter's lifetime is normally managed by `run_async`, `run`, | 1106 | The quitter's lifetime is normally managed by `run_async`, `run`, | |||||
| 683 | - | or the awaiting parent. For cooperative cancellation, use | 1107 | + | or the awaiting parent. | |||
| 684 | - | `std::stop_token`. | ||||||
| 685 | 1108 | |||||||
| 686 | @note The operation, queue or primitive holding a suspended | 1109 | @note The operation, queue or primitive holding a suspended | |||||
| 687 | coroutine gives it up by passing the continuation it keeps to | 1110 | coroutine gives it up by passing the continuation it keeps to | |||||
| 688 | @ref abandon. `handle()` belongs to the owning quitter object. | 1111 | @ref abandon. `handle()` belongs to the owning quitter object. | |||||
| 689 | 1112 | |||||||
| 1113 | + | @note To run the quitter for an awaiting coroutine, call its | ||||||
| 1114 | + | `await_suspend`, which opens a segment for it. Do not resume | ||||||
| 1115 | + | this handle with the awaiting coroutine's environment: a stop | ||||||
| 1116 | + | then abandons the awaiting coroutine's segment, and a @ref task | ||||||
| 1117 | + | chain in it is torn down with no handler called. When | ||||||
| 1118 | + | `run_async` or `run` owns that segment, a debug build asserts. | ||||||
| 1119 | + | A launcher that resumes the handle itself must own the segment: | ||||||
| 1120 | + | see @ref await_context::owner. | ||||||
| 1121 | + | |||||||
| 690 | @return The coroutine handle. | 1122 | @return The coroutine handle. | |||||
| 691 | */ | 1123 | */ | |||||
| HITCBC | 692 | 29 | std::coroutine_handle<promise_type> handle() const noexcept | 1124 | 65 | std::coroutine_handle<promise_type> handle() const noexcept | ||
| 693 | { | 1125 | { | |||||
| HITCBC | 694 | 29 | return h_; | 1126 | 65 | return h_; | ||
| 695 | } | 1127 | } | |||||
| 696 | 1128 | |||||||
| 697 | /** Release ownership of the coroutine frame. | 1129 | /** Release ownership of the coroutine frame. | |||||
| 698 | 1130 | |||||||
| 699 | @note The caller may call `destroy()` on the released handle | 1131 | @note The caller may call `destroy()` on the released handle | |||||
| 700 | only when the quitter has not started or has fully completed. | 1132 | only when the quitter has not started or has fully completed. | |||||
| 701 | Destroying the released handle of a quitter that is being | 1133 | Destroying the released handle of a quitter that is being | |||||
| 702 | awaited is not supported and has undefined behavior. | 1134 | awaited is not supported and has undefined behavior. | |||||
| 703 | 1135 | |||||||
| 704 | @par Postconditions | 1136 | @par Postconditions | |||||
| 705 | `handle()` returns a null handle. Callers needing the | 1137 | `handle()` returns a null handle. Callers needing the | |||||
| 706 | original handle must save it, via @ref handle, before | 1138 | original handle must save it, via @ref handle, before | |||||
| 707 | calling this. | 1139 | calling this. | |||||
| 708 | */ | 1140 | */ | |||||
| HITCBC | 709 | 27 | void release() noexcept | 1141 | 50 | void release() noexcept | ||
| 710 | { | 1142 | { | |||||
| HITCBC | 711 | 27 | h_ = nullptr; | 1143 | 50 | h_ = nullptr; | ||
| HITCBC | 712 | 27 | } | 1144 | 50 | } | ||
| 713 | 1145 | |||||||
| 714 | /** Copy construction is disabled; a quitter uniquely owns its frame. | 1146 | /** Copy construction is disabled; a quitter uniquely owns its frame. | |||||
| 715 | 1147 | |||||||
| 716 | @param other The quitter that would be copied. | 1148 | @param other The quitter that would be copied. | |||||
| 717 | */ | 1149 | */ | |||||
| 718 | quitter(quitter const& other) = delete; | 1150 | quitter(quitter const& other) = delete; | |||||
| 719 | 1151 | |||||||
| 720 | /** Copy assignment is disabled; a quitter uniquely owns its frame. | 1152 | /** Copy assignment is disabled; a quitter uniquely owns its frame. | |||||
| 721 | 1153 | |||||||
| 722 | @param other The quitter that would be assigned from. | 1154 | @param other The quitter that would be assigned from. | |||||
| 723 | 1155 | |||||||
| 724 | @return A reference to `*this`. | 1156 | @return A reference to `*this`. | |||||
| 725 | */ | 1157 | */ | |||||
| 726 | quitter& operator=(quitter const& other) = delete; | 1158 | quitter& operator=(quitter const& other) = delete; | |||||
| 727 | 1159 | |||||||
| 728 | /** Construct by moving, transferring ownership. | 1160 | /** Construct by moving, transferring ownership. | |||||
| 729 | 1161 | |||||||
| 1162 | + | @par Preconditions | ||||||
| 1163 | + | `other` is not being awaited. | ||||||
| 1164 | + | |||||||
| 730 | @par Postconditions | 1165 | @par Postconditions | |||||
| 731 | `other` is empty and must not be awaited. | 1166 | `other` is empty and must not be awaited. | |||||
| 732 | 1167 | |||||||
| 733 | @param other The quitter to move from. | 1168 | @param other The quitter to move from. | |||||
| 734 | */ | 1169 | */ | |||||
| HITCBC | 735 | 38 | quitter(quitter&& other) noexcept | 1170 | 20233 | quitter(quitter&& other) noexcept | ||
| HITCBC | 736 | 38 | : h_(std::exchange(other.h_, nullptr)) | 1171 | 20233 | : h_(std::exchange(other.h_, nullptr)) | ||
| 737 | { | 1172 | { | |||||
| HITGNC | 1173 | + | 20233 | release_top_ = other.release_top_; | ||||
| HITCBC | 738 | 38 | } | 1174 | 20233 | } | ||
| 739 | 1175 | |||||||
| 740 | /** Assign by moving, transferring ownership. | 1176 | /** Assign by moving, transferring ownership. | |||||
| 741 | 1177 | |||||||
| 742 | If this quitter already owns a coroutine frame, that frame is | 1178 | If this quitter already owns a coroutine frame, that frame is | |||||
| 743 | destroyed first. Self-assignment is a no-op. | 1179 | destroyed first. Self-assignment is a no-op. | |||||
| 744 | 1180 | |||||||
| 745 | @par Preconditions | 1181 | @par Preconditions | |||||
| 746 | The frame being replaced is not lent: no parent is suspended | 1182 | The frame being replaced is not lent: no parent is suspended | |||||
| 747 | awaiting it. Replacing a lent frame calls `std::terminate`. | 1183 | awaiting it. Replacing a lent frame calls `std::terminate`. | |||||
| 1184 | + | `other` is not being awaited. | ||||||
| 748 | 1185 | |||||||
| 749 | @par Postconditions | 1186 | @par Postconditions | |||||
| 750 | `other` is empty and must not be awaited. | 1187 | `other` is empty and must not be awaited. | |||||
| 751 | 1188 | |||||||
| 752 | @param other The quitter to move from. | 1189 | @param other The quitter to move from. | |||||
| 753 | 1190 | |||||||
| 754 | @return A reference to `*this`. | 1191 | @return A reference to `*this`. | |||||
| 755 | */ | 1192 | */ | |||||
| HITCBC | 756 | 1 | quitter& operator=(quitter&& other) noexcept | 1193 | 1 | quitter& operator=(quitter&& other) noexcept | ||
| 757 | { | 1194 | { | |||||
| HITCBC | 758 | 1 | if(this != &other) | 1195 | 1 | if(this != &other) | ||
| 759 | { | 1196 | { | |||||
| HITCBC | 760 | 1 | destroy_frame(); | 1197 | 1 | destroy_frame(); | ||
| HITCBC | 761 | 1 | h_ = std::exchange(other.h_, nullptr); | 1198 | 1 | h_ = std::exchange(other.h_, nullptr); | ||
| HITGNC | 1199 | + | 1 | stopped_ = false; | ||||
| 762 | } | 1200 | } | |||||
| HITCBC | 763 | 1 | return *this; | 1201 | 1 | return *this; | ||
| 764 | } | 1202 | } | |||||
| 765 | 1203 | |||||||
| 766 | private: | 1204 | private: | |||||
| 767 | - | // Set while a parent is suspended awaiting this quitter. | 1205 | + | template<class> | |||
| 768 | - | bool lent_ = false; | 1206 | + | friend struct quitter; | |||
| 769 | 1207 | |||||||
| HITCBC | 770 | 45 | explicit quitter(std::coroutine_handle<promise_type> h) | 1208 | 10204 | explicit quitter(std::coroutine_handle<promise_type> h) | ||
| HITCBC | 771 | 45 | : h_(h) | 1209 | 10204 | : h_(h) | ||
| 772 | { | 1210 | { | |||||
| HITGNC | 1211 | + | 10204 | release_top_ = &release_top; | ||||
| HITGNC | 1212 | + | 10204 | } | ||||
| 1213 | + | |||||||
| 1214 | + | static std::coroutine_handle<> | ||||||
| HITGNC | 1215 | + | 4804 | release_top(detail::quitter_opener& o) noexcept | ||||
| 1216 | + | { | ||||||
| HITGNC | 1217 | + | 4804 | return std::exchange(static_cast<quitter&>(o).h_, nullptr); | ||||
| 1218 | + | } | ||||||
| 1219 | + | |||||||
| 1220 | + | // Start as a quitter joining the segment of the quitter `parent`. | ||||||
| HITGNC | 1221 | + | 107 | std::coroutine_handle<> join( | ||||
| 1222 | + | std::coroutine_handle<> parent, | ||||||
| 1223 | + | detail::quitter_segment::promise_type* seg, | ||||||
| 1224 | + | await_context const* env) noexcept | ||||||
| 1225 | + | { | ||||||
| HITGNC | 1226 | + | 107 | auto& p = h_.promise(); | ||||
| HITGNC | 1227 | + | 107 | p.seg_ = seg; | ||||
| HITGNC | 1228 | + | 107 | p.set_environment(env); | ||||
| HITGNC | 1229 | + | 107 | p.set_continuation(parent); | ||||
| HITGNC | 1230 | + | 107 | lent_ = true; | ||||
| HITGNC | 1231 | + | 107 | return h_; | ||||
| ECB | 773 | 45 | } | 1232 | } | |||
| 774 | 1233 | |||||||
| HITCBC | 775 | 84 | void destroy_frame() noexcept | 1234 | 30438 | void destroy_frame() noexcept | ||
| 776 | { | 1235 | { | |||||
| HITCBC | 777 | - | 84 | if(!h_) | 1236 | + | 30438 | if(h_) |
| DCB | 778 | - | 66 | return; | ||||
| DCB | 779 | - | 18 | if(lent_ && !h_.done()) | ||||
| 780 | { | 1237 | { | |||||
| HITGIC | 781 | - | // Only the unwind of an abandoned chain destroys a frame | 1238 | + | 5350 | if(lent_ && !h_.done()) | |
| 782 | - | // that is still awaited. | 1239 | + | { | |||
| ECB | 783 | - | 1 | auto* owner = h_.promise().environment()->owner; | 1240 | + | // Only the unwind of an abandoned chain destroys a frame | |
| ECB | 784 | - | 1 | if(!owner || !owner->abandoned()) | 1241 | + | // that is still awaited. | |
| HITGIC | 785 | - | std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process | 1242 | + | 103 | auto* owner = h_.promise().environment()->owner; | |
| HITGNC | 1243 | + | 103 | if(!owner || !owner->abandoned()) | ||||
| 1244 | + | std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process | ||||||
| 1245 | + | } | ||||||
| HITGNC | 1246 | + | 5350 | std::exchange(h_, nullptr).destroy(); | ||||
| 1247 | + | } | ||||||
| HITGNC | 1248 | + | 30438 | if(seg_.h_) | ||||
| 1249 | + | { | ||||||
| HITGNC | 1250 | + | 10039 | seg_.h_.promise().active_ = false; | ||||
| HITGNC | 1251 | + | 10039 | std::exchange(seg_.h_, nullptr).destroy(); | ||||
| 786 | - | std::exchange(h_, nullptr).destroy(); | ||||||
| ECB | 787 | 18 | } | 1252 | } | |||
| HITCBC | 788 | 18 | lent_ = false; | 1253 | 30438 | lent_ = false; | ||
| HITGIC | 789 | } | 1254 | 30438 | } | |||
| 790 | }; | 1255 | }; | |||||
| 791 | 1256 | |||||||
| 792 | } // namespace capy | 1257 | } // namespace capy | |||||
| 793 | } // namespace boost | 1258 | } // namespace boost | |||||
| 794 | 1259 | |||||||
| 795 | #endif | 1260 | #endif | |||||