0.00% Lines (0/0)
0.00% Functions (0/0)
| 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_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/abandon.hpp> | ||||||
| 16 | - | #include <boost/capy/ex/frame_alloc_mixin.hpp> | ||||||
| 17 | - | #include <boost/capy/ex/frame_allocator.hpp> | ||||||
| 18 | - | #include <boost/capy/ex/io_env.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 `io_env` | ||||||
| 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 an IoAwaitable | ||||||
| 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 IoAwaitable), 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 io_env | ||||||
| 102 | - | @see IoAwaitable | ||||||
| 103 | - | */ | ||||||
| 104 | - | template<typename Derived> | ||||||
| 105 | - | class io_awaitable_promise_base | ||||||
| 106 | - | : public frame_alloc_mixin | ||||||
| 107 | - | { | ||||||
| 108 | - | io_env 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 | - | */ | ||||||
| DCB | 124 | - | 107616 | void set_continuation(std::coroutine_handle<> cont) noexcept | ||||
| 125 | - | { | ||||||
| DCB | 126 | - | 107616 | cont_ = cont; | ||||
| DCB | 127 | - | 107616 | } | ||||
| 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 | - | */ | ||||||
| DCB | 136 | - | 5064 | std::coroutine_handle<> continuation() const noexcept | ||||
| 137 | - | { | ||||||
| DCB | 138 | - | 5064 | 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 | - | `io_env` must outlive this coroutine. | ||||||
| 151 | - | |||||||
| 152 | - | @param env The environment to store. | ||||||
| 153 | - | */ | ||||||
| DCB | 154 | - | 107702 | void set_environment(io_env const* env) noexcept | ||||
| 155 | - | { | ||||||
| DCB | 156 | - | 107702 | env_ = env; | ||||
| DCB | 157 | - | 107702 | } | ||||
| 158 | - | |||||||
| 159 | - | /** Return the stored execution environment. | ||||||
| 160 | - | |||||||
| 161 | - | @return The environment. | ||||||
| 162 | - | */ | ||||||
| DCB | 163 | - | 221261 | io_env const* environment() const noexcept | ||||
| 164 | - | { | ||||||
| DCB | 165 | - | 221261 | BOOST_CAPY_ASSERT(env_); | ||||
| DCB | 166 | - | 221261 | 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> | ||||||
| DCB | 180 | - | 1 | decltype(auto) transform_awaitable(A&& a) | ||||
| 181 | - | { | ||||||
| DCB | 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> | ||||||
| DCB | 198 | - | 107798 | 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 | - | { | ||||||
| DCB | 204 | - | 24 | BOOST_CAPY_ASSERT(env_); | ||||
| 205 | - | struct awaiter | ||||||
| 206 | - | { | ||||||
| 207 | - | io_env const* env_; | ||||||
| DCB | 208 | - | 22 | bool await_ready() const noexcept { return true; } | ||||
| DCB | 209 | - | 2 | void await_suspend(std::coroutine_handle<>) const noexcept { } | ||||
| DCB | 210 | - | 21 | io_env const* await_resume() const noexcept { return env_; } | ||||
| 211 | - | }; | ||||||
| DCB | 212 | - | 24 | return awaiter{env_}; | ||||
| 213 | - | } | ||||||
| 214 | - | else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>) | ||||||
| 215 | - | { | ||||||
| DCB | 216 | - | 4 | BOOST_CAPY_ASSERT(env_); | ||||
| 217 | - | struct awaiter | ||||||
| 218 | - | { | ||||||
| 219 | - | executor_ref executor_; | ||||||
| DCB | 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 | ||||||
| DCB | 222 | - | 3 | executor_ref await_resume() const noexcept { return executor_; } | ||||
| 223 | - | }; | ||||||
| DCB | 224 | - | 4 | return awaiter{env_->executor}; | ||||
| 225 | - | } | ||||||
| 226 | - | else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>) | ||||||
| 227 | - | { | ||||||
| DCB | 228 | - | 24 | BOOST_CAPY_ASSERT(env_); | ||||
| 229 | - | struct awaiter | ||||||
| 230 | - | { | ||||||
| 231 | - | std::stop_token token_; | ||||||
| DCB | 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 | ||||||
| DCB | 234 | - | 23 | std::stop_token await_resume() const noexcept { return token_; } | ||||
| 235 | - | }; | ||||||
| DCB | 236 | - | 24 | return awaiter{env_->stop_token}; | ||||
| 237 | - | } | ||||||
| 238 | - | else if constexpr (std::is_same_v<Tag, this_coro::frame_allocator_tag>) | ||||||
| 239 | - | { | ||||||
| DCB | 240 | - | 8 | BOOST_CAPY_ASSERT(env_); | ||||
| 241 | - | struct awaiter | ||||||
| 242 | - | { | ||||||
| 243 | - | std::pmr::memory_resource* frame_allocator_; | ||||||
| DCB | 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 | ||||||
| DCB | 246 | - | 7 | std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; } | ||||
| 247 | - | }; | ||||||
| DCB | 248 | - | 8 | return awaiter{env_->frame_allocator}; | ||||
| 249 | - | } | ||||||
| 250 | - | else | ||||||
| 251 | - | { | ||||||
| DCB | 252 | - | 2506 | return static_cast<Derived*>(this)->transform_awaitable( | ||||
| DCB | 253 | - | 107738 | std::forward<T>(t)); | ||||
| 254 | - | } | ||||||
| 255 | - | } | ||||||
| 256 | - | }; | ||||||
| 257 | - | |||||||
| 258 | - | } // namespace capy | ||||||
| 259 | - | } // namespace boost | ||||||
| 260 | - | |||||||
| 261 | - | #endif | ||||||