LCOV - code coverage report
Current view: top level - capy - task.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 80 80
Test Date: 2026-08-17 19:05:11 Functions: 95.7 % 1517 1452 65

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

Generated by: LCOV version 2.3