LCOV - code coverage report
Current view: top level - capy - quitter.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 229 229
Test Date: 2026-10-30 19:48:36 Functions: 74.5 % 435 324 111

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Michael Vandeberg
       3                 : //
       4                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/capy
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_CAPY_QUITTER_HPP
      11                 : #define BOOST_CAPY_QUITTER_HPP
      12                 : 
      13                 : #include <boost/capy/detail/config.hpp>
      14                 : #include <boost/capy/concept/executor.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>
      20                 : #include <boost/capy/ex/context_awaitable_promise_base.hpp>
      21                 : #include <boost/capy/ex/await_context.hpp>
      22                 : #include <boost/capy/ex/frame_alloc_mixin.hpp>
      23                 : #include <boost/capy/ex/frame_allocator.hpp>
      24                 : #include <boost/capy/detail/await_suspend_helper.hpp>
      25                 : #include <boost/capy/detail/quitter_stopped.hpp>
      26                 : #include <boost/capy/results.hpp>
      27                 : 
      28                 : #include <coroutine>
      29                 : #include <exception>
      30                 : #include <optional>
      31                 : #include <system_error>
      32                 : #include <type_traits>
      33                 : #include <utility>
      34                 : 
      35                 : /* Stop-aware coroutine task.
      36                 : 
      37                 :    quitter<T> is task<T>, except that a stop request ends its frames
      38                 :    by abandonment instead of resuming them. Nothing in the body runs
      39                 :    after the stop, so nothing can intercept it, and no exception is
      40                 :    thrown inside the quitter.
      41                 : 
      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. */
      93                 : 
      94                 : namespace boost {
      95                 : namespace capy {
      96                 : 
      97                 : template<typename T>
      98                 : struct quitter;
      99                 : 
     100                 : namespace detail {
     101                 : 
     102                 : template<typename T>
     103                 : struct quitter_return_base
     104                 : {
     105                 :     std::optional<T> result_;
     106                 : 
     107 HIT        5245 :     void return_value(T value)
     108                 :     {
     109            5245 :         result_ = std::move(value);
     110            5245 :     }
     111                 : 
     112               5 :     T&& result() noexcept
     113                 :     {
     114               5 :         return std::move(*result_);
     115                 :     }
     116                 : };
     117                 : 
     118                 : template<>
     119                 : struct quitter_return_base<void>
     120                 : {
     121               8 :     void return_void()
     122                 :     {
     123               8 :     }
     124                 : };
     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                 : {
     136              29 :     static T get()
     137                 :     {
     138                 : #if defined(__cpp_exceptions)
     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                 : 
     157            4806 :     static results<Ts...> get()
     158                 :     {
     159            4806 :         return results<Ts...>(make_error_code(error::canceled), Ts{}...);
     160                 :     }
     161                 : };
     162                 : 
     163                 : template<>
     164                 : struct stopped_value<std::error_code>
     165                 : {
     166               1 :     static std::error_code get() noexcept
     167                 :     {
     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
     182           20303 : quitter_stop_pending(await_context const* env) noexcept
     183                 : {
     184           20303 :     if(!env->stop_token.stop_requested())
     185           15467 :         return false;
     186            4836 :     BOOST_CAPY_ASSERT(env->owner &&
     187                 :         "a quitter's environment needs an owner to stop");
     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                 : 
     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.
     239           10090 :         if(active_)
     240                 :         {
     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.
     244               1 :             opener_->seg_.h_ = nullptr;
     245               1 :             opener_->release_top_(*opener_).destroy();
     246               1 :             auto parent = parent_;
     247               1 :             capy::abandon(parent);
     248                 :         }
     249           10090 :     }
     250                 : 
     251           10090 :     quitter_segment get_return_object() noexcept
     252                 :     {
     253                 :         return quitter_segment{
     254           10090 :             std::coroutine_handle<promise_type>::from_promise(*this)};
     255                 :     }
     256                 : 
     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                 : 
     262           10118 :     std::coroutine_handle<> handle() noexcept
     263                 :     {
     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.
     269            4834 :     void stop() noexcept
     270                 :     {
     271            4834 :         frame_env_->owner->abandon_for_stop();
     272            4834 :     }
     273                 : 
     274           14909 :     std::coroutine_handle<> decide() noexcept
     275                 :     {
     276           14909 :         if(opener_ && owner_.abandoned())
     277                 :         {
     278            4803 :             active_ = false;
     279            4803 :             auto* op = opener_;
     280            4803 :             op->release_top_(*op).destroy();
     281            4803 :             if(owner_.stopped())
     282                 :             {
     283            4801 :                 op->stopped_ = true;
     284            4801 :                 return parent_.h;
     285                 :             }
     286               2 :             auto parent = parent_;
     287               2 :             capy::abandon(parent);
     288               2 :             return std::noop_coroutine();
     289                 :         }
     290           10106 :         if(quitter_stop_pending(frame_env_))
     291                 :         {
     292            4825 :             stop();
     293            4825 :             return std::noop_coroutine();
     294                 :         }
     295            5281 :         return target_;
     296                 :     }
     297                 : };
     298                 : 
     299                 : struct quitter_hop
     300                 : {
     301           14909 :     bool await_ready() const noexcept { return false; }
     302                 : 
     303           14909 :     auto await_suspend(
     304                 :         std::coroutine_handle<quitter_segment::promise_type> h) noexcept
     305                 :     {
     306           14909 :         return detail::symmetric_transfer(h.promise().decide());
     307                 :     }
     308                 : 
     309            4824 :     void await_resume() const noexcept {}
     310                 : };
     311                 : 
     312                 : inline quitter_segment
     313           10090 : make_quitter_segment()
     314                 : {
     315                 :     for(;;)
     316                 :         co_await quitter_hop{};
     317           20180 : }
     318                 : 
     319                 : } // namespace detail
     320                 : 
     321                 : /** Defers a coroutine body until awaited, and ends it on a stop request without resuming it.
     322                 : 
     323                 :     A `quitter` is a @ref task whose author has chosen that a stop
     324                 :     request ends it. When the stop token fires, the quitter's frames are
     325                 :     not resumed: they are destroyed, from the top down, as an abandoned
     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.
     331                 : 
     332                 :     A quitter stops only quitter frames. A @ref task below a quitter
     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.
     340                 : 
     341                 :     @par Await-effects
     342                 : 
     343                 :     Let `q` be a `quitter<T>`. If the stop token in the awaiting
     344                 :     environment has already been triggered, `co_await q` does not start
     345                 :     the body, and await-returns the stopped value. Otherwise it suspends
     346                 :     the awaiting coroutine and transfers control directly into the
     347                 :     quitter's body on the current thread; no executor operation is
     348                 :     posted. The quitter propagates the caller's executor, stop token and
     349                 :     frame allocator to every `co_await` inside the body.
     350                 : 
     351                 :     At every `co_await` in the body, the stop token is checked before
     352                 :     the awaited operation is started. An operation that completes
     353                 :     asynchronously does not resume the body directly: the stop token is
     354                 :     checked again first. If either check finds a stop, the body is not
     355                 :     resumed. The quitter's frames are destroyed on the executor, and
     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.
     359                 : 
     360                 :     An operation that completes without suspending always has
     361                 :     its `await_resume` called, because its `await_ready` or
     362                 :     `await_suspend` may already have acquired a resource, such as
     363                 :     `async_mutex`'s lock, that only its `await_resume` hands over. If
     364                 :     that result is a @ref results holding `cond::canceled` while a stop
     365                 :     is pending, the operation saw the stop first and acquired nothing,
     366                 :     so the quitter is stopped at that `co_await`. Any other result is
     367                 :     handed to the body, and the stop takes effect at the next
     368                 :     suspension. An operation whose result is not a @ref results can
     369                 :     therefore hand the body one result it produced after seeing the
     370                 :     stop.
     371                 : 
     372                 :     When the body returns or exits via an exception, control transfers
     373                 :     directly back to the awaiting coroutine.
     374                 : 
     375                 :     @par Await-returns
     376                 : 
     377                 :     The value the body passed to `co_return`, moved out of the quitter,
     378                 :     or nothing when `T` is `void`. If the body exits via an unhandled
     379                 :     exception, that exception is rethrown instead.
     380                 : 
     381                 :     If the quitter was stopped, the stopped value instead:
     382                 : 
     383                 :     @li for `results<Ts...>`: `make_error_code(error::canceled)` with
     384                 :         each `Ts` value-initialized; it compares equal to
     385                 :         `cond::canceled`. Every `Ts` must be default-constructible.
     386                 :     @li for `std::error_code`: `make_error_code(error::canceled)`.
     387                 :     @li for any other `T`, `void` included: no value; an exception of
     388                 :         an unspecified type derived from `std::system_error`, whose
     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.
     404                 : 
     405                 :     @par Await-postcondition
     406                 :     The quitter's coroutine has completed, has been destroyed by a
     407                 :     stop, or, when the stop was requested before it was awaited, was
     408                 :     never started. A quitter must not be awaited twice.
     409                 : 
     410                 :     @par Remarks
     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.
     419                 : 
     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`:
     427                 : 
     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
     437                 : */
     438                 : template<typename T = void>
     439                 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
     440                 :     quitter
     441                 :     : private detail::quitter_opener
     442                 : {
     443                 :     /** Stores `quitter<T>`'s result and routes its awaits through the segment's gate.
     444                 : 
     445                 :         This is the promise object the compiler associates with a
     446                 :         `quitter<T>` coroutine. It satisfies the coroutine promise
     447                 :         requirements and participates in the I/O awaitable protocol via
     448                 :         @ref context_awaitable_promise_base. Unlike @ref task::promise_type,
     449                 :         its `transform_awaitable` checks the stop token before each
     450                 :         awaited operation starts. It hands the operation the segment's
     451                 :         gate as the continuation, not this coroutine. It is part of
     452                 :         the coroutine machinery and is not intended to be used directly
     453                 :         by callers.
     454                 : 
     455                 :         Result storage and `return_value`/`return_void` are provided by
     456                 :         `detail::quitter_return_base<T>`.
     457                 : 
     458                 :         @see @ref context_awaitable_promise_base, @ref ContextRunnable
     459                 :     */
     460                 :     struct promise_type
     461                 :         : context_awaitable_promise_base<promise_type>
     462                 :         , detail::quitter_return_base<T>
     463                 :     {
     464                 :     private:
     465                 :         friend quitter;
     466                 : 
     467                 :         template<class>
     468                 :         friend struct quitter;
     469                 : 
     470                 :         std::exception_ptr ep_;
     471                 : 
     472                 :         // The gate of the segment this frame runs in.
     473                 :         detail::quitter_segment::promise_type* seg_ = nullptr;
     474                 : 
     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                 : 
     481              50 :         void open_gate()
     482                 :         {
     483              50 :             own_gate_ = detail::make_quitter_segment();
     484              50 :             seg_ = &own_gate_.h_.promise();
     485              50 :             seg_->frame_env_ = this->environment();
     486              50 :         }
     487                 : 
     488                 :     public:
     489                 :         /// Construct the promise.
     490           10204 :         promise_type() noexcept = default;
     491                 : 
     492                 :         /// Destroy the promise, and the gate it owns, if any.
     493           10204 :         ~promise_type()
     494                 :         {
     495           10204 :             if(own_gate_.h_)
     496              50 :                 own_gate_.h_.destroy();
     497           10204 :         }
     498                 : 
     499                 :         /** Return the exception the body exited with, if any.
     500                 : 
     501                 :             @return The stored exception if the coroutine exited via an
     502                 :             exception, otherwise a null `std::exception_ptr`.
     503                 :         */
     504              18 :         std::exception_ptr exception() const noexcept
     505                 :         {
     506              18 :             return ep_;
     507                 :         }
     508                 : 
     509                 :         /** Return what a stopped `quitter<T>` delivers.
     510                 : 
     511                 :             Launchers that start a quitter directly call this when its
     512                 :             segment was abandoned for a stop: see
     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`.
     522                 :         */
     523            4836 :         static T stopped_result()
     524                 :         {
     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                 :         */
     544              48 :         std::coroutine_handle<> launch_handle()
     545                 :         {
     546              48 :             open_gate();
     547              48 :             seg_->target_ =
     548              48 :                 std::coroutine_handle<promise_type>::from_promise(*this);
     549              48 :             return seg_->handle();
     550                 :         }
     551                 : 
     552                 :         /** Return the owning `quitter` for this coroutine.
     553                 : 
     554                 :             Called by the compiler to produce the object returned to the
     555                 :             caller when the coroutine is created.
     556                 : 
     557                 :             @return A `quitter` owning the coroutine frame.
     558                 :         */
     559           10204 :         quitter get_return_object()
     560                 :         {
     561                 :             return quitter{
     562           10204 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
     563                 :         }
     564                 : 
     565                 :         /** Return the initial-suspend awaiter.
     566                 : 
     567                 :             The coroutine always suspends at the initial suspend point,
     568                 :             so the body does not start until the quitter is awaited or
     569                 :             started. When the body is resumed, the awaiter restores the
     570                 :             thread-local frame allocator. A quitter started directly by a
     571                 :             launcher that did not call @ref launch_handle creates its
     572                 :             gate at that point.
     573                 : 
     574                 :             @return An awaiter that suspends unconditionally.
     575                 :         */
     576           10204 :         auto initial_suspend() noexcept
     577                 :         {
     578                 :             struct awaiter
     579                 :             {
     580                 :                 promise_type* p_;
     581                 : 
     582           10204 :                 bool await_ready() const noexcept
     583                 :                 {
     584           10204 :                     return false;
     585                 :                 }
     586                 : 
     587           10204 :                 void await_suspend(std::coroutine_handle<>) const noexcept
     588                 :                 {
     589           10204 :                 }
     590                 : 
     591                 :                 // Allocates the gate of a quitter that a launcher started
     592                 :                 // without launch_handle(); a failure is reported as the
     593                 :                 // body's exception.
     594           10193 :                 void await_resume() const
     595                 :                 {
     596           10193 :                     set_current_frame_allocator(
     597           10193 :                         p_->environment()->frame_allocator);
     598           10193 :                     if(!p_->seg_)
     599               2 :                         p_->open_gate();
     600           10193 :                 }
     601                 :             };
     602           10204 :             return awaiter{this};
     603                 :         }
     604                 : 
     605                 :         /** Return the final-suspend awaiter.
     606                 : 
     607                 :             The coroutine always suspends at the final suspend point. The
     608                 :             awaiter's `await_suspend` performs symmetric transfer to the
     609                 :             stored continuation, resuming the awaiting coroutine.
     610                 : 
     611                 :             @return An awaiter that suspends and transfers to the
     612                 :             continuation.
     613                 :         */
     614            5257 :         auto final_suspend() noexcept
     615                 :         {
     616                 :             struct awaiter
     617                 :             {
     618                 :                 promise_type* p_;
     619                 : 
     620            5257 :                 bool await_ready() const noexcept
     621                 :                 {
     622            5257 :                     return false;
     623                 :                 }
     624                 : 
     625            5257 :                 std::coroutine_handle<> await_suspend(
     626                 :                     std::coroutine_handle<>) const noexcept
     627                 :                 {
     628                 :                     // The top frame completed: its segment owes no
     629                 :                     // unwind.
     630            5257 :                     if(p_->top_)
     631            5236 :                         p_->seg_->active_ = false;
     632            5257 :                     return p_->continuation();
     633                 :                 }
     634                 : 
     635                 :                 void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
     636                 :             };
     637            5257 :             return awaiter{this};
     638                 :         }
     639                 : 
     640                 :         /** Capture the in-flight exception from the coroutine body.
     641                 : 
     642                 :             Called by the compiler when the coroutine body exits via an
     643                 :             unhandled exception. The exception is rethrown when the
     644                 :             quitter is awaited, or routed to the error handler when it is
     645                 :             run.
     646                 :         */
     647               4 :         void unhandled_exception() noexcept
     648                 :         {
     649               4 :             ep_ = std::current_exception();
     650               4 :         }
     651                 : 
     652                 :         /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable.
     653                 : 
     654                 :             Forwards the environment to the inner awaitable's
     655                 :             environment-taking `await_suspend`, and restores the
     656                 :             thread-local frame allocator before the body resumes. Unlike
     657                 :             `task`'s, it checks the stop token before the inner awaitable
     658                 :             is touched. It passes the inner awaitable the segment's gate
     659                 :             as the continuation, not this coroutine.
     660                 : 
     661                 :             @tparam Awaitable The awaitable being transformed.
     662                 :         */
     663                 :         template<class Awaitable>
     664                 :         struct transform_awaiter
     665                 :         {
     666                 :             /// The wrapped awaitable, decayed and stored by value.
     667                 :             std::decay_t<Awaitable> a_;
     668                 : 
     669                 :             /// The promise of the coroutine performing the `co_await`.
     670                 :             promise_type* p_;
     671                 : 
     672                 :             /// The wrapped awaitable's await-result type.
     673                 :             using result_type = decltype(
     674                 :                 std::declval<std::decay_t<Awaitable>&>().await_resume());
     675                 : 
     676                 :             /// True when the await-result is a @ref results, which is
     677                 :             /// checked for a canceled stop after a synchronous completion.
     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                 :             */
     717              14 :             bool stops_on_sync_completion() noexcept(checks_noexcept)
     718                 :             {
     719                 :                 if constexpr (checks_canceled)
     720                 :                 {
     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.
     725              14 :                     return detail::quitter_stop_pending(p_->environment()) &&
     726              14 :                         std::get<0>(*result_) == cond::canceled;
     727                 :                 }
     728                 :                 else
     729                 :                 {
     730               5 :                     return false;
     731                 :                 }
     732                 :             }
     733                 : 
     734                 :             /** Report whether the `co_await` can continue without
     735                 :                 suspending.
     736                 : 
     737                 :                 The stop token is checked first. If a stop is pending, the
     738                 :                 wrapped awaitable is never touched: this returns `false`,
     739                 :                 and @ref await_suspend ends the quitter. Otherwise the
     740                 :                 wrapped awaitable's own `await_ready` decides. A `true`
     741                 :                 result means it completed synchronously. Its
     742                 :                 `await_resume` then hands over its result, even if a stop
     743                 :                 arrives afterwards, unless that result is a @ref results
     744                 :                 canceled by the stop (see @ref stops_on_sync_completion).
     745                 : 
     746                 :                 Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
     747                 :                 this always returns `false`, and @ref await_suspend does both
     748                 :                 steps instead. See `detail::await_ready_then_suspend`.
     749                 : 
     750                 :                 @return `true` if no suspension is needed.
     751                 :             */
     752           10080 :             bool await_ready() noexcept(checks_noexcept)
     753                 :             {
     754                 : #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
     755                 :                 return false;
     756                 : #else
     757           10080 :                 if(detail::quitter_stop_pending(p_->environment()))
     758                 :                 {
     759               5 :                     stop_here_ = true;
     760               5 :                     return false;
     761                 :                 }
     762           10075 :                 if(!a_.await_ready())
     763           10070 :                     return false;
     764               5 :                 stop_here_ = stops_on_sync_completion();
     765               5 :                 return !stop_here_;
     766                 : #endif
     767                 :             }
     768                 : 
     769                 :             /** Restore the frame allocator, then return the wrapped
     770                 :                 awaitable's result.
     771                 : 
     772                 :                 The body is resumed only when the wrapped awaitable
     773                 :                 completed synchronously, or completed through the gate
     774                 :                 with no stop requested.
     775                 : 
     776                 :                 @return The wrapped awaitable's await-result, forwarded
     777                 :                 unchanged, or the result already taken from it.
     778                 :             */
     779            5248 :             decltype(auto) await_resume()
     780                 :             {
     781            5248 :                 set_current_frame_allocator(
     782            5248 :                     p_->environment()->frame_allocator);
     783                 :                 if constexpr (checks_canceled)
     784                 :                 {
     785            5237 :                     if(result_)
     786               6 :                         return result_type(std::move(*result_));
     787                 :                 }
     788            5242 :                 return a_.await_resume();
     789                 :             }
     790                 : 
     791                 :             /** Start the wrapped awaitable with the gate as its
     792                 :                 continuation, or end the quitter on a pending stop.
     793                 : 
     794                 :                 If a stop is pending, the segment is abandoned for the
     795                 :                 stop and this coroutine stays suspended until it is
     796                 :                 destroyed. Otherwise this coroutine becomes the gate's
     797                 :                 target, and the wrapped awaitable's @ref ContextAwaitable
     798                 :                 overload is called with the gate's handle and the
     799                 :                 promise's environment. If that call completes without
     800                 :                 suspending (returning `false`, or the gate's own handle),
     801                 :                 this coroutine continues to @ref await_resume without
     802                 :                 passing through the gate, unless its result is a
     803                 :                 @ref results canceled by the stop (see
     804                 :                 @ref stops_on_sync_completion).
     805                 : 
     806                 :                 @param h The coroutine performing the `co_await`.
     807                 : 
     808                 :                 @return Where the workaround is off, the handle to resume:
     809                 :                 `h` to continue, a handle the wrapped awaitable returned,
     810                 :                 or `std::noop_coroutine()`. Where
     811                 :                 `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, `false`
     812                 :                 to continue and `true` to stay suspended; the stop check
     813                 :                 and the wrapped awaitable's `await_ready` both run here,
     814                 :                 through `detail::await_ready_then_suspend`.
     815                 :             */
     816                 :             template<class Promise>
     817           10076 :             auto await_suspend(
     818                 :                 std::coroutine_handle<Promise> h) noexcept(checks_noexcept)
     819                 :             {
     820                 :                 // After the wrapped awaitable's await_suspend, the gate
     821                 :                 // may already have run and destroyed this frame on
     822                 :                 // another thread: only locals are used from then on,
     823                 :                 // except after a synchronous completion, which leaves
     824                 :                 // the frame to us.
     825           10076 :                 auto* seg = p_->seg_;
     826           10076 :                 auto const* env = p_->environment();
     827                 : #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
     828                 :                 if(detail::quitter_stop_pending(env))
     829                 :                 {
     830                 :                     seg->stop();
     831                 :                     return true;
     832                 :                 }
     833                 :                 seg->target_ = h;
     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;
     843                 : #else
     844           10076 :                 if(stop_here_)
     845                 :                 {
     846               6 :                     seg->stop();
     847               6 :                     return std::coroutine_handle<>(std::noop_coroutine());
     848                 :                 }
     849           10070 :                 seg->target_ = h;
     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>)
     853                 :                 {
     854               5 :                     if(!a_.await_suspend(gate, env))
     855               4 :                         return continue_or_stop(h);
     856               1 :                     return std::coroutine_handle<>(std::noop_coroutine());
     857                 :                 }
     858                 :                 else if constexpr (std::is_void_v<R>)
     859                 :                 {
     860                 :                     a_.await_suspend(gate, env);
     861                 :                     return std::coroutine_handle<>(std::noop_coroutine());
     862                 :                 }
     863                 :                 else
     864                 :                 {
     865           10065 :                     std::coroutine_handle<> r = a_.await_suspend(gate, env);
     866           10065 :                     if(r == gate)
     867               5 :                         return continue_or_stop(h);
     868           10060 :                     return r;
     869                 :                 }
     870                 : #endif
     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<>
     883               9 :             continue_or_stop(std::coroutine_handle<> h)
     884                 :                 noexcept(checks_noexcept)
     885                 :             {
     886               9 :                 if(!stops_on_sync_completion())
     887               7 :                     return h;
     888               2 :                 p_->seg_->stop();
     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.
     913             108 :             bool await_ready() const noexcept
     914                 :             {
     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>
     928             108 :             auto await_suspend(std::coroutine_handle<Promise> h) noexcept
     929                 :             {
     930             108 :                 auto const* env = p_->environment();
     931             108 :                 if(detail::quitter_stop_pending(env))
     932                 :                 {
     933               1 :                     p_->seg_->stop();
     934               1 :                     return detail::symmetric_transfer(
     935               2 :                         std::noop_coroutine());
     936                 :                 }
     937             107 :                 return detail::symmetric_transfer(
     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                 :             */
     946               4 :             decltype(auto) await_resume()
     947                 :             {
     948               4 :                 set_current_frame_allocator(
     949               4 :                     p_->environment()->frame_allocator);
     950               4 :                 return q_.await_resume();
     951                 :             }
     952                 :         };
     953                 : 
     954                 :         /** Transform a nested awaitable before `co_await`.
     955                 : 
     956                 :             A quitter is wrapped in a @ref join_awaiter. Any other
     957                 :             @ref ContextAwaitable is wrapped in a @ref transform_awaiter.
     958                 :             A diagnostic is emitted for anything else.
     959                 : 
     960                 :             @param a The awaitable expression from `co_await a`.
     961                 : 
     962                 :             @return The awaiter wrapping `a`.
     963                 :         */
     964                 :         template<class Awaitable>
     965           10188 :         auto transform_awaitable(Awaitable&& a)
     966                 :         {
     967                 :             using A = std::decay_t<Awaitable>;
     968                 :             if constexpr (detail::is_quitter<A>::value)
     969                 :             {
     970             216 :                 return join_awaiter<A>{std::forward<Awaitable>(a), this};
     971                 :             }
     972                 :             else if constexpr (ContextAwaitable<A>)
     973                 :             {
     974                 :                 return transform_awaiter<Awaitable>{
     975           20147 :                     std::forward<Awaitable>(a), this};
     976                 :             }
     977                 :             else
     978                 :             {
     979                 :                 static_assert(sizeof(A) == 0,
     980                 :                     "requires ContextAwaitable");
     981                 :             }
     982           10169 :         }
     983                 :     };
     984                 : 
     985                 :     /** Handle to the owned coroutine frame.
     986                 : 
     987                 :         Null when the quitter is empty (for example after a move, after
     988                 :         @ref release, or once a stop has destroyed the frame). Prefer
     989                 :         @ref handle to read this; the member is public for use by the
     990                 :         coroutine machinery.
     991                 :     */
     992                 :     std::coroutine_handle<promise_type> h_;
     993                 : 
     994                 :     /** Destroy the quitter and its coroutine frame if owned.
     995                 : 
     996                 :         @par Preconditions
     997                 :         The frame is not lent: no parent is suspended awaiting it,
     998                 :         unless its chain is being unwound after an @ref abandon.
     999                 :         Destroying a lent frame otherwise calls `std::terminate`; end
    1000                 :         a running chain by requesting stop, or by destroying its
    1001                 :         execution context.
    1002                 :     */
    1003           30437 :     ~quitter()
    1004                 :     {
    1005           30437 :         destroy_frame();
    1006           30437 :     }
    1007                 : 
    1008                 :     /** Return false; quitters are never immediately ready.
    1009                 : 
    1010                 :         A quitter is lazy and has not started when it is awaited, so the
    1011                 :         awaiting coroutine always suspends.
    1012                 : 
    1013                 :         @return `false`.
    1014                 :     */
    1015           10051 :     bool await_ready() const noexcept
    1016                 :     {
    1017           10051 :         return false;
    1018                 :     }
    1019                 : 
    1020                 :     /** Return the result, rethrow the body's exception, or deliver the stopped value.
    1021                 : 
    1022                 :         @return The result value for non-void `T`, moved out of the
    1023                 :         quitter, or the stopped value if the quitter was stopped.
    1024                 : 
    1025                 :         @par Exception Safety
    1026                 :         If the body exited via an exception, that exception is rethrown.
    1027                 :         If the quitter was stopped and `T` is not `results<Ts...>` or
    1028                 :         `std::error_code`, a `std::system_error` whose `code()`
    1029                 :         compares equal to `cond::canceled` is thrown.
    1030                 :     */
    1031           10045 :     T await_resume()
    1032                 :     {
    1033           10045 :         lent_ = false;
    1034           10045 :         if(stopped_)
    1035            4805 :             return promise_type::stopped_result();
    1036            5240 :         auto& p = h_.promise();
    1037            5240 :         if(p.ep_)
    1038               1 :             std::rethrow_exception(p.ep_);
    1039                 :         if constexpr (! std::is_void_v<T>)
    1040            5238 :             return std::move(*p.result_);
    1041                 :     }
    1042                 : 
    1043                 :     /** Open a segment and start the quitter with the caller's context.
    1044                 : 
    1045                 :         If the stop token in `env` has already been triggered, the body
    1046                 :         is not started. `cont` is returned, so the caller resumes at once
    1047                 :         with the stopped value.
    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.
    1054                 : 
    1055                 :         @param cont The awaiting coroutine to resume when the quitter
    1056                 :         completes or is stopped.
    1057                 : 
    1058                 :         @param env The execution environment. It must outlive the
    1059                 :         quitter.
    1060                 : 
    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.
    1068                 :     */
    1069           10044 :     std::coroutine_handle<> await_suspend(
    1070                 :         std::coroutine_handle<> cont,
    1071                 :         await_context const* env)
    1072                 :     {
    1073           10044 :         if(env->stop_token.stop_requested())
    1074                 :         {
    1075               4 :             stopped_ = true;
    1076               4 :             return cont;
    1077                 :         }
    1078           10040 :         seg_ = detail::make_quitter_segment();
    1079           10040 :         auto& sp = seg_.h_.promise();
    1080           10040 :         sp.owner_.bind(seg_.h_, env->executor);
    1081           10040 :         sp.env_ = *env;
    1082           10040 :         sp.env_.owner = &sp.owner_;
    1083           10040 :         sp.frame_env_ = &sp.env_;
    1084           10040 :         sp.parent_.h = cont;
    1085           10040 :         sp.parent_.owner = env->owner;
    1086           10040 :         sp.opener_ = this;
    1087           10040 :         sp.active_ = true;
    1088           10040 :         auto& p = h_.promise();
    1089           10040 :         p.seg_ = &sp;
    1090           10040 :         p.top_ = true;
    1091           10040 :         p.set_environment(&sp.env_);
    1092           10040 :         p.set_continuation(cont);
    1093           10040 :         lent_ = true;
    1094           10040 :         return h_;
    1095                 :     }
    1096                 : 
    1097                 :     /** Return the coroutine handle.
    1098                 : 
    1099                 :         @note Do not call `destroy()` on the returned handle while the
    1100                 :         quitter is being awaited: the awaiting frame still holds it and
    1101                 :         destroys it again, which has undefined behavior. A holder that
    1102                 :         gives the coroutine up passes its continuation to @ref abandon
    1103                 :         instead. (Destroying or
    1104                 :         move-assigning over the quitter object itself while its frame is
    1105                 :         lent is a different operation, and calls `std::terminate`.)
    1106                 :         The quitter's lifetime is normally managed by `run_async`, `run`,
    1107                 :         or the awaiting parent.
    1108                 : 
    1109                 :         @note The operation, queue or primitive holding a suspended
    1110                 :         coroutine gives it up by passing the continuation it keeps to
    1111                 :         @ref abandon. `handle()` belongs to the owning quitter object.
    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                 : 
    1122                 :         @return The coroutine handle.
    1123                 :     */
    1124              65 :     std::coroutine_handle<promise_type> handle() const noexcept
    1125                 :     {
    1126              65 :         return h_;
    1127                 :     }
    1128                 : 
    1129                 :     /** Release ownership of the coroutine frame.
    1130                 : 
    1131                 :         @note The caller may call `destroy()` on the released handle
    1132                 :         only when the quitter has not started or has fully completed.
    1133                 :         Destroying the released handle of a quitter that is being
    1134                 :         awaited is not supported and has undefined behavior.
    1135                 : 
    1136                 :         @par Postconditions
    1137                 :         `handle()` returns a null handle. Callers needing the
    1138                 :         original handle must save it, via @ref handle, before
    1139                 :         calling this.
    1140                 :     */
    1141              50 :     void release() noexcept
    1142                 :     {
    1143              50 :         h_ = nullptr;
    1144              50 :     }
    1145                 : 
    1146                 :     /** Copy construction is disabled; a quitter uniquely owns its frame.
    1147                 : 
    1148                 :         @param other The quitter that would be copied.
    1149                 :     */
    1150                 :     quitter(quitter const& other) = delete;
    1151                 : 
    1152                 :     /** Copy assignment is disabled; a quitter uniquely owns its frame.
    1153                 : 
    1154                 :         @param other The quitter that would be assigned from.
    1155                 : 
    1156                 :         @return A reference to `*this`.
    1157                 :     */
    1158                 :     quitter& operator=(quitter const& other) = delete;
    1159                 : 
    1160                 :     /** Construct by moving, transferring ownership.
    1161                 : 
    1162                 :         @par Preconditions
    1163                 :         `other` is not being awaited.
    1164                 : 
    1165                 :         @par Postconditions
    1166                 :         `other` is empty and must not be awaited.
    1167                 : 
    1168                 :         @param other The quitter to move from.
    1169                 :     */
    1170           20233 :     quitter(quitter&& other) noexcept
    1171           20233 :         : h_(std::exchange(other.h_, nullptr))
    1172                 :     {
    1173           20233 :         release_top_ = other.release_top_;
    1174           20233 :     }
    1175                 : 
    1176                 :     /** Assign by moving, transferring ownership.
    1177                 : 
    1178                 :         If this quitter already owns a coroutine frame, that frame is
    1179                 :         destroyed first. Self-assignment is a no-op.
    1180                 : 
    1181                 :         @par Preconditions
    1182                 :         The frame being replaced is not lent: no parent is suspended
    1183                 :         awaiting it. Replacing a lent frame calls `std::terminate`.
    1184                 :         `other` is not being awaited.
    1185                 : 
    1186                 :         @par Postconditions
    1187                 :         `other` is empty and must not be awaited.
    1188                 : 
    1189                 :         @param other The quitter to move from.
    1190                 : 
    1191                 :         @return A reference to `*this`.
    1192                 :     */
    1193               1 :     quitter& operator=(quitter&& other) noexcept
    1194                 :     {
    1195               1 :         if(this != &other)
    1196                 :         {
    1197               1 :             destroy_frame();
    1198               1 :             h_ = std::exchange(other.h_, nullptr);
    1199               1 :             stopped_ = false;
    1200                 :         }
    1201               1 :         return *this;
    1202                 :     }
    1203                 : 
    1204                 : private:
    1205                 :     template<class>
    1206                 :     friend struct quitter;
    1207                 : 
    1208           10204 :     explicit quitter(std::coroutine_handle<promise_type> h)
    1209           10204 :         : h_(h)
    1210                 :     {
    1211           10204 :         release_top_ = &release_top;
    1212           10204 :     }
    1213                 : 
    1214                 :     static std::coroutine_handle<>
    1215            4804 :     release_top(detail::quitter_opener& o) noexcept
    1216                 :     {
    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`.
    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                 :     {
    1226             107 :         auto& p = h_.promise();
    1227             107 :         p.seg_ = seg;
    1228             107 :         p.set_environment(env);
    1229             107 :         p.set_continuation(parent);
    1230             107 :         lent_ = true;
    1231             107 :         return h_;
    1232                 :     }
    1233                 : 
    1234           30438 :     void destroy_frame() noexcept
    1235                 :     {
    1236           30438 :         if(h_)
    1237                 :         {
    1238            5350 :             if(lent_ && !h_.done())
    1239                 :             {
    1240                 :                 // Only the unwind of an abandoned chain destroys a frame
    1241                 :                 // that is still awaited.
    1242             103 :                 auto* owner = h_.promise().environment()->owner;
    1243             103 :                 if(!owner || !owner->abandoned())
    1244                 :                     std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
    1245                 :             }
    1246            5350 :             std::exchange(h_, nullptr).destroy();
    1247                 :         }
    1248           30438 :         if(seg_.h_)
    1249                 :         {
    1250           10039 :             seg_.h_.promise().active_ = false;
    1251           10039 :             std::exchange(seg_.h_, nullptr).destroy();
    1252                 :         }
    1253           30438 :         lent_ = false;
    1254           30438 :     }
    1255                 : };
    1256                 : 
    1257                 : } // namespace capy
    1258                 : } // namespace boost
    1259                 : 
    1260                 : #endif
        

Generated by: LCOV version 2.3