LCOV - code coverage report
Current view: top level - capy - quitter.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 125 125
Test Date: 2026-10-30 18:56:53 Functions: 91.7 % 168 154 14

           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/detail/stop_requested_exception.hpp>
      15                 : #include <boost/capy/cond.hpp>
      16                 : #include <boost/capy/results.hpp>
      17                 : #include <boost/capy/concept/executor.hpp>
      18                 : #include <boost/capy/concept/context_awaitable.hpp>
      19                 : #include <boost/capy/ex/context_awaitable_promise_base.hpp>
      20                 : #include <boost/capy/ex/await_context.hpp>
      21                 : #include <boost/capy/ex/frame_allocator.hpp>
      22                 : #include <boost/capy/detail/await_suspend_helper.hpp>
      23                 : 
      24                 : #include <exception>
      25                 : #include <optional>
      26                 : #include <tuple>
      27                 : #include <type_traits>
      28                 : #include <utility>
      29                 : 
      30                 : /* Stop-aware coroutine task.
      31                 : 
      32                 :    quitter<T> is identical to task<T> except that a stop request ends
      33                 :    the body without the body checking an error code.  The promise's
      34                 :    transform_awaiter checks the stop token before it touches an awaited
      35                 :    operation, and again when an operation that completed
      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                 : 
      46                 :    See doc/quitter.md for the full design rationale. */
      47                 : 
      48                 : namespace boost {
      49                 : namespace capy {
      50                 : 
      51                 : namespace detail {
      52                 : 
      53                 : // Reuse the same return-value storage as task<T>.
      54                 : // task_return_base is defined in task.hpp, but quitter needs its own
      55                 : // copy to avoid a header dependency on task.hpp.
      56                 : template<typename T>
      57                 : struct quitter_return_base
      58                 : {
      59                 :     std::optional<T> result_;
      60                 : 
      61 HIT          11 :     void return_value(T value)
      62                 :     {
      63              11 :         result_ = std::move(value);
      64              11 :     }
      65                 : 
      66               5 :     T&& result() noexcept
      67                 :     {
      68               5 :         return std::move(*result_);
      69                 :     }
      70                 : };
      71                 : 
      72                 : template<>
      73                 : struct quitter_return_base<void>
      74                 : {
      75               8 :     void return_void()
      76                 :     {
      77               8 :     }
      78                 : };
      79                 : 
      80                 : } // namespace detail
      81                 : 
      82                 : /** Defers a coroutine body until awaited, then unwinds it early on a stop request.
      83                 : 
      84                 :     When the stop token is triggered, the body is unwound at a
      85                 :     `co_await` instead of resuming, and RAII destructors run normally.
      86                 :     Await-effects below says at which one.  The parent observes a "stopped"
      87                 :     completion via @ref promise_type::stopped.
      88                 : 
      89                 :     Everything else — frame allocation, environment propagation,
      90                 :     symmetric transfer, move semantics — is identical to @ref task.
      91                 : 
      92                 :     @par Await-effects
      93                 : 
      94                 :     Let `q` be a `quitter<T>`. `co_await q` always suspends the awaiting
      95                 :     coroutine, then transfers control directly into the quitter's
      96                 :     coroutine body on the current thread; no executor operation is
      97                 :     posted. The quitter records the caller's environment (executor, stop
      98                 :     token, and frame allocator) by pointer rather than copying it. It
      99                 :     propagates that environment to every `co_await` inside the body.
     100                 : 
     101                 :     Unlike @ref task, the stop token is checked before the body's first
     102                 :     statement, before each awaited operation is started, and when an
     103                 :     operation that completed asynchronously resumes the body. If a stop
     104                 :     request is pending at those points, the body is not resumed. An
     105                 :     internal sentinel exception unwinds it instead, so RAII destructors
     106                 :     run, and the coroutine completes as stopped.
     107                 : 
     108                 :     An awaited operation that completes synchronously always has its
     109                 :     `await_resume` called, even if a stop request arrives at the same
     110                 :     time. Its `await_ready` or `await_suspend` may already have acquired
     111                 :     a resource, such as `async_mutex`'s lock, that only its
     112                 :     `await_resume` hands over. If that result is a @ref results holding
     113                 :     `cond::canceled` while a stop is pending, the operation saw the stop
     114                 :     first and acquired nothing, so the body is unwound there. Any other
     115                 :     result is 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
     117                 :     therefore hand the body one result it produced after seeing the
     118                 :     stop.
     119                 : 
     120                 :     The body runs until it returns, exits via an exception, or is unwound
     121                 :     by a stop request. Control then transfers directly back to the
     122                 :     awaiting coroutine, again without an executor operation.
     123                 : 
     124                 :     @par Await-returns
     125                 :     The value the body passed to `co_return`, moved out of the quitter,
     126                 :     or nothing when `T` is `void`.
     127                 : 
     128                 :     If the body exits via an unhandled exception, that exception is
     129                 :     rethrown instead.
     130                 : 
     131                 :     If the coroutine completed as stopped, the internal sentinel
     132                 :     exception is thrown instead of await-returning. Awaiting a stopped
     133                 :     `quitter` from another `quitter` therefore stops that one too. A
     134                 :     @ref task awaiting it sees the sentinel as an unhandled exception in
     135                 :     its own body. When a quitter is started by `run_async`, a stopped
     136                 :     completion reaches the error handler as the sentinel
     137                 :     `std::exception_ptr`, not the value handler.
     138                 : 
     139                 :     @par Await-postcondition
     140                 :     The quitter's coroutine has run to completion and is suspended at its
     141                 :     final suspend point; the body's RAII destructors have run. Exactly
     142                 :     one of the following holds: the body returned a value; the body
     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                 : 
     147                 :     @par Remarks
     148                 :     Supports _ContextAwaitable cancellation_.
     149                 : 
     150                 :     @tparam T The result type.  Use `quitter<>` for `quitter<void>`.
     151                 : 
     152                 :     @see @ref task, @ref ContextRunnable, @ref ContextAwaitable
     153                 : */
     154                 : template<typename T = void>
     155                 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
     156                 :     quitter
     157                 : {
     158                 :     /** Stores `quitter<T>`'s result and unwinds the body when the stop token fires.
     159                 : 
     160                 :         This is the promise object the compiler associates with a
     161                 :         `quitter<T>` coroutine. It satisfies the coroutine promise
     162                 :         requirements and participates in the I/O awaitable protocol via
     163                 :         @ref context_awaitable_promise_base. Unlike @ref task::promise_type,
     164                 :         its `transform_awaitable` checks the stop token before each
     165                 :         awaited result reaches the body. A pending stop request throws an
     166                 :         internal sentinel exception that unwinds to a "stopped"
     167                 :         completion. It is part of the coroutine machinery and is not
     168                 :         intended to be used directly by callers.
     169                 : 
     170                 :         Result storage and `return_value`/`return_void` are provided by
     171                 :         `detail::quitter_return_base<T>`.
     172                 : 
     173                 :         @see @ref context_awaitable_promise_base, @ref ContextRunnable
     174                 :     */
     175                 :     struct promise_type
     176                 :         : context_awaitable_promise_base<promise_type>
     177                 :         , detail::quitter_return_base<T>
     178                 :     {
     179                 :     private:
     180                 :         friend quitter;
     181                 : 
     182                 :         enum class completion { running, value, exception, stopped };
     183                 : 
     184                 :         union { std::exception_ptr ep_; };
     185                 :         completion state_;
     186                 : 
     187                 :     public:
     188                 :         /// Construct the promise in the running state.
     189              45 :         promise_type() noexcept
     190              45 :             : state_(completion::running)
     191                 :         {
     192              45 :         }
     193                 : 
     194                 :         /// Destroy the promise, releasing any stored exception.
     195              45 :         ~promise_type()
     196                 :         {
     197              45 :             if(state_ == completion::exception ||
     198              41 :                state_ == completion::stopped)
     199              23 :                 ep_.~exception_ptr();
     200              45 :         }
     201                 : 
     202                 :         /** Return a non-null exception_ptr when the coroutine threw
     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                 : 
     210                 :             @return The stored exception if the coroutine exited via an
     211                 :             exception or was stopped, otherwise a null
     212                 :             `std::exception_ptr`.
     213                 :         */
     214              38 :         std::exception_ptr exception() const noexcept
     215                 :         {
     216              38 :             if(state_ == completion::exception ||
     217              32 :                state_ == completion::stopped)
     218              26 :                 return ep_;
     219              12 :             return {};
     220                 :         }
     221                 : 
     222                 :         /** True when the coroutine was stopped via the stop token.
     223                 : 
     224                 :             @return `true` if the body was unwound by a stop request;
     225                 :             `false` if it returned a value or exited via any other
     226                 :             exception.
     227                 :         */
     228              12 :         bool stopped() const noexcept
     229                 :         {
     230              12 :             return state_ == completion::stopped;
     231                 :         }
     232                 : 
     233                 :         /** Return the owning `quitter` for this coroutine.
     234                 : 
     235                 :             Called by the compiler to produce the object returned to the
     236                 :             caller when the coroutine is created.
     237                 : 
     238                 :             @return A `quitter` owning the coroutine frame.
     239                 :         */
     240              45 :         quitter get_return_object()
     241                 :         {
     242                 :             return quitter{
     243              45 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
     244                 :         }
     245                 : 
     246                 :         /** Return the initial-suspend awaiter.
     247                 : 
     248                 :             The coroutine always suspends at the initial suspend point,
     249                 :             so the body does not start until the quitter is awaited. When
     250                 :             the body is resumed, the awaiter restores the thread-local
     251                 :             frame allocator. It then throws the internal sentinel
     252                 :             exception if stop is already requested, so the body never
     253                 :             runs and the coroutine completes as stopped.
     254                 : 
     255                 :             @return An awaiter that suspends unconditionally.
     256                 :         */
     257              45 :         auto initial_suspend() noexcept
     258                 :         {
     259                 :             struct awaiter
     260                 :             {
     261                 :                 promise_type* p_;
     262                 : 
     263              45 :                 bool await_ready() const noexcept
     264                 :                 {
     265              45 :                     return false;
     266                 :                 }
     267                 : 
     268              45 :                 void await_suspend(std::coroutine_handle<>) const noexcept
     269                 :                 {
     270              45 :                 }
     271                 : 
     272                 :                 // Potentially-throwing: checks the stop token before
     273                 :                 // the coroutine body executes its first statement.
     274              43 :                 void await_resume() const
     275                 :                 {
     276              43 :                     set_current_frame_allocator(
     277              43 :                         p_->environment()->frame_allocator);
     278              43 :                     if(p_->environment()->stop_token.stop_requested())
     279               2 :                         throw detail::stop_requested_exception{};
     280              41 :                 }
     281                 :             };
     282              45 :             return awaiter{this};
     283                 :         }
     284                 : 
     285                 :         /** Return the final-suspend awaiter.
     286                 : 
     287                 :             The coroutine always suspends at the final suspend point. The
     288                 :             awaiter's `await_suspend` performs symmetric transfer to the
     289                 :             stored continuation, resuming the awaiting coroutine.
     290                 : 
     291                 :             @return An awaiter that suspends and transfers to the
     292                 :             continuation.
     293                 :         */
     294              42 :         auto final_suspend() noexcept
     295                 :         {
     296                 :             struct awaiter
     297                 :             {
     298                 :                 promise_type* p_;
     299                 : 
     300              42 :                 bool await_ready() const noexcept
     301                 :                 {
     302              42 :                     return false;
     303                 :                 }
     304                 : 
     305              42 :                 std::coroutine_handle<> await_suspend(
     306                 :                     std::coroutine_handle<>) const noexcept
     307                 :                 {
     308              42 :                     return p_->continuation();
     309                 :                 }
     310                 : 
     311                 :                 void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
     312                 :             };
     313              42 :             return awaiter{this};
     314                 :         }
     315                 : 
     316                 :         /** Capture the in-flight exception from the coroutine body.
     317                 : 
     318                 :             Called by the compiler when the coroutine body exits via an
     319                 :             unhandled exception. The internal stop sentinel is recorded as
     320                 :             a stopped completion; any other exception is recorded as an
     321                 :             exception completion. The stored exception is surfaced (or
     322                 :             routed to the error handler) when the quitter is awaited or run.
     323                 :         */
     324              23 :         void unhandled_exception()
     325                 :         {
     326                 :             try
     327                 :             {
     328              23 :                 throw;
     329                 :             }
     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.
     335              19 :                 new (&ep_) std::exception_ptr(
     336                 :                     std::current_exception());
     337              19 :                 state_ = completion::stopped;
     338                 :             }
     339               4 :             catch(...)
     340                 :             {
     341               4 :                 new (&ep_) std::exception_ptr(
     342                 :                     std::current_exception());
     343               4 :                 state_ = completion::exception;
     344                 :             }
     345              23 :         }
     346                 : 
     347                 :         //------------------------------------------------------
     348                 :         // transform_awaitable — the key difference from task<T>
     349                 :         //------------------------------------------------------
     350                 : 
     351                 :         /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable.
     352                 : 
     353                 :             Forwards the environment to the inner awaitable's
     354                 :             environment-taking `await_suspend` and restores the
     355                 :             thread-local frame allocator before the body resumes. Unlike
     356                 :             `task`'s, it also checks the stop token. A stop pending before
     357                 :             the wrapped awaitable is touched, or on resumption after an
     358                 :             asynchronous completion, throws the internal sentinel. The
     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                 : 
     363                 :             @tparam Awaitable The awaitable being transformed.
     364                 :         */
     365                 :         template<class Awaitable>
     366                 :         struct transform_awaiter
     367                 :         {
     368                 :             /// The wrapped awaitable, decayed and stored by value.
     369                 :             std::decay_t<Awaitable> a_;
     370                 : 
     371                 :             /// The promise of the coroutine performing the `co_await`.
     372                 :             promise_type* p_;
     373                 : 
     374                 :             /// True when a stop was pending at the `co_await`, so the
     375                 :             /// wrapped awaitable was never touched.
     376                 :             bool skipped_ = false;
     377                 : 
     378                 :             /// True when the wrapped awaitable completed synchronously, so
     379                 :             /// its `await_resume` must run to hand over what it acquired.
     380                 :             bool completed_ = false;
     381                 : 
     382                 :             /** Report whether the `co_await` can continue without
     383                 :                 suspending.
     384                 : 
     385                 :                 The stop token is checked first. If a stop is pending, the
     386                 :                 wrapped awaitable is never touched: this returns `true`, and
     387                 :                 @ref await_resume throws. Otherwise the wrapped awaitable's
     388                 :                 own `await_ready` decides. A `true` result there means it
     389                 :                 completed synchronously, so @ref await_resume calls its
     390                 :                 `await_resume` even if a stop arrives afterwards. That hands
     391                 :                 over anything it acquired, such as `async_mutex`'s lock.
     392                 : 
     393                 :                 Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
     394                 :                 this always returns `false`, and @ref await_suspend does both
     395                 :                 steps instead. See `detail::await_ready_then_suspend`.
     396                 : 
     397                 :                 @return `true` if no suspension is needed. Always `false`
     398                 :                 where the workaround is on.
     399                 :             */
     400              32 :             bool await_ready() noexcept
     401                 :             {
     402                 : #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
     403                 :                 return false;
     404                 : #else
     405              32 :                 if(p_->environment()->stop_token.stop_requested())
     406                 :                 {
     407               1 :                     skipped_ = true;
     408               1 :                     return true;
     409                 :                 }
     410              31 :                 completed_ = a_.await_ready();
     411              31 :                 return completed_;
     412                 : #endif
     413                 :             }
     414                 : 
     415                 :             /** Restore the frame allocator, check for stop, then resume the
     416                 :                 wrapped awaitable.
     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                 : 
     433                 :                 After an asynchronous completion with a stop pending, the
     434                 :                 operation's result is dropped. Releasing anything it held is
     435                 :                 then the wrapped awaiter's destructor's job: `async_mutex`
     436                 :                 releases a lock it granted, and `async_waker` drops the
     437                 :                 wakeup. The body is unwinding, so nothing observes the loss.
     438                 : 
     439                 :                 @return The wrapped awaitable's await-result, forwarded
     440                 :                 unchanged, when no sentinel is thrown.
     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                 :             */
     448              31 :             decltype(auto) await_resume()
     449                 :             {
     450              31 :                 set_current_frame_allocator(
     451              31 :                     p_->environment()->frame_allocator);
     452              61 :                 if(skipped_ ||
     453              52 :                     (!completed_ &&
     454              22 :                      p_->environment()->stop_token.stop_requested()))
     455              17 :                     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.
     461               5 :                     R r = a_.await_resume();
     462               7 :                     if(std::get<0>(r) == cond::canceled &&
     463               2 :                         p_->environment()->stop_token.stop_requested())
     464               1 :                         throw detail::stop_requested_exception{};
     465               8 :                     return r;
     466               3 :                 }
     467                 :                 else
     468                 :                 {
     469               9 :                     return a_.await_resume();
     470                 :                 }
     471                 :             }
     472                 : 
     473                 :             /** Suspend by calling the wrapped awaitable with the
     474                 :                 environment.
     475                 : 
     476                 :                 This is the plain `await_suspend` the compiler calls for the
     477                 :                 nested `co_await`. It forwards to the wrapped awaitable's
     478                 :                 @ref ContextAwaitable overload, supplying the promise's stored
     479                 :                 environment as the second argument. It keeps the wrapped
     480                 :                 awaitable's suspension decision, whatever form it takes.
     481                 :                 When that decision is to continue without suspending, the
     482                 :                 wrapped awaitable completed synchronously, and
     483                 :                 @ref await_resume will call its `await_resume`. Returning
     484                 :                 `h` itself counts as continuing without suspending.
     485                 : 
     486                 :                 @param h The coroutine performing the `co_await`.
     487                 : 
     488                 :                 @return Whatever the wrapped awaitable's `await_suspend`
     489                 :                 returns. When that is a `std::coroutine_handle<>`, the
     490                 :                 handle is routed through `detail::symmetric_transfer`.
     491                 :                 Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
     492                 :                 this function instead returns `bool`. It first checks the
     493                 :                 stop token, as @ref await_ready does where the workaround is
     494                 :                 off, and returns `false` without touching the wrapped
     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                 :             */
     500                 :             template<class Promise>
     501              26 :             auto await_suspend(
     502                 :                 std::coroutine_handle<Promise> h) noexcept
     503                 :             {
     504                 :                 // completed_ is written only where nothing else can have
     505                 :                 // resumed this frame: a frame that really suspended may
     506                 :                 // already be running, or gone, on another thread.
     507                 : #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
     508                 :                 if(p_->environment()->stop_token.stop_requested())
     509                 :                 {
     510                 :                     skipped_ = true;
     511                 :                     return false;
     512                 :                 }
     513                 :                 return detail::await_ready_then_suspend(
     514                 :                     a_, h, p_->environment(), &completed_);
     515                 : #else
     516                 :                 using R = decltype(
     517                 :                     a_.await_suspend(h, p_->environment()));
     518                 :                 if constexpr (std::is_same_v<
     519                 :                     R, std::coroutine_handle<>>)
     520                 :                 {
     521                 :                     std::coroutine_handle<> next =
     522              23 :                         a_.await_suspend(h, p_->environment());
     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
     534              23 :                     if(next == h)
     535               1 :                         completed_ = true;
     536                 : #if defined(__GNUC__) && !defined(__clang__)
     537                 : #pragma GCC diagnostic pop
     538                 : #endif
     539              23 :                     return detail::symmetric_transfer(next);
     540                 :                 }
     541                 :                 else if constexpr (std::is_same_v<R, bool>)
     542                 :                 {
     543               3 :                     if(!a_.await_suspend(h, p_->environment()))
     544                 :                     {
     545               2 :                         completed_ = true;
     546               2 :                         return false;
     547                 :                     }
     548               1 :                     return true;
     549                 :                 }
     550                 :                 else
     551                 :                 {
     552                 :                     return a_.await_suspend(
     553                 :                         h, p_->environment());
     554                 :                 }
     555                 : #endif
     556                 :             }
     557                 :         };
     558                 : 
     559                 :         /** Transform a nested awaitable before `co_await`.
     560                 : 
     561                 :             Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the
     562                 :             coroutine's environment is propagated into it and the stop
     563                 :             token is checked on resumption. A diagnostic is emitted if the
     564                 :             awaitable does not satisfy @ref ContextAwaitable.
     565                 : 
     566                 :             @param a The awaitable expression from `co_await a`.
     567                 : 
     568                 :             @return A @ref transform_awaiter wrapping `a`.
     569                 :         */
     570                 :         template<class Awaitable>
     571              32 :         auto transform_awaitable(Awaitable&& a)
     572                 :         {
     573                 :             using A = std::decay_t<Awaitable>;
     574                 :             if constexpr (ContextAwaitable<A>)
     575                 :             {
     576                 :                 return transform_awaiter<Awaitable>{
     577              53 :                     std::forward<Awaitable>(a), this};
     578                 :             }
     579                 :             else
     580                 :             {
     581                 :                 static_assert(sizeof(A) == 0,
     582                 :                     "requires ContextAwaitable");
     583                 :             }
     584              21 :         }
     585                 :     };
     586                 : 
     587                 :     /** Handle to the owned coroutine frame.
     588                 : 
     589                 :         Null when the quitter is empty (for example after a move or after
     590                 :         @ref release). Prefer @ref handle to read this; the member is
     591                 :         public for use by the coroutine machinery.
     592                 :     */
     593                 :     std::coroutine_handle<promise_type> h_;
     594                 : 
     595                 :     /** Destroy the quitter and its coroutine frame if owned.
     596                 : 
     597                 :         @par Preconditions
     598                 :         The frame is not lent: no parent is suspended awaiting it,
     599                 :         unless its chain is being unwound after an @ref abandon.
     600                 :         Destroying a lent frame otherwise calls `std::terminate`; end
     601                 :         a running chain by requesting stop, or by destroying its
     602                 :         execution context.
     603                 :     */
     604              83 :     ~quitter()
     605                 :     {
     606              83 :         destroy_frame();
     607              83 :     }
     608                 : 
     609                 :     /** Return false; quitters are never immediately ready.
     610                 : 
     611                 :         A quitter is lazy and has not started when it is awaited, so the
     612                 :         awaiting coroutine always suspends.
     613                 : 
     614                 :         @return `false`.
     615                 :     */
     616              16 :     bool await_ready() const noexcept
     617                 :     {
     618              16 :         return false;
     619                 :     }
     620                 : 
     621                 :     /** Return the result, rethrow exception, or propagate stop.
     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                 : 
     627                 :         @return The result value for non-void `T`, moved out of the
     628                 :         quitter; otherwise `void`.
     629                 : 
     630                 :         @par Exception Safety
     631                 :         If the coroutine was stopped, the library's internal stop sentinel
     632                 :         is thrown. If the body exited via any other exception, that
     633                 :         exception is rethrown.
     634                 :     */
     635              12 :     auto await_resume()
     636                 :     {
     637              12 :         lent_ = false;
     638              12 :         if(h_.promise().stopped())
     639               6 :             throw detail::stop_requested_exception{};
     640               6 :         if(h_.promise().state_ == promise_type::completion::exception)
     641               1 :             std::rethrow_exception(h_.promise().ep_);
     642                 :         if constexpr (! std::is_void_v<T>)
     643               4 :             return std::move(*h_.promise().result_);
     644                 :         else
     645               1 :             return;
     646                 :     }
     647                 : 
     648                 :     /** Start execution with the caller's context.
     649                 : 
     650                 :         Stores `cont` as the continuation to resume on completion.
     651                 :         Stores `env` as the execution environment propagated to nested
     652                 :         `co_await` expressions. Then transfers control into the quitter's
     653                 :         coroutine body via the returned handle.
     654                 : 
     655                 :         @param cont The awaiting coroutine to resume when the quitter
     656                 :         completes.
     657                 : 
     658                 :         @param env The execution environment (executor, stop token, and
     659                 :         frame allocator). It must outlive the quitter.
     660                 : 
     661                 :         @return The quitter's coroutine handle, for symmetric transfer.
     662                 :     */
     663              16 :     std::coroutine_handle<> await_suspend(
     664                 :         std::coroutine_handle<> cont,
     665                 :         await_context const* env)
     666                 :     {
     667              16 :         h_.promise().set_continuation(cont);
     668              16 :         h_.promise().set_environment(env);
     669              16 :         lent_ = true;
     670              16 :         return h_;
     671                 :     }
     672                 : 
     673                 :     /** Return the coroutine handle.
     674                 : 
     675                 :         @note Do not call `destroy()` on the returned handle while the
     676                 :         quitter is being awaited: the awaiting frame still holds it and
     677                 :         destroys it again, which has undefined behavior. A holder that
     678                 :         gives the coroutine up passes its continuation to @ref abandon
     679                 :         instead. (Destroying or
     680                 :         move-assigning over the quitter object itself while its frame is
     681                 :         lent is a different operation, and calls `std::terminate`.)
     682                 :         The quitter's lifetime is normally managed by `run_async`, `run`,
     683                 :         or the awaiting parent. For cooperative cancellation, use
     684                 :         `std::stop_token`.
     685                 : 
     686                 :         @note The operation, queue or primitive holding a suspended
     687                 :         coroutine gives it up by passing the continuation it keeps to
     688                 :         @ref abandon. `handle()` belongs to the owning quitter object.
     689                 : 
     690                 :         @return The coroutine handle.
     691                 :     */
     692              29 :     std::coroutine_handle<promise_type> handle() const noexcept
     693                 :     {
     694              29 :         return h_;
     695                 :     }
     696                 : 
     697                 :     /** Release ownership of the coroutine frame.
     698                 : 
     699                 :         @note The caller may call `destroy()` on the released handle
     700                 :         only when the quitter has not started or has fully completed.
     701                 :         Destroying the released handle of a quitter that is being
     702                 :         awaited is not supported and has undefined behavior.
     703                 : 
     704                 :         @par Postconditions
     705                 :         `handle()` returns a null handle. Callers needing the
     706                 :         original handle must save it, via @ref handle, before
     707                 :         calling this.
     708                 :     */
     709              27 :     void release() noexcept
     710                 :     {
     711              27 :         h_ = nullptr;
     712              27 :     }
     713                 : 
     714                 :     /** Copy construction is disabled; a quitter uniquely owns its frame.
     715                 : 
     716                 :         @param other The quitter that would be copied.
     717                 :     */
     718                 :     quitter(quitter const& other) = delete;
     719                 : 
     720                 :     /** Copy assignment is disabled; a quitter uniquely owns its frame.
     721                 : 
     722                 :         @param other The quitter that would be assigned from.
     723                 : 
     724                 :         @return A reference to `*this`.
     725                 :     */
     726                 :     quitter& operator=(quitter const& other) = delete;
     727                 : 
     728                 :     /** Construct by moving, transferring ownership.
     729                 : 
     730                 :         @par Postconditions
     731                 :         `other` is empty and must not be awaited.
     732                 : 
     733                 :         @param other The quitter to move from.
     734                 :     */
     735              38 :     quitter(quitter&& other) noexcept
     736              38 :         : h_(std::exchange(other.h_, nullptr))
     737                 :     {
     738              38 :     }
     739                 : 
     740                 :     /** Assign by moving, transferring ownership.
     741                 : 
     742                 :         If this quitter already owns a coroutine frame, that frame is
     743                 :         destroyed first. Self-assignment is a no-op.
     744                 : 
     745                 :         @par Preconditions
     746                 :         The frame being replaced is not lent: no parent is suspended
     747                 :         awaiting it. Replacing a lent frame calls `std::terminate`.
     748                 : 
     749                 :         @par Postconditions
     750                 :         `other` is empty and must not be awaited.
     751                 : 
     752                 :         @param other The quitter to move from.
     753                 : 
     754                 :         @return A reference to `*this`.
     755                 :     */
     756               1 :     quitter& operator=(quitter&& other) noexcept
     757                 :     {
     758               1 :         if(this != &other)
     759                 :         {
     760               1 :             destroy_frame();
     761               1 :             h_ = std::exchange(other.h_, nullptr);
     762                 :         }
     763               1 :         return *this;
     764                 :     }
     765                 : 
     766                 : private:
     767                 :     // Set while a parent is suspended awaiting this quitter.
     768                 :     bool lent_ = false;
     769                 : 
     770              45 :     explicit quitter(std::coroutine_handle<promise_type> h)
     771              45 :         : h_(h)
     772                 :     {
     773              45 :     }
     774                 : 
     775              84 :     void destroy_frame() noexcept
     776                 :     {
     777              84 :         if(!h_)
     778              66 :             return;
     779              18 :         if(lent_ && !h_.done())
     780                 :         {
     781                 :             // Only the unwind of an abandoned chain destroys a frame
     782                 :             // that is still awaited.
     783               1 :             auto* owner = h_.promise().environment()->owner;
     784               1 :             if(!owner || !owner->abandoned())
     785                 :                 std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
     786                 :         }
     787              18 :         std::exchange(h_, nullptr).destroy();
     788              18 :         lent_ = false;
     789                 :     }
     790                 : };
     791                 : 
     792                 : } // namespace capy
     793                 : } // namespace boost
     794                 : 
     795                 : #endif
        

Generated by: LCOV version 2.3