LCOV - code coverage report
Current view: top level - capy - task.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 76 76
Test Date: 2026-06-24 18:54:23 Functions: 92.8 % 1208 1121 87

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       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_TASK_HPP
      11                 : #define BOOST_CAPY_TASK_HPP
      12                 : 
      13                 : #include <boost/capy/detail/config.hpp>
      14                 : #include <boost/capy/concept/executor.hpp>
      15                 : #include <boost/capy/concept/io_awaitable.hpp>
      16                 : #include <boost/capy/ex/io_awaitable_promise_base.hpp>
      17                 : #include <boost/capy/ex/io_env.hpp>
      18                 : #include <boost/capy/ex/frame_allocator.hpp>
      19                 : #include <boost/capy/detail/await_suspend_helper.hpp>
      20                 : 
      21                 : #include <exception>
      22                 : #include <optional>
      23                 : #include <type_traits>
      24                 : #include <utility>
      25                 : #include <variant>
      26                 : 
      27                 : namespace boost {
      28                 : namespace capy {
      29                 : 
      30                 : namespace detail {
      31                 : 
      32                 : // Helper base for result storage and return_void/return_value
      33                 : template<typename T>
      34                 : struct task_return_base
      35                 : {
      36                 :     std::optional<T> result_;
      37                 : 
      38 HIT        1323 :     void return_value(T value)
      39                 :     {
      40            1323 :         result_ = std::move(value);
      41            1323 :     }
      42                 : 
      43             160 :     T&& result() noexcept
      44                 :     {
      45             160 :         return std::move(*result_);
      46                 :     }
      47                 : };
      48                 : 
      49                 : template<>
      50                 : struct task_return_base<void>
      51                 : {
      52            1987 :     void return_void()
      53                 :     {
      54            1987 :     }
      55                 : };
      56                 : 
      57                 : } // namespace detail
      58                 : 
      59                 : /** Lazy coroutine task satisfying @ref IoRunnable.
      60                 : 
      61                 :     Use `task<T>` as the return type for coroutines that perform I/O
      62                 :     and return a value of type `T`. The coroutine body does not start
      63                 :     executing until the task is awaited, enabling efficient composition
      64                 :     without unnecessary eager execution.
      65                 : 
      66                 :     The task participates in the I/O awaitable protocol: when awaited,
      67                 :     it receives the caller's executor and stop token, propagating them
      68                 :     to nested `co_await` expressions. This enables cancellation and
      69                 :     proper completion dispatch across executor boundaries.
      70                 : 
      71                 :     @par Thread Safety
      72                 :     Distinct objects: Safe.
      73                 :     Shared objects: Unsafe.
      74                 : 
      75                 :     @par Example
      76                 : 
      77                 :     @code
      78                 :     task<int> compute_value()
      79                 :     {
      80                 :         auto [ec, n] = co_await stream.read_some( buf );
      81                 :         if( ec )
      82                 :             co_return 0;
      83                 :         co_return process( buf, n );
      84                 :     }
      85                 : 
      86                 :     task<> run_session( tcp_socket sock )
      87                 :     {
      88                 :         int result = co_await compute_value();
      89                 :         // ...
      90                 :     }
      91                 :     @endcode
      92                 : 
      93                 :     @tparam T The result type. Use `task<>` for `task<void>`.
      94                 : 
      95                 :     @see IoRunnable, IoAwaitable, run, run_async
      96                 : */
      97                 : template<typename T = void>
      98                 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
      99                 :     task
     100                 : {
     101                 :     /** The coroutine promise type for `task<T>`.
     102                 : 
     103                 :         This is the promise object the compiler associates with a
     104                 :         `task<T>` coroutine. It satisfies the coroutine promise
     105                 :         requirements and participates in the I/O awaitable protocol via
     106                 :         @ref io_awaitable_promise_base. It is part of the coroutine
     107                 :         machinery and is not intended to be used directly by callers.
     108                 : 
     109                 :         Result storage and `return_value`/`return_void` are provided by
     110                 :         `detail::task_return_base<T>`.
     111                 : 
     112                 :         @see io_awaitable_promise_base, IoRunnable
     113                 :     */
     114                 :     struct promise_type
     115                 :         : io_awaitable_promise_base<promise_type>
     116                 :         , detail::task_return_base<T>
     117                 :     {
     118                 :     private:
     119                 :         friend task;
     120                 :         union { std::exception_ptr ep_; };
     121                 :         bool has_ep_;
     122                 : 
     123                 :     public:
     124                 :         /// Construct the promise with no stored exception.
     125            5108 :         promise_type() noexcept
     126            5108 :             : has_ep_(false)
     127                 :         {
     128            5108 :         }
     129                 : 
     130                 :         /// Destroy the promise, releasing any stored exception.
     131            5108 :         ~promise_type()
     132                 :         {
     133            5108 :             if(has_ep_)
     134            1612 :                 ep_.~exception_ptr();
     135            5108 :         }
     136                 : 
     137                 :         /** Return the exception captured by the coroutine body, if any.
     138                 : 
     139                 :             @return The stored exception, or a null `std::exception_ptr`
     140                 :             if the coroutine did not exit via an unhandled exception.
     141                 :         */
     142            4182 :         std::exception_ptr exception() const noexcept
     143                 :         {
     144            4182 :             if(has_ep_)
     145            2108 :                 return ep_;
     146            2074 :             return {};
     147                 :         }
     148                 : 
     149                 :         /** Return the owning `task` for this coroutine.
     150                 : 
     151                 :             Called by the compiler to produce the object returned to the
     152                 :             caller when the coroutine is created.
     153                 : 
     154                 :             @return A `task` owning the coroutine frame.
     155                 :         */
     156            5108 :         task get_return_object()
     157                 :         {
     158            5108 :             return task{std::coroutine_handle<promise_type>::from_promise(*this)};
     159                 :         }
     160                 : 
     161                 :         /** Return the initial-suspend awaiter.
     162                 : 
     163                 :             The coroutine always suspends at the initial suspend point,
     164                 :             so the body does not start until the task is awaited. When the
     165                 :             body is resumed, the awaiter restores the thread-local frame
     166                 :             allocator from the stored environment.
     167                 : 
     168                 :             @return An awaiter that suspends unconditionally.
     169                 :         */
     170            5108 :         auto initial_suspend() noexcept
     171                 :         {
     172                 :             struct awaiter
     173                 :             {
     174                 :                 promise_type* p_;
     175                 : 
     176            5108 :                 bool await_ready() const noexcept
     177                 :                 {
     178            5108 :                     return false;
     179                 :                 }
     180                 : 
     181            5108 :                 void await_suspend(std::coroutine_handle<>) const noexcept
     182                 :                 {
     183            5108 :                 }
     184                 : 
     185            5100 :                 void await_resume() const noexcept
     186                 :                 {
     187                 :                     // Restore TLS when body starts executing
     188            5100 :                     set_current_frame_allocator(p_->environment()->frame_allocator);
     189            5100 :                 }
     190                 :             };
     191            5108 :             return awaiter{this};
     192                 :         }
     193                 : 
     194                 :         /** Return the final-suspend awaiter.
     195                 : 
     196                 :             The coroutine always suspends at the final suspend point. The
     197                 :             awaiter's `await_suspend` performs symmetric transfer to the
     198                 :             stored continuation (consuming it), resuming the awaiting
     199                 :             coroutine.
     200                 : 
     201                 :             @return An awaiter that suspends and transfers to the
     202                 :             continuation.
     203                 :         */
     204            4922 :         auto final_suspend() noexcept
     205                 :         {
     206                 :             struct awaiter
     207                 :             {
     208                 :                 promise_type* p_;
     209                 : 
     210            4922 :                 bool await_ready() const noexcept
     211                 :                 {
     212            4922 :                     return false;
     213                 :                 }
     214                 : 
     215            4922 :                 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
     216                 :                 {
     217            4922 :                     return p_->continuation();
     218                 :                 }
     219                 : 
     220                 :                 void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
     221                 :             };
     222            4922 :             return awaiter{this};
     223                 :         }
     224                 : 
     225                 :         /** Capture the in-flight exception from the coroutine body.
     226                 : 
     227                 :             Called by the compiler when the coroutine body exits via an
     228                 :             unhandled exception. The captured exception is rethrown when
     229                 :             the task is awaited.
     230                 :         */
     231            1612 :         void unhandled_exception() noexcept
     232                 :         {
     233            1612 :             new (&ep_) std::exception_ptr(std::current_exception());
     234            1612 :             has_ep_ = true;
     235            1612 :         }
     236                 : 
     237                 :         /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
     238                 : 
     239                 :             Forwards the environment to the inner awaitable's
     240                 :             environment-taking `await_suspend` and restores the
     241                 :             thread-local frame allocator before the body resumes.
     242                 : 
     243                 :             @tparam Awaitable The awaitable being transformed.
     244                 :         */
     245                 :         template<class Awaitable>
     246                 :         struct transform_awaiter
     247                 :         {
     248                 :             std::decay_t<Awaitable> a_;
     249                 :             promise_type* p_;
     250                 : 
     251            9260 :             bool await_ready() noexcept
     252                 :             {
     253            9260 :                 return a_.await_ready();
     254                 :             }
     255                 : 
     256            9082 :             decltype(auto) await_resume()
     257                 :             {
     258                 :                 // Restore TLS before body resumes
     259            9082 :                 set_current_frame_allocator(p_->environment()->frame_allocator);
     260            9082 :                 return a_.await_resume();
     261                 :             }
     262                 : 
     263                 :             template<class Promise>
     264            2545 :             auto await_suspend(std::coroutine_handle<Promise> h) noexcept
     265                 :             {
     266                 :                 using R = decltype(a_.await_suspend(h, p_->environment()));
     267                 :                 if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
     268            2544 :                     return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
     269                 :                 else
     270               1 :                     return a_.await_suspend(h, p_->environment());
     271                 :             }
     272                 :         };
     273                 : 
     274                 :         /** Transform a nested awaitable before `co_await`.
     275                 : 
     276                 :             Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
     277                 :             coroutine's environment is propagated into it. A diagnostic
     278                 :             is emitted if the awaitable does not satisfy @ref IoAwaitable.
     279                 : 
     280                 :             @param a The awaitable expression from `co_await a`.
     281                 : 
     282                 :             @return A @ref transform_awaiter wrapping `a`.
     283                 :         */
     284                 :         template<class Awaitable>
     285            9260 :         auto transform_awaitable(Awaitable&& a)
     286                 :         {
     287                 :             using A = std::decay_t<Awaitable>;
     288                 :             if constexpr (IoAwaitable<A>)
     289                 :             {
     290                 :                 return transform_awaiter<Awaitable>{
     291           11458 :                     std::forward<Awaitable>(a), this};
     292                 :             }
     293                 :             else
     294                 :             {
     295                 :                 static_assert(sizeof(A) == 0, "requires IoAwaitable");
     296                 :             }
     297            2198 :         }
     298                 :     };
     299                 : 
     300                 :     /** Handle to the owned coroutine frame.
     301                 : 
     302                 :         Null when the task is empty (for example after a move or after
     303                 :         @ref release). Prefer @ref handle to read this; the member is
     304                 :         public for use by the coroutine machinery.
     305                 :     */
     306                 :     std::coroutine_handle<promise_type> h_;
     307                 : 
     308                 :     /// Destroy the task and its coroutine frame if owned.
     309           10575 :     ~task()
     310                 :     {
     311           10575 :         if(h_)
     312            1737 :             h_.destroy();
     313           10575 :     }
     314                 : 
     315                 :     /** Report whether the awaited task is already complete.
     316                 : 
     317                 :         Always returns `false`; a task is lazy and has not started when
     318                 :         it is awaited, so the awaiting coroutine always suspends.
     319                 : 
     320                 :         @return `false`.
     321                 :     */
     322            1599 :     bool await_ready() const noexcept
     323                 :     {
     324            1599 :         return false;
     325                 :     }
     326                 : 
     327                 :     /** Return the task's result, rethrowing any captured exception.
     328                 : 
     329                 :         If the coroutine body exited via an unhandled exception, that
     330                 :         exception is rethrown here. Otherwise the result is returned by
     331                 :         move (for `task<T>`) or nothing is returned (for `task<void>`).
     332                 : 
     333                 :         @return The result value for non-void `T`; otherwise `void`.
     334                 : 
     335                 :         @throws The exception captured by the coroutine body, if any.
     336                 :     */
     337            1734 :     auto await_resume()
     338                 :     {
     339            1734 :         if(h_.promise().has_ep_)
     340             557 :             std::rethrow_exception(h_.promise().ep_);
     341                 :         if constexpr (! std::is_void_v<T>)
     342            1161 :             return std::move(*h_.promise().result_);
     343                 :         else
     344              16 :             return;
     345                 :     }
     346                 : 
     347                 :     /** Start the task with the awaiting coroutine's context.
     348                 : 
     349                 :         Stores `cont` as the continuation to resume on completion and
     350                 :         `env` as the execution environment propagated to nested
     351                 :         `co_await` expressions, then transfers control into the task's
     352                 :         coroutine body via the returned handle.
     353                 : 
     354                 :         @param cont The awaiting coroutine to resume when the task
     355                 :         completes.
     356                 : 
     357                 :         @param env The execution environment (executor, stop token, and
     358                 :         frame allocator). It must outlive the task.
     359                 : 
     360                 :         @return The task's coroutine handle, for symmetric transfer.
     361                 :     */
     362            1712 :     std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
     363                 :     {
     364            1712 :         h_.promise().set_continuation(cont);
     365            1712 :         h_.promise().set_environment(env);
     366            1712 :         return h_;
     367                 :     }
     368                 : 
     369                 :     /** Return the coroutine handle.
     370                 : 
     371                 :         @note Do not call `destroy()` on the returned handle while the
     372                 :         task is being awaited. The task's lifetime is normally managed
     373                 :         by `run_async`, `run`, or the awaiting parent; manually
     374                 :         destroying a suspended task that another coroutine is awaiting
     375                 :         produces undefined behavior. For cooperative cancellation, use
     376                 :         `std::stop_token`.
     377                 : 
     378                 :         @return The coroutine handle.
     379                 :     */
     380            3396 :     std::coroutine_handle<promise_type> handle() const noexcept
     381                 :     {
     382            3396 :         return h_;
     383                 :     }
     384                 : 
     385                 :     /** Release ownership of the coroutine frame.
     386                 : 
     387                 :         After calling this, destroying the task does not destroy the
     388                 :         coroutine frame. The caller becomes responsible for the frame's
     389                 :         lifetime.
     390                 : 
     391                 :         @note If the caller intends to call `destroy()` on the
     392                 :         released handle, it must do so only when the task has not
     393                 :         started or has fully completed. Destroying a suspended task
     394                 :         that is being awaited produces undefined behavior.
     395                 : 
     396                 :         @par Postconditions
     397                 :         `handle()` returns the original handle, but the task no longer
     398                 :         owns it.
     399                 :     */
     400            3371 :     void release() noexcept
     401                 :     {
     402            3371 :         h_ = nullptr;
     403            3371 :     }
     404                 : 
     405                 :     task(task const&) = delete;
     406                 :     task& operator=(task const&) = delete;
     407                 : 
     408                 :     /** Construct by moving, transferring ownership of the frame.
     409                 : 
     410                 :         @par Postconditions
     411                 :         `other` is empty and must not be awaited.
     412                 : 
     413                 :         @param other The task to move from.
     414                 :     */
     415            5467 :     task(task&& other) noexcept
     416            5467 :         : h_(std::exchange(other.h_, nullptr))
     417                 :     {
     418            5467 :     }
     419                 : 
     420                 :     /** Assign by moving, transferring ownership of the frame.
     421                 : 
     422                 :         If this task already owns a coroutine frame, that frame is
     423                 :         destroyed first. Self-assignment is a no-op.
     424                 : 
     425                 :         @par Postconditions
     426                 :         `other` is empty and must not be awaited.
     427                 : 
     428                 :         @param other The task to move from.
     429                 : 
     430                 :         @return `*this`.
     431                 :     */
     432                 :     task& operator=(task&& other) noexcept
     433                 :     {
     434                 :         if(this != &other)
     435                 :         {
     436                 :             if(h_)
     437                 :                 h_.destroy();
     438                 :             h_ = std::exchange(other.h_, nullptr);
     439                 :         }
     440                 :         return *this;
     441                 :     }
     442                 : 
     443                 : private:
     444            5108 :     explicit task(std::coroutine_handle<promise_type> h)
     445            5108 :         : h_(h)
     446                 :     {
     447            5108 :     }
     448                 : };
     449                 : 
     450                 : } // namespace capy
     451                 : } // namespace boost
     452                 : 
     453                 : #endif
        

Generated by: LCOV version 2.3