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