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