LCOV - code coverage report
Current view: top level - capy/ex - io_awaitable_promise_base.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 35 35
Test Date: 2026-08-21 22:12:46 Functions: 92.9 % 534 496 38

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3                 : // Copyright (c) 2026 Michael Vandeberg
       4                 : //
       5                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       6                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       7                 : //
       8                 : // Official repository: https://github.com/cppalliance/capy
       9                 : //
      10                 : 
      11                 : #ifndef BOOST_CAPY_EX_IO_AWAITABLE_PROMISE_BASE_HPP
      12                 : #define BOOST_CAPY_EX_IO_AWAITABLE_PROMISE_BASE_HPP
      13                 : 
      14                 : #include <boost/capy/detail/config.hpp>
      15                 : #include <boost/capy/ex/frame_alloc_mixin.hpp>
      16                 : #include <boost/capy/ex/frame_allocator.hpp>
      17                 : #include <boost/capy/ex/io_env.hpp>
      18                 : #include <boost/capy/ex/this_coro.hpp>
      19                 : 
      20                 : #include <coroutine>
      21                 : #include <memory_resource>
      22                 : #include <stop_token>
      23                 : #include <type_traits>
      24                 : 
      25                 : namespace boost {
      26                 : namespace capy {
      27                 : 
      28                 : /** CRTP mixin that adds I/O awaitable support to a promise type.
      29                 : 
      30                 :     Inherit from this class to enable these capabilities in your coroutine:
      31                 : 
      32                 :     1. **Frame allocation** — The mixin provides `operator new/delete` that
      33                 :        use the thread-local frame allocator set by `run_async`.
      34                 : 
      35                 :     2. **Environment storage** — The mixin stores a pointer to the `io_env`
      36                 :        containing the executor, stop token, and allocator for this coroutine.
      37                 : 
      38                 :     3. **Environment access** — Coroutine code can retrieve the environment
      39                 :        via `co_await this_coro::environment`, or individual fields via
      40                 :        `co_await this_coro::executor`, `co_await this_coro::stop_token`,
      41                 :        and `co_await this_coro::frame_allocator`.
      42                 : 
      43                 :     @tparam Derived The derived promise type (CRTP pattern).
      44                 : 
      45                 :     @par Basic Usage
      46                 : 
      47                 :     For coroutines that need to access their execution environment:
      48                 : 
      49                 :     @par !example example_1
      50                 : 
      51                 : 
      52                 :     @par Custom Awaitable Transformation
      53                 : 
      54                 :     If your promise needs to transform awaitables (e.g., for affinity or
      55                 :     logging), override `transform_awaitable` instead of `await_transform`:
      56                 : 
      57                 :     @par !example example_2
      58                 : 
      59                 : 
      60                 :     The mixin's `await_transform` intercepts @ref this_coro::environment_tag
      61                 :     and the fine-grained tag types (@ref this_coro::executor_tag,
      62                 :     @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag),
      63                 :     then delegates all other awaitables to your `transform_awaitable`.
      64                 : 
      65                 :     @par Making Your Coroutine an IoAwaitable
      66                 : 
      67                 :     The mixin handles the "inside the coroutine" part—accessing the
      68                 :     environment. To receive the environment when your coroutine is awaited
      69                 :     (satisfying @ref IoAwaitable), implement the `await_suspend` overload
      70                 :     on your coroutine return type:
      71                 : 
      72                 :     @par !example example_3
      73                 : 
      74                 : 
      75                 :     @par Thread Safety
      76                 :     The environment is stored during `await_suspend` and read during
      77                 :     `co_await this_coro::environment`. These occur on the same logical
      78                 :     thread of execution, so no synchronization is required.
      79                 : 
      80                 :     @see this_coro::environment, this_coro::executor,
      81                 :          this_coro::stop_token, this_coro::frame_allocator
      82                 :     @see io_env
      83                 :     @see IoAwaitable
      84                 : */
      85                 : template<typename Derived>
      86                 : class io_awaitable_promise_base
      87                 :     : public frame_alloc_mixin
      88                 : {
      89                 :     io_env const* env_ = nullptr;
      90                 :     mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
      91                 : 
      92                 : public:
      93                 :     /** Destroy the promise, destroying an orphaned continuation.
      94                 : 
      95                 :         A continuation is still stored only when the coroutine never
      96                 :         reached `final_suspend`, because @ref continuation consumes the
      97                 :         stored handle. Destroying it here is what keeps an abandoned
      98                 :         coroutine from leaking the trampoline frame that was waiting on it.
      99                 : 
     100                 :         @par Preconditions
     101                 :         No parent coroutine is awaiting this one. A parent's `await_suspend`
     102                 :         installs its own handle as the continuation, so destroying such a
     103                 :         coroutine directly would destroy the parent from here as well. See
     104                 :         @ref task::handle and @ref quitter::handle for the contract.
     105                 :     */
     106 HIT        2819 :     ~io_awaitable_promise_base()
     107                 :     {
     108                 :         // Abnormal teardown: destroy an orphaned continuation, e.g.
     109                 :         // a run_async trampoline when the task is destroyed before
     110                 :         // reaching final_suspend. Callers must not destroy a task
     111                 :         // via handle().destroy() while it is being awaited by a
     112                 :         // parent coroutine: that puts cont_ under another owner
     113                 :         // and would produce a double-destroy from this branch. See
     114                 :         // task::handle() / quitter::handle() for the contract.
     115            2819 :         if(cont_ != std::noop_coroutine())
     116             127 :             cont_.destroy();
     117            2819 :     }
     118                 : 
     119                 :     //----------------------------------------------------------
     120                 :     // Continuation support
     121                 :     //----------------------------------------------------------
     122                 : 
     123                 :     /** Store the continuation to resume on completion.
     124                 : 
     125                 :         Call this from your coroutine type's `await_suspend` overload
     126                 :         to set up the completion path. The `final_suspend` awaiter
     127                 :         returns this handle via unconditional symmetric transfer.
     128                 : 
     129                 :         @param cont The continuation to resume on completion.
     130                 :     */
     131            2730 :     void set_continuation(std::coroutine_handle<> cont) noexcept
     132                 :     {
     133            2730 :         cont_ = cont;
     134            2730 :     }
     135                 : 
     136                 :     /** Return and consume the stored continuation handle.
     137                 : 
     138                 :         Resets the stored handle to `noop_coroutine()` so the
     139                 :         destructor does not double-destroy it.
     140                 : 
     141                 :         @return The continuation for symmetric transfer.
     142                 :     */
     143            2667 :     std::coroutine_handle<> continuation() const noexcept
     144                 :     {
     145            2667 :         return std::exchange(cont_, std::noop_coroutine());
     146                 :     }
     147                 : 
     148                 :     //----------------------------------------------------------
     149                 :     // Environment support
     150                 :     //----------------------------------------------------------
     151                 : 
     152                 :     /** Store a pointer to the execution environment.
     153                 : 
     154                 :         Call this from your coroutine type's `await_suspend`
     155                 :         overload to make the environment available via
     156                 :         `co_await this_coro::environment`. The pointed-to
     157                 :         `io_env` must outlive this coroutine.
     158                 : 
     159                 :         @param env The environment to store.
     160                 :     */
     161            2815 :     void set_environment(io_env const* env) noexcept
     162                 :     {
     163            2815 :         env_ = env;
     164            2815 :     }
     165                 : 
     166                 :     /** Return the stored execution environment.
     167                 : 
     168                 :         @return The environment.
     169                 :     */
     170            7891 :     io_env const* environment() const noexcept
     171                 :     {
     172            7891 :         BOOST_CAPY_ASSERT(env_);
     173            7891 :         return env_;
     174                 :     }
     175                 : 
     176                 :     /** Transform an awaitable before co_await.
     177                 : 
     178                 :         Override this in your derived promise type to customize how
     179                 :         awaitables are transformed. The default implementation passes
     180                 :         the awaitable through unchanged.
     181                 : 
     182                 :         @param a The awaitable expression from `co_await a`.
     183                 : 
     184                 :         @return The transformed awaitable.
     185                 :     */
     186                 :     template<typename A>
     187                 :     decltype(auto) transform_awaitable(A&& a)
     188                 :     {
     189                 :         return std::forward<A>(a);
     190                 :     }
     191                 : 
     192                 :     /** Intercept co_await expressions.
     193                 : 
     194                 :         This function handles @ref this_coro::environment_tag and
     195                 :         the fine-grained tags (@ref this_coro::executor_tag,
     196                 :         @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag)
     197                 :         specially, returning an awaiter that yields the stored value.
     198                 :         All other awaitables are delegated to @ref transform_awaitable.
     199                 : 
     200                 :         @param t The awaited expression.
     201                 : 
     202                 :         @return An awaiter for the expression.
     203                 :     */
     204                 :     template<typename T>
     205            2953 :     auto await_transform(T&& t)
     206                 :     {
     207                 :         using Tag = std::decay_t<T>;
     208                 : 
     209                 :         if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
     210                 :         {
     211              18 :             BOOST_CAPY_ASSERT(env_);
     212                 :             struct awaiter
     213                 :             {
     214                 :                 io_env const* env_;
     215              16 :                 bool await_ready() const noexcept { return true; }
     216               2 :                 void await_suspend(std::coroutine_handle<>) const noexcept { }
     217              15 :                 io_env const* await_resume() const noexcept { return env_; }
     218                 :             };
     219              18 :             return awaiter{env_};
     220                 :         }
     221                 :         else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>)
     222                 :         {
     223               4 :             BOOST_CAPY_ASSERT(env_);
     224                 :             struct awaiter
     225                 :             {
     226                 :                 executor_ref executor_;
     227               3 :                 bool await_ready() const noexcept { return true; }
     228                 :                 void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
     229               3 :                 executor_ref await_resume() const noexcept { return executor_; }
     230                 :             };
     231               4 :             return awaiter{env_->executor};
     232                 :         }
     233                 :         else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>)
     234                 :         {
     235              24 :             BOOST_CAPY_ASSERT(env_);
     236                 :             struct awaiter
     237                 :             {
     238                 :                 std::stop_token token_;
     239              23 :                 bool await_ready() const noexcept { return true; }
     240                 :                 void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
     241              23 :                 std::stop_token await_resume() const noexcept { return token_; }
     242                 :             };
     243              24 :             return awaiter{env_->stop_token};
     244                 :         }
     245                 :         else if constexpr (std::is_same_v<Tag, this_coro::frame_allocator_tag>)
     246                 :         {
     247               8 :             BOOST_CAPY_ASSERT(env_);
     248                 :             struct awaiter
     249                 :             {
     250                 :                 std::pmr::memory_resource* frame_allocator_;
     251               6 :                 bool await_ready() const noexcept { return true; }
     252                 :                 void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
     253               7 :                 std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; }
     254                 :             };
     255               8 :             return awaiter{env_->frame_allocator};
     256                 :         }
     257                 :         else
     258                 :         {
     259            1340 :             return static_cast<Derived*>(this)->transform_awaitable(
     260            2899 :                 std::forward<T>(t));
     261                 :         }
     262                 :     }
     263                 : };
     264                 : 
     265                 : } // namespace capy
     266                 : } // namespace boost
     267                 : 
     268                 : #endif
        

Generated by: LCOV version 2.3