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