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