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