LCOV - code coverage report
Current view: top level - capy - quitter.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 109 109
Test Date: 2026-10-23 18:56:46 Functions: 95.1 % 144 137 7

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

Generated by: LCOV version 2.3