100.00% Lines (33/33) 100.00% Functions (15/15)
TLA Baseline Branch
Line Hits Code Line Hits 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_CONTEXT_AWAITABLE_PROMISE_BASE_HPP
  12 + #define BOOST_CAPY_EX_CONTEXT_AWAITABLE_PROMISE_BASE_HPP
  13 +
  14 + #include <boost/capy/detail/config.hpp>
  15 + #include <boost/capy/ex/abandon.hpp>
  16 + #include <boost/capy/ex/await_context.hpp>
  17 + #include <boost/capy/ex/frame_alloc_mixin.hpp>
  18 + #include <boost/capy/ex/frame_allocator.hpp>
  19 + #include <boost/capy/ex/this_coro.hpp>
  20 +
  21 + #include <coroutine>
  22 + #include <memory_resource>
  23 + #include <stop_token>
  24 + #include <type_traits>
  25 +
  26 + namespace boost {
  27 + namespace capy {
  28 +
  29 + /** CRTP mixin that adds I/O awaitable support to a promise type.
  30 +
  31 + Inherit from this class to enable these capabilities in your coroutine:
  32 +
  33 + 1. **Frame allocation** — The mixin provides `operator new/delete` that
  34 + use the thread-local frame allocator set by `run_async`.
  35 +
  36 + 2. **Environment storage** — The mixin stores a pointer to the `await_context`
  37 + containing the executor, stop token, and allocator for this coroutine.
  38 +
  39 + 3. **Environment access** — Coroutine code can retrieve the environment
  40 + via `co_await this_coro::environment`, or individual fields via
  41 + `co_await this_coro::executor`, `co_await this_coro::stop_token`,
  42 + and `co_await this_coro::frame_allocator`.
  43 +
  44 + @tparam Derived The derived promise type (CRTP pattern).
  45 +
  46 + @par Basic Usage
  47 +
  48 + For coroutines that need to access their execution environment:
  49 +
  50 + @par !example example_1
  51 +
  52 +
  53 + @par Custom Awaitable Transformation
  54 +
  55 + If your promise needs to transform awaitables (e.g., for affinity or
  56 + logging), override `transform_awaitable` instead of `await_transform`:
  57 +
  58 + @par !example example_2
  59 +
  60 +
  61 + The mixin's `await_transform` intercepts @ref this_coro::environment_tag
  62 + and the fine-grained tag types (@ref this_coro::executor_tag,
  63 + @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag),
  64 + then delegates all other awaitables to your `transform_awaitable`.
  65 +
  66 + @par Making Your Coroutine a ContextAwaitable
  67 +
  68 + The mixin handles the "inside the coroutine" part—accessing the
  69 + environment. To receive the environment when your coroutine is awaited
  70 + (satisfying @ref ContextAwaitable), implement the `await_suspend` overload
  71 + on your coroutine return type:
  72 +
  73 + @par !example example_3
  74 +
  75 +
  76 + @par Ownership
  77 + A coroutine's frame is owned by the object that awaits it, such as
  78 + the parent's `task`, so a chain is destroyed from its top down, each
  79 + frame freeing the one it awaits. The suspended coroutine at the
  80 + bottom is held by whoever will resume it: an operation, queue or
  81 + primitive. A holder that will never resume it calls @ref abandon,
  82 + which unwinds the chain's segment from its owner (see
  83 + @ref chain_owner) rather than destroying the bottom frame.
  84 + Destroying a frame that is still awaited, other than in that
  85 + unwind, calls `std::terminate`.
  86 +
  87 + @par Thread Safety
  88 + The environment is stored during `await_suspend` and read during
  89 + `co_await this_coro::environment`. These occur on the same logical
  90 + thread of execution, so no synchronization is required.
  91 +
  92 + @note A chain waiting on a primitive stored in its own frames owns
  93 + itself through that primitive; it is freed only by a stop request or
  94 + by the primitive being signalled. A mutex must outlive every
  95 + coroutine holding or handed its lock. A cross-thread `wake()` racing
  96 + the teardown of an armed waker's frame on its executor thread is a
  97 + precondition violation.
  98 +
  99 + @see this_coro::environment, this_coro::executor,
  100 + this_coro::stop_token, this_coro::frame_allocator
  101 + @see await_context
  102 + @see ContextAwaitable
  103 + */
  104 + template<typename Derived>
  105 + class context_awaitable_promise_base
  106 + : public frame_alloc_mixin
  107 + {
  108 + await_context const* env_ = nullptr;
  109 + mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
  110 +
  111 + public:
  112 + //----------------------------------------------------------
  113 + // Continuation support
  114 + //----------------------------------------------------------
  115 +
  116 + /** Store the continuation to resume on completion.
  117 +
  118 + Call this from your coroutine type's `await_suspend` overload
  119 + to set up the completion path. The `final_suspend` awaiter
  120 + returns this handle via unconditional symmetric transfer.
  121 +
  122 + @param cont The continuation to resume on completion.
  123 + */
HITGNC   124 + 107627 void set_continuation(std::coroutine_handle<> cont) noexcept
  125 + {
HITGNC   126 + 107627 cont_ = cont;
HITGNC   127 + 107627 }
  128 +
  129 + /** Return and consume the stored continuation handle.
  130 +
  131 + Resets the stored handle to `noop_coroutine()` so the
  132 + destructor does not double-destroy it.
  133 +
  134 + @return The continuation for symmetric transfer.
  135 + */
HITGNC   136 + 5062 std::coroutine_handle<> continuation() const noexcept
  137 + {
HITGNC   138 + 5062 return std::exchange(cont_, std::noop_coroutine());
  139 + }
  140 +
  141 + //----------------------------------------------------------
  142 + // Environment support
  143 + //----------------------------------------------------------
  144 +
  145 + /** Store a pointer to the execution environment.
  146 +
  147 + Call this from your coroutine type's `await_suspend`
  148 + overload to make the environment available via
  149 + `co_await this_coro::environment`. The pointed-to
  150 + `await_context` must outlive this coroutine.
  151 +
  152 + @param env The environment to store.
  153 + */
HITGNC   154 + 107713 void set_environment(await_context const* env) noexcept
  155 + {
HITGNC   156 + 107713 env_ = env;
HITGNC   157 + 107713 }
  158 +
  159 + /** Return the stored execution environment.
  160 +
  161 + @return The environment.
  162 + */
HITGNC   163 + 221255 await_context const* environment() const noexcept
  164 + {
HITGNC   165 + 221255 BOOST_CAPY_ASSERT(env_);
HITGNC   166 + 221255 return env_;
  167 + }
  168 +
  169 + /** Transform an awaitable before co_await.
  170 +
  171 + Override this in your derived promise type to customize how
  172 + awaitables are transformed. The default implementation passes
  173 + the awaitable through unchanged.
  174 +
  175 + @param a The awaitable expression from `co_await a`.
  176 +
  177 + @return The transformed awaitable.
  178 + */
  179 + template<typename A>
HITGNC   180 + 1 decltype(auto) transform_awaitable(A&& a)
  181 + {
HITGNC   182 + 1 return std::forward<A>(a);
  183 + }
  184 +
  185 + /** Intercept co_await expressions.
  186 +
  187 + This function handles @ref this_coro::environment_tag and
  188 + the fine-grained tags (@ref this_coro::executor_tag,
  189 + @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag)
  190 + specially, returning an awaiter that yields the stored value.
  191 + All other awaitables are delegated to @ref transform_awaitable.
  192 +
  193 + @param t The awaited expression.
  194 +
  195 + @return An awaiter for the expression.
  196 + */
  197 + template<typename T>
HITGNC   198 + 107809 auto await_transform(T&& t)
  199 + {
  200 + using Tag = std::decay_t<T>;
  201 +
  202 + if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
  203 + {
HITGNC   204 + 24 BOOST_CAPY_ASSERT(env_);
  205 + struct awaiter
  206 + {
  207 + await_context const* env_;
HITGNC   208 + 22 bool await_ready() const noexcept { return true; }
HITGNC   209 + 2 void await_suspend(std::coroutine_handle<>) const noexcept { }
HITGNC   210 + 21 await_context const* await_resume() const noexcept { return env_; }
  211 + };
HITGNC   212 + 24 return awaiter{env_};
  213 + }
  214 + else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>)
  215 + {
HITGNC   216 + 4 BOOST_CAPY_ASSERT(env_);
  217 + struct awaiter
  218 + {
  219 + executor_ref executor_;
HITGNC   220 + 3 bool await_ready() const noexcept { return true; }
  221 + void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
HITGNC   222 + 3 executor_ref await_resume() const noexcept { return executor_; }
  223 + };
HITGNC   224 + 4 return awaiter{env_->executor};
  225 + }
  226 + else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>)
  227 + {
HITGNC   228 + 24 BOOST_CAPY_ASSERT(env_);
  229 + struct awaiter
  230 + {
  231 + std::stop_token token_;
HITGNC   232 + 23 bool await_ready() const noexcept { return true; }
  233 + void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
HITGNC   234 + 23 std::stop_token await_resume() const noexcept { return token_; }
  235 + };
HITGNC   236 + 24 return awaiter{env_->stop_token};
  237 + }
  238 + else if constexpr (std::is_same_v<Tag, this_coro::frame_allocator_tag>)
  239 + {
HITGNC   240 + 8 BOOST_CAPY_ASSERT(env_);
  241 + struct awaiter
  242 + {
  243 + std::pmr::memory_resource* frame_allocator_;
HITGNC   244 + 6 bool await_ready() const noexcept { return true; }
  245 + void await_suspend(std::coroutine_handle<>) const noexcept { } // LCOV_EXCL_LINE await_ready() always true, never suspends
HITGNC   246 + 7 std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; }
  247 + };
HITGNC   248 + 8 return awaiter{env_->frame_allocator};
  249 + }
  250 + else
  251 + {
HITGNC   252 + 2506 return static_cast<Derived*>(this)->transform_awaitable(
HITGNC   253 + 107749 std::forward<T>(t));
  254 + }
  255 + }
  256 + };
  257 +
  258 + } // namespace capy
  259 + } // namespace boost
  260 +
  261 + #endif