LCOV - code coverage report
Current view: top level - capy - quitter.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 93.8 % 97 91 6
Test Date: 2026-03-21 03:20:11 Functions: 92.7 % 123 114 9

           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/io_awaitable.hpp>
      17                 : #include <boost/capy/ex/io_awaitable_promise_base.hpp>
      18                 : #include <boost/capy/ex/io_env.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           9 :     void return_value(T value)
      51                 :     {
      52               9 :         result_ = std::move(value);
      53               9 :     }
      54                 : 
      55               3 :     T&& result() noexcept
      56                 :     {
      57               3 :         return std::move(*result_);
      58                 :     }
      59                 : };
      60                 : 
      61                 : template<>
      62                 : struct quitter_return_base<void>
      63                 : {
      64               1 :     void return_void()
      65                 :     {
      66               1 :     }
      67                 : };
      68                 : 
      69                 : } // namespace detail
      70                 : 
      71                 : /** Stop-aware lazy coroutine task satisfying @ref IoRunnable.
      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                 :     @tparam T The result type.  Use `quitter<>` for `quitter<void>`.
      82                 : 
      83                 :     @see task, IoRunnable, IoAwaitable
      84                 : */
      85                 : template<typename T = void>
      86                 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
      87                 :     quitter
      88                 : {
      89                 :     struct promise_type
      90                 :         : io_awaitable_promise_base<promise_type>
      91                 :         , detail::quitter_return_base<T>
      92                 :     {
      93                 :     private:
      94                 :         friend quitter;
      95                 : 
      96                 :         enum class completion { running, value, exception, stopped };
      97                 : 
      98                 :         union { std::exception_ptr ep_; };
      99                 :         completion state_;
     100                 : 
     101                 :     public:
     102              28 :         promise_type() noexcept
     103              28 :             : state_(completion::running)
     104                 :         {
     105              28 :         }
     106                 : 
     107              28 :         ~promise_type()
     108                 :         {
     109              28 :             if(state_ == completion::exception ||
     110              26 :                state_ == completion::stopped)
     111              18 :                 ep_.~exception_ptr();
     112              28 :         }
     113                 : 
     114                 :         /// Return a non-null exception_ptr when the coroutine threw
     115                 :         /// or was stopped.  Stopped quitters report the sentinel
     116                 :         /// stop_requested_exception so that run_async routes to
     117                 :         /// the error handler instead of accessing a non-existent
     118                 :         /// result.
     119              22 :         std::exception_ptr exception() const noexcept
     120                 :         {
     121              22 :             if(state_ == completion::exception ||
     122              18 :                state_ == completion::stopped)
     123              18 :                 return ep_;
     124               4 :             return {};
     125                 :         }
     126                 : 
     127                 :         /// True when the coroutine was stopped via the stop token.
     128              10 :         bool stopped() const noexcept
     129                 :         {
     130              10 :             return state_ == completion::stopped;
     131                 :         }
     132                 : 
     133              28 :         quitter get_return_object()
     134                 :         {
     135                 :             return quitter{
     136              28 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
     137                 :         }
     138                 : 
     139              28 :         auto initial_suspend() noexcept
     140                 :         {
     141                 :             struct awaiter
     142                 :             {
     143                 :                 promise_type* p_;
     144                 : 
     145              28 :                 bool await_ready() const noexcept
     146                 :                 {
     147              28 :                     return false;
     148                 :                 }
     149                 : 
     150              28 :                 void await_suspend(std::coroutine_handle<>) const noexcept
     151                 :                 {
     152              28 :                 }
     153                 : 
     154                 :                 // Potentially-throwing: checks the stop token before
     155                 :                 // the coroutine body executes its first statement.
     156              28 :                 void await_resume() const
     157                 :                 {
     158              28 :                     set_current_frame_allocator(
     159              28 :                         p_->environment()->frame_allocator);
     160              28 :                     if(p_->environment()->stop_token.stop_requested())
     161               3 :                         throw detail::stop_requested_exception{};
     162              25 :                 }
     163                 :             };
     164              28 :             return awaiter{this};
     165                 :         }
     166                 : 
     167              28 :         auto final_suspend() noexcept
     168                 :         {
     169                 :             struct awaiter
     170                 :             {
     171                 :                 promise_type* p_;
     172                 : 
     173              28 :                 bool await_ready() const noexcept
     174                 :                 {
     175              28 :                     return false;
     176                 :                 }
     177                 : 
     178              28 :                 std::coroutine_handle<> await_suspend(
     179                 :                     std::coroutine_handle<>) const noexcept
     180                 :                 {
     181              28 :                     return p_->continuation();
     182                 :                 }
     183                 : 
     184 MIS           0 :                 void await_resume() const noexcept
     185                 :                 {
     186               0 :                 }
     187                 :             };
     188 HIT          28 :             return awaiter{this};
     189                 :         }
     190                 : 
     191              18 :         void unhandled_exception()
     192                 :         {
     193                 :             try
     194                 :             {
     195              18 :                 throw;
     196                 :             }
     197              18 :             catch(detail::stop_requested_exception const&)
     198                 :             {
     199                 :                 // Store the exception_ptr so that run_async's
     200                 :                 // invoke_impl routes to the error handler
     201                 :                 // instead of accessing a non-existent result.
     202              16 :                 new (&ep_) std::exception_ptr(
     203                 :                     std::current_exception());
     204              16 :                 state_ = completion::stopped;
     205                 :             }
     206               2 :             catch(...)
     207                 :             {
     208               2 :                 new (&ep_) std::exception_ptr(
     209                 :                     std::current_exception());
     210               2 :                 state_ = completion::exception;
     211                 :             }
     212              18 :         }
     213                 : 
     214                 :         //------------------------------------------------------
     215                 :         // transform_awaitable — the key difference from task<T>
     216                 :         //------------------------------------------------------
     217                 : 
     218                 :         template<class Awaitable>
     219                 :         struct transform_awaiter
     220                 :         {
     221                 :             std::decay_t<Awaitable> a_;
     222                 :             promise_type* p_;
     223                 : 
     224              18 :             bool await_ready() noexcept
     225                 :             {
     226              18 :                 return a_.await_ready();
     227                 :             }
     228                 : 
     229                 :             // Check the stop token BEFORE the coroutine body
     230                 :             // sees the result of the I/O operation.
     231              18 :             decltype(auto) await_resume()
     232                 :             {
     233              18 :                 set_current_frame_allocator(
     234              18 :                     p_->environment()->frame_allocator);
     235              18 :                 if(p_->environment()->stop_token.stop_requested())
     236              13 :                     throw detail::stop_requested_exception{};
     237               5 :                 return a_.await_resume();
     238                 :             }
     239                 : 
     240                 :             template<class Promise>
     241              16 :             auto await_suspend(
     242                 :                 std::coroutine_handle<Promise> h) noexcept
     243                 :             {
     244                 :                 using R = decltype(
     245                 :                     a_.await_suspend(h, p_->environment()));
     246                 :                 if constexpr (std::is_same_v<
     247                 :                     R, std::coroutine_handle<>>)
     248              16 :                     return detail::symmetric_transfer(
     249              32 :                         a_.await_suspend(h, p_->environment()));
     250                 :                 else
     251 MIS           0 :                     return a_.await_suspend(
     252               0 :                         h, p_->environment());
     253                 :             }
     254                 :         };
     255                 : 
     256                 :         template<class Awaitable>
     257 HIT          18 :         auto transform_awaitable(Awaitable&& a)
     258                 :         {
     259                 :             using A = std::decay_t<Awaitable>;
     260                 :             if constexpr (IoAwaitable<A>)
     261                 :             {
     262                 :                 return transform_awaiter<Awaitable>{
     263              33 :                     std::forward<Awaitable>(a), this};
     264                 :             }
     265                 :             else
     266                 :             {
     267                 :                 static_assert(sizeof(A) == 0,
     268                 :                     "requires IoAwaitable");
     269                 :             }
     270              15 :         }
     271                 :     };
     272                 : 
     273                 :     std::coroutine_handle<promise_type> h_;
     274                 : 
     275                 :     /// Destroy the quitter and its coroutine frame if owned.
     276              72 :     ~quitter()
     277                 :     {
     278              72 :         if(h_)
     279              13 :             h_.destroy();
     280              72 :     }
     281                 : 
     282                 :     /// Return false; quitters are never immediately ready.
     283              13 :     bool await_ready() const noexcept
     284                 :     {
     285              13 :         return false;
     286                 :     }
     287                 : 
     288                 :     /** Return the result, rethrow exception, or propagate stop.
     289                 : 
     290                 :         When stopped, throws stop_requested_exception so that a
     291                 :         parent quitter also stops.  A parent task<T> will see this
     292                 :         as an unhandled exception — by design.
     293                 :     */
     294              10 :     auto await_resume()
     295                 :     {
     296              10 :         if(h_.promise().stopped())
     297               6 :             throw detail::stop_requested_exception{};
     298               4 :         if(h_.promise().state_ == promise_type::completion::exception)
     299 MIS           0 :             std::rethrow_exception(h_.promise().ep_);
     300                 :         if constexpr (! std::is_void_v<T>)
     301 HIT           4 :             return std::move(*h_.promise().result_);
     302                 :         else
     303 MIS           0 :             return;
     304                 :     }
     305                 : 
     306                 :     /// Start execution with the caller's context.
     307 HIT          13 :     std::coroutine_handle<> await_suspend(
     308                 :         std::coroutine_handle<> cont,
     309                 :         io_env const* env)
     310                 :     {
     311              13 :         h_.promise().set_continuation(cont);
     312              13 :         h_.promise().set_environment(env);
     313              13 :         return h_;
     314                 :     }
     315                 : 
     316                 :     /// Return the coroutine handle.
     317              17 :     std::coroutine_handle<promise_type> handle() const noexcept
     318                 :     {
     319              17 :         return h_;
     320                 :     }
     321                 : 
     322                 :     /// Release ownership of the coroutine frame.
     323              15 :     void release() noexcept
     324                 :     {
     325              15 :         h_ = nullptr;
     326              15 :     }
     327                 : 
     328                 :     quitter(quitter const&) = delete;
     329                 :     quitter& operator=(quitter const&) = delete;
     330                 : 
     331                 :     /// Construct by moving, transferring ownership.
     332              44 :     quitter(quitter&& other) noexcept
     333              44 :         : h_(std::exchange(other.h_, nullptr))
     334                 :     {
     335              44 :     }
     336                 : 
     337                 :     /// Assign by moving, transferring ownership.
     338                 :     quitter& operator=(quitter&& other) noexcept
     339                 :     {
     340                 :         if(this != &other)
     341                 :         {
     342                 :             if(h_)
     343                 :                 h_.destroy();
     344                 :             h_ = std::exchange(other.h_, nullptr);
     345                 :         }
     346                 :         return *this;
     347                 :     }
     348                 : 
     349                 : private:
     350              28 :     explicit quitter(std::coroutine_handle<promise_type> h)
     351              28 :         : h_(h)
     352                 :     {
     353              28 :     }
     354                 : };
     355                 : 
     356                 : } // namespace capy
     357                 : } // namespace boost
     358                 : 
     359                 : #endif
        

Generated by: LCOV version 2.3