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