LCOV - code coverage report
Current view: top level - boost/capy - task.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 92.9 % 70 65
Test Date: 2026-01-21 22:59:19 Functions: 93.1 % 232 216

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/corosio
       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/io_awaitable.hpp>
      17              : #include <boost/capy/ex/executor_ref.hpp>
      18              : #include <boost/capy/ex/frame_allocator.hpp>
      19              : 
      20              : #include <exception>
      21              : #include <optional>
      22              : #include <type_traits>
      23              : #include <utility>
      24              : #include <variant>
      25              : 
      26              : namespace boost {
      27              : namespace capy {
      28              : 
      29              : namespace detail {
      30              : 
      31              : // Helper base for result storage and return_void/return_value
      32              : template<typename T>
      33              : struct task_return_base
      34              : {
      35              :     std::optional<T> result_;
      36              : 
      37          134 :     void return_value(T value)
      38              :     {
      39          134 :         result_ = std::move(value);
      40          134 :     }
      41              : };
      42              : 
      43              : template<>
      44              : struct task_return_base<void>
      45              : {
      46           28 :     void return_void()
      47              :     {
      48           28 :     }
      49              : };
      50              : 
      51              : } // namespace detail
      52              : 
      53              : /** A coroutine task type implementing the affine awaitable protocol.
      54              : 
      55              :     This task type represents an asynchronous operation that can be awaited.
      56              :     It implements the affine awaitable protocol where `await_suspend` receives
      57              :     the caller's executor, enabling proper completion dispatch across executor
      58              :     boundaries.
      59              : 
      60              :     @tparam T The return type of the task. Defaults to void.
      61              : 
      62              :     Key features:
      63              :     @li Lazy execution - the coroutine does not start until awaited
      64              :     @li Symmetric transfer - uses coroutine handle returns for efficient
      65              :         resumption
      66              :     @li Executor inheritance - inherits caller's executor unless explicitly
      67              :         bound
      68              : 
      69              :     The task uses `[[clang::coro_await_elidable]]` (when available) to enable
      70              :     heap allocation elision optimization (HALO) for nested coroutine calls.
      71              : 
      72              :     @see executor_ref
      73              : */
      74              : template<typename T = void>
      75              : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
      76              :     task
      77              : {
      78              :     struct promise_type
      79              :         : frame_allocating_base
      80              :         , io_awaitable_support<promise_type>
      81              :         , detail::task_return_base<T>
      82              :     {
      83              :         executor_ref caller_ex_;
      84              :         coro continuation_;
      85              :         std::exception_ptr ep_;
      86              :         detail::frame_allocator_base* alloc_ = nullptr;
      87              :         bool needs_dispatch_ = false;
      88              : 
      89          200 :         task get_return_object()
      90              :         {
      91          200 :             return task{std::coroutine_handle<promise_type>::from_promise(*this)};
      92              :         }
      93              : 
      94          200 :         auto initial_suspend() noexcept
      95              :         {
      96              :             struct awaiter
      97              :             {
      98              :                 promise_type* p_;
      99              : 
     100          200 :                 bool await_ready() const noexcept
     101              :                 {
     102          200 :                     return false;
     103              :                 }
     104              : 
     105          200 :                 void await_suspend(coro) const noexcept
     106              :                 {
     107              :                     // Capture TLS allocator while it's still valid
     108          200 :                     p_->alloc_ = get_frame_allocator();
     109          200 :                 }
     110              : 
     111          199 :                 void await_resume() const noexcept
     112              :                 {
     113              :                     // Restore TLS when body starts executing
     114          199 :                     if(p_->alloc_)
     115            0 :                         set_frame_allocator(*p_->alloc_);
     116          199 :                 }
     117              :             };
     118          200 :             return awaiter{this};
     119              :         }
     120              : 
     121          199 :         auto final_suspend() noexcept
     122              :         {
     123              :             struct awaiter
     124              :             {
     125              :                 promise_type* p_;
     126              : 
     127          199 :                 bool await_ready() const noexcept
     128              :                 {
     129          199 :                     return false;
     130              :                 }
     131              : 
     132          199 :                 coro await_suspend(coro) const noexcept
     133              :                 {
     134          199 :                     if(p_->continuation_)
     135              :                     {
     136              :                         // Same executor: true symmetric transfer
     137          182 :                         if(!p_->needs_dispatch_)
     138          182 :                             return p_->continuation_;
     139            0 :                         return p_->caller_ex_.dispatch(p_->continuation_);
     140              :                     }
     141           17 :                     return std::noop_coroutine();
     142              :                 }
     143              : 
     144            0 :                 void await_resume() const noexcept
     145              :                 {
     146            0 :                 }
     147              :             };
     148          199 :             return awaiter{this};
     149              :         }
     150              : 
     151              :         // return_void() or return_value() inherited from task_return_base
     152              : 
     153           37 :         void unhandled_exception()
     154              :         {
     155           37 :             ep_ = std::current_exception();
     156           37 :         }
     157              : 
     158              :         template<class Awaitable>
     159              :         struct transform_awaiter
     160              :         {
     161              :             std::decay_t<Awaitable> a_;
     162              :             promise_type* p_;
     163              : 
     164           64 :             bool await_ready()
     165              :             {
     166           64 :                 return a_.await_ready();
     167              :             }
     168              : 
     169           64 :             auto await_resume()
     170              :             {
     171              :                 // Restore TLS before body resumes
     172           64 :                 if(p_->alloc_)
     173            0 :                     set_frame_allocator(*p_->alloc_);
     174           64 :                 return a_.await_resume();
     175              :             }
     176              : 
     177              :             template<class Promise>
     178           64 :             auto await_suspend(std::coroutine_handle<Promise> h)
     179              :             {
     180           64 :                 return a_.await_suspend(h, p_->executor(), p_->stop_token());
     181              :             }
     182              :         };
     183              : 
     184              :         template<class Awaitable>
     185           64 :         auto transform_awaitable(Awaitable&& a)
     186              :         {
     187              :             using A = std::decay_t<Awaitable>;
     188              :             if constexpr (IoAwaitable<A, executor_ref>)
     189              :             {
     190              :                 // Zero-overhead path for I/O awaitables
     191              :                 return transform_awaiter<Awaitable>{
     192          104 :                     std::forward<Awaitable>(a), this};
     193              :             }
     194              :             else
     195              :             {
     196              :                 static_assert(sizeof(A) == 0, "requires IoAwaitable");
     197              :             }
     198           40 :         }
     199              :     };
     200              : 
     201              :     std::coroutine_handle<promise_type> h_;
     202              : 
     203          555 :     ~task()
     204              :     {
     205          555 :         if(h_)
     206          102 :             h_.destroy();
     207          555 :     }
     208              : 
     209          102 :     bool await_ready() const noexcept
     210              :     {
     211          102 :         return false;
     212              :     }
     213              : 
     214          101 :     auto await_resume()
     215              :     {
     216          101 :         if(h_.promise().ep_)
     217           16 :             std::rethrow_exception(h_.promise().ep_);
     218              :         if constexpr (! std::is_void_v<T>)
     219           72 :             return std::move(*h_.promise().result_);
     220              :         else
     221           13 :             return;
     222              :     }
     223              : 
     224              :     // IoAwaitable: receive caller's executor and stop_token for completion dispatch
     225              :     template<typename Ex>
     226          101 :     coro await_suspend(coro continuation, Ex const& caller_ex, capy::stop_token token)
     227              :     {
     228          101 :         h_.promise().caller_ex_ = caller_ex;
     229          101 :         h_.promise().continuation_ = continuation;
     230          101 :         h_.promise().set_executor(caller_ex);
     231          101 :         h_.promise().set_stop_token(token);
     232          101 :         h_.promise().needs_dispatch_ = false;
     233          101 :         return h_;
     234              :     }
     235              : 
     236              :     /** Release ownership of the coroutine handle.
     237              : 
     238              :         After calling this, the task no longer owns the handle and will
     239              :         not destroy it. The caller is responsible for the handle's lifetime.
     240              : 
     241              :         @return The coroutine handle, or nullptr if already released.
     242              :     */
     243          101 :     auto release() noexcept ->
     244              :         std::coroutine_handle<promise_type>
     245              :     {
     246          101 :         return std::exchange(h_, nullptr);
     247              :     }
     248              : 
     249              :     // Non-copyable
     250              :     task(task const&) = delete;
     251              :     task& operator=(task const&) = delete;
     252              : 
     253              :     // Movable
     254          355 :     task(task&& other) noexcept
     255          355 :         : h_(std::exchange(other.h_, nullptr))
     256              :     {
     257          355 :     }
     258              : 
     259              :     task& operator=(task&& other) noexcept
     260              :     {
     261              :         if(this != &other)
     262              :         {
     263              :             if(h_)
     264              :                 h_.destroy();
     265              :             h_ = std::exchange(other.h_, nullptr);
     266              :         }
     267              :         return *this;
     268              :     }
     269              : 
     270              : private:
     271          200 :     explicit task(std::coroutine_handle<promise_type> h)
     272          200 :         : h_(h)
     273              :     {
     274          200 :     }
     275              : };
     276              : 
     277              : } // namespace capy
     278              : } // namespace boost
     279              : 
     280              : #endif
        

Generated by: LCOV version 2.3