LCOV - code coverage report
Current view: top level - capy - task.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 96.2 % 78 75 3
Test Date: 2026-06-04 15:25:39 Functions: 90.4 % 1141 1031 110

           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 <concepts>
      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                 : template<typename ... Ts>
      32                 : struct io_result;
      33                 : 
      34                 : namespace detail {
      35                 : 
      36                 : // Helper base for result storage and return_void/return_value
      37                 : template<typename T>
      38                 : struct task_return_base
      39                 : {
      40                 :     std::optional<T> result_;
      41                 : 
      42 HIT        1289 :     void return_value(T value)
      43                 :     {
      44            1289 :         result_ = std::move(value);
      45            1289 :     }
      46                 : 
      47             154 :     T&& result() noexcept
      48                 :     {
      49             154 :         return std::move(*result_);
      50                 :     }
      51                 : };
      52                 : 
      53                 : template<>
      54                 : struct task_return_base<void>
      55                 : {
      56            1996 :     void return_void()
      57                 :     {
      58            1996 :     }
      59                 : };
      60                 : 
      61                 : template<typename ... Us>
      62                 : void handle_yield_result(io_result<Us...> & res, std::error_code ec)
      63                 : {
      64                 :     static_assert((std::constructible_from<Us> && ...), "co_yield requires all result value to be default constructible");
      65                 : }
      66                 : 
      67                 : } // namespace detail
      68                 : 
      69                 : /** Lazy coroutine task satisfying @ref IoRunnable.
      70                 : 
      71                 :     Use `task<T>` as the return type for coroutines that perform I/O
      72                 :     and return a value of type `T`. The coroutine body does not start
      73                 :     executing until the task is awaited, enabling efficient composition
      74                 :     without unnecessary eager execution.
      75                 : 
      76                 :     The task participates in the I/O awaitable protocol: when awaited,
      77                 :     it receives the caller's executor and stop token, propagating them
      78                 :     to nested `co_await` expressions. This enables cancellation and
      79                 :     proper completion dispatch across executor boundaries.
      80                 : 
      81                 :     @par Thread Safety
      82                 :     Distinct objects: Safe.
      83                 :     Shared objects: Unsafe.
      84                 : 
      85                 :     @par Example
      86                 : 
      87                 :     @code
      88                 :     task<int> compute_value()
      89                 :     {
      90                 :         auto [ec, n] = co_await stream.read_some( buf );
      91                 :         if( ec )
      92                 :             co_return 0;
      93                 :         co_return process( buf, n );
      94                 :     }
      95                 : 
      96                 :     task<> run_session( tcp_socket sock )
      97                 :     {
      98                 :         int result = co_await compute_value();
      99                 :         // ...
     100                 :     }
     101                 :     @endcode
     102                 : 
     103                 :     @tparam T The result type. Use `task<>` for `task<void>`.
     104                 : 
     105                 :     @see IoRunnable, IoAwaitable, run, run_async
     106                 : */
     107                 : template<typename T = void>
     108                 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
     109                 :     task
     110                 : {
     111                 :     struct promise_type
     112                 :         : io_awaitable_promise_base<promise_type>
     113                 :         , detail::task_return_base<T>
     114                 :     {
     115                 :     protected:
     116                 :         friend task;
     117                 :         union { std::exception_ptr ep_; };
     118                 :         bool has_ep_;
     119                 : 
     120                 :     public:
     121            5047 :         promise_type() noexcept
     122            5047 :             : has_ep_(false)
     123                 :         {
     124            5047 :         }
     125                 : 
     126            5047 :         ~promise_type()
     127                 :         {
     128            5047 :             if(has_ep_)
     129            1599 :                 ep_.~exception_ptr();
     130            5047 :         }
     131                 : 
     132            4174 :         std::exception_ptr exception() const noexcept
     133                 :         {
     134            4174 :             if(has_ep_)
     135            2094 :                 return ep_;
     136            2080 :             return {};
     137                 :         }
     138                 : 
     139            3433 :         task get_return_object()
     140                 :         {
     141            3433 :             return task{std::coroutine_handle<promise_type>::from_promise(*this)};
     142                 :         }
     143                 : 
     144            5047 :         auto initial_suspend() noexcept
     145                 :         {
     146                 :             struct awaiter
     147                 :             {
     148                 :                 promise_type* p_;
     149                 : 
     150            5047 :                 bool await_ready() const noexcept
     151                 :                 {
     152            5047 :                     return false;
     153                 :                 }
     154                 : 
     155            5047 :                 void await_suspend(std::coroutine_handle<>) const noexcept
     156                 :                 {
     157            5047 :                 }
     158                 : 
     159            5044 :                 void await_resume() const noexcept
     160                 :                 {
     161                 :                     // Restore TLS when body starts executing
     162            5044 :                     set_current_frame_allocator(p_->environment()->frame_allocator);
     163            5044 :                 }
     164                 :             };
     165            5047 :             return awaiter{this};
     166                 :         }
     167                 : 
     168            4882 :         auto final_suspend() noexcept
     169                 :         {
     170                 :             struct awaiter
     171                 :             {
     172                 :                 promise_type* p_;
     173                 : 
     174            4882 :                 bool await_ready() const noexcept
     175                 :                 {
     176            4882 :                     return false;
     177                 :                 }
     178                 : 
     179            4882 :                 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
     180                 :                 {
     181            4882 :                     return p_->continuation();
     182                 :                 }
     183                 : 
     184 MIS           0 :                 void await_resume() const noexcept
     185                 :                 {
     186               0 :                 }
     187                 :             };
     188 HIT        4882 :             return awaiter{this};
     189                 :         }
     190                 : 
     191            1599 :         void unhandled_exception() noexcept
     192                 :         {
     193            1599 :             new (&ep_) std::exception_ptr(std::current_exception());
     194            1599 :             has_ep_ = true;
     195            1599 :         }
     196                 : 
     197                 :         template<class Awaitable>
     198                 :         struct transform_awaiter
     199                 :         {
     200                 :             std::decay_t<Awaitable> a_;
     201                 :             promise_type* p_;
     202                 : 
     203            9185 :             bool await_ready() noexcept
     204                 :             {
     205            9185 :                 return a_.await_ready();
     206                 :             }
     207                 : 
     208            9025 :             decltype(auto) await_resume()
     209                 :             {
     210                 :                 // Restore TLS before body resumes
     211            9025 :                 set_current_frame_allocator(p_->environment()->frame_allocator);
     212            9025 :                 return a_.await_resume();
     213                 :             }
     214                 : 
     215                 :             template<class Promise>
     216            2497 :             auto await_suspend(std::coroutine_handle<Promise> h) noexcept
     217                 :             {
     218                 :                 using R = decltype(a_.await_suspend(h, p_->environment()));
     219                 :                 if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
     220            2497 :                     return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
     221                 :                 else
     222 MIS           0 :                     return a_.await_suspend(h, p_->environment());
     223                 :             }
     224                 :         };
     225                 : 
     226                 :         template<class Awaitable>
     227 HIT        9185 :         auto transform_awaitable(Awaitable&& a)
     228                 :         {
     229                 :             using A = std::decay_t<Awaitable>;
     230                 :             if constexpr (IoAwaitable<A>)
     231                 :             {
     232                 :                 return transform_awaiter<Awaitable>{
     233           11354 :                     std::forward<Awaitable>(a), this};
     234                 :             }
     235                 :             else
     236                 :             {
     237                 :                 static_assert(sizeof(A) == 0, "requires IoAwaitable");
     238                 :             }
     239            2169 :         }
     240                 : 
     241                 :                 
     242                 : 
     243                 :         template<class ... Ts>
     244                 :         auto yield_value(io_result<Ts...> res)
     245                 :         {
     246                 :             struct awaitable
     247                 :             {
     248                 :                 io_result<Ts...> res;
     249                 :                 
     250                 :                 bool await_ready() const {return !res.ec;}
     251                 :                 void await_suspend(std::coroutine_handle<promise_type> h)
     252                 :                 {
     253                 :                     auto & p = h.promise();
     254                 :                     try 
     255                 :                     {
     256                 :                         detail::handle_yield_result(h.promise(), res.ec);
     257                 :                     }
     258                 :                     catch (...)
     259                 :                     {
     260                 :                         p.uncaught_exception();
     261                 :                     }
     262                 :                     
     263                 :                 }
     264                 :                 std::tuple<Ts...> await_resume() 
     265                 :                 {
     266                 :                     return std::move(res.values);
     267                 :                 }
     268                 :             };
     269                 :         }
     270                 :     };
     271                 : 
     272                 :     std::coroutine_handle<promise_type> h_;
     273                 : 
     274                 :     /// Destroy the task and its coroutine frame if owned.
     275            6375 :     ~task()
     276                 :     {
     277            6375 :         if(h_)
     278              96 :             h_.destroy();
     279            6375 :     }
     280                 : 
     281                 :     /// Return false; tasks are never immediately ready.
     282              94 :     bool await_ready() const noexcept
     283                 :     {
     284              94 :         return false;
     285                 :     }
     286                 : 
     287                 :     /// Return the result or rethrow any stored exception.
     288              93 :     auto await_resume()
     289                 :     {
     290              93 :         if(h_.promise().has_ep_)
     291              18 :             std::rethrow_exception(h_.promise().ep_);
     292                 :         if constexpr (! std::is_void_v<T>)
     293              59 :             return std::move(*h_.promise().result_);
     294                 :         else
     295              16 :             return;
     296                 :     }
     297                 : 
     298                 :     /// Start execution with the caller's context.
     299              71 :     std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
     300                 :     {
     301              71 :         h_.promise().set_continuation(cont);
     302              71 :         h_.promise().set_environment(env);
     303              71 :         return h_;
     304                 :     }
     305                 : 
     306                 :     /** Return the coroutine handle.
     307                 : 
     308                 :         @note Do not call `destroy()` on the returned handle while the
     309                 :         task is being awaited. The task's lifetime is normally managed
     310                 :         by `run_async`, `run`, or the awaiting parent; manually
     311                 :         destroying a suspended task that another coroutine is awaiting
     312                 :         produces undefined behavior. For cooperative cancellation, use
     313                 :         `std::stop_token`.
     314                 : 
     315                 :         @return The coroutine handle.
     316                 :     */
     317            3362 :     std::coroutine_handle<promise_type> handle() const noexcept
     318                 :     {
     319            3362 :         return h_;
     320                 :     }
     321                 : 
     322                 :     /** Release ownership of the coroutine frame.
     323                 : 
     324                 :         After calling this, destroying the task does not destroy the
     325                 :         coroutine frame. The caller becomes responsible for the frame's
     326                 :         lifetime.
     327                 : 
     328                 :         @note If the caller intends to call `destroy()` on the
     329                 :         released handle, it must do so only when the task has not
     330                 :         started or has fully completed. Destroying a suspended task
     331                 :         that is being awaited produces undefined behavior.
     332                 : 
     333                 :         @par Postconditions
     334                 :         `handle()` returns the original handle, but the task no longer
     335                 :         owns it.
     336                 :     */
     337            3337 :     void release() noexcept
     338                 :     {
     339            3337 :         h_ = nullptr;
     340            3337 :     }
     341                 : 
     342                 :     task(task const&) = delete;
     343                 :     task& operator=(task const&) = delete;
     344                 : 
     345                 :     /// Construct by moving, transferring ownership.
     346            2942 :     task(task&& other) noexcept
     347            2942 :         : h_(std::exchange(other.h_, nullptr))
     348                 :     {
     349            2942 :     }
     350                 : 
     351                 :     /// Assign by moving, transferring ownership.
     352                 :     task& operator=(task&& other) noexcept
     353                 :     {
     354                 :         if(this != &other)
     355                 :         {
     356                 :             if(h_)
     357                 :                 h_.destroy();
     358                 :             h_ = std::exchange(other.h_, nullptr);
     359                 :         }
     360                 :         return *this;
     361                 :     }
     362                 : 
     363                 : private:
     364            3433 :     explicit task(std::coroutine_handle<promise_type> h)
     365            3433 :         : h_(h)
     366                 :     {
     367            3433 :     }
     368                 : };
     369                 : 
     370                 : } // namespace capy
     371                 : } // namespace boost
     372                 : 
     373                 : #endif
        

Generated by: LCOV version 2.3