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_allocator.hpp> 15   #include <boost/capy/ex/frame_allocator.hpp>
15 - #include <boost/capy/ex/recycling_memory_resource.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 <cstddef>  
20 - #include <cstring>  
21   #include <coroutine> 19   #include <coroutine>
22   #include <memory_resource> 20   #include <memory_resource>
23   #include <stop_token> 21   #include <stop_token>
24   #include <type_traits> 22   #include <type_traits>
25   23  
26   namespace boost { 24   namespace boost {
27   namespace capy { 25   namespace capy {
28   26  
29   /** 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.
30   28  
31   Inherit from this class to enable these capabilities in your coroutine: 29   Inherit from this class to enable these capabilities in your coroutine:
32   30  
33   1. **Frame allocation** — The mixin provides `operator new/delete` that 31   1. **Frame allocation** — The mixin provides `operator new/delete` that
34   use the thread-local frame allocator set by `run_async`. 32   use the thread-local frame allocator set by `run_async`.
35   33  
36   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`
37   containing the executor, stop token, and allocator for this coroutine. 35   containing the executor, stop token, and allocator for this coroutine.
38   36  
39   3. **Environment access** — Coroutine code can retrieve the environment 37   3. **Environment access** — Coroutine code can retrieve the environment
40   via `co_await this_coro::environment`, or individual fields via 38   via `co_await this_coro::environment`, or individual fields via
41   `co_await this_coro::executor`, `co_await this_coro::stop_token`, 39   `co_await this_coro::executor`, `co_await this_coro::stop_token`,
42 - and `co_await this_coro::allocator`. 40 + and `co_await this_coro::frame_allocator`.
43   41  
44   @tparam Derived The derived promise type (CRTP pattern). 42   @tparam Derived The derived promise type (CRTP pattern).
45   43  
46   @par Basic Usage 44   @par Basic Usage
47   45  
48   For coroutines that need to access their execution environment: 46   For coroutines that need to access their execution environment:
49   47  
50   @code 48   @code
51   struct my_task 49   struct my_task
52   { 50   {
53   struct promise_type : io_awaitable_promise_base<promise_type> 51   struct promise_type : io_awaitable_promise_base<promise_type>
54   { 52   {
55   my_task get_return_object(); 53   my_task get_return_object();
56   std::suspend_always initial_suspend() noexcept; 54   std::suspend_always initial_suspend() noexcept;
57   std::suspend_always final_suspend() noexcept; 55   std::suspend_always final_suspend() noexcept;
58   void return_void(); 56   void return_void();
59   void unhandled_exception(); 57   void unhandled_exception();
60   }; 58   };
61   59  
62   // ... awaitable interface ... 60   // ... awaitable interface ...
63   }; 61   };
64   62  
65   my_task example() 63   my_task example()
66   { 64   {
67   auto env = co_await this_coro::environment; 65   auto env = co_await this_coro::environment;
68 - // Access env->executor, env->stop_token, env->allocator 66 + // Access env->executor, env->stop_token, env->frame_allocator
69   67  
70   // Or use fine-grained accessors: 68   // Or use fine-grained accessors:
71   auto ex = co_await this_coro::executor; 69   auto ex = co_await this_coro::executor;
72   auto token = co_await this_coro::stop_token; 70   auto token = co_await this_coro::stop_token;
73 - auto* alloc = co_await this_coro::allocator; 71 + auto* alloc = co_await this_coro::frame_allocator;
74   } 72   }
75   @endcode 73   @endcode
76   74  
77   @par Custom Awaitable Transformation 75   @par Custom Awaitable Transformation
78   76  
79   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
80   logging), override `transform_awaitable` instead of `await_transform`: 78   logging), override `transform_awaitable` instead of `await_transform`:
81   79  
82   @code 80   @code
83   struct promise_type : io_awaitable_promise_base<promise_type> 81   struct promise_type : io_awaitable_promise_base<promise_type>
84   { 82   {
85   template<typename A> 83   template<typename A>
86   auto transform_awaitable(A&& a) 84   auto transform_awaitable(A&& a)
87   { 85   {
88   // Your custom transformation logic 86   // Your custom transformation logic
89   return std::forward<A>(a); 87   return std::forward<A>(a);
90   } 88   }
91   }; 89   };
92   @endcode 90   @endcode
93   91  
94   The mixin's `await_transform` intercepts @ref this_coro::environment_tag 92   The mixin's `await_transform` intercepts @ref this_coro::environment_tag
95   and the fine-grained tag types (@ref this_coro::executor_tag, 93   and the fine-grained tag types (@ref this_coro::executor_tag,
96   @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),
97   then delegates all other awaitables to your `transform_awaitable`. 95   then delegates all other awaitables to your `transform_awaitable`.
98   96  
99   @par Making Your Coroutine an IoAwaitable 97   @par Making Your Coroutine an IoAwaitable
100   98  
101   The mixin handles the "inside the coroutine" part—accessing the 99   The mixin handles the "inside the coroutine" part—accessing the
102   environment. To receive the environment when your coroutine is awaited 100   environment. To receive the environment when your coroutine is awaited
103   (satisfying @ref IoAwaitable), implement the `await_suspend` overload 101   (satisfying @ref IoAwaitable), implement the `await_suspend` overload
104   on your coroutine return type: 102   on your coroutine return type:
105   103  
106   @code 104   @code
107   struct my_task 105   struct my_task
108   { 106   {
109   struct promise_type : io_awaitable_promise_base<promise_type> { ... }; 107   struct promise_type : io_awaitable_promise_base<promise_type> { ... };
110   108  
111   std::coroutine_handle<promise_type> h_; 109   std::coroutine_handle<promise_type> h_;
112   110  
113   // IoAwaitable await_suspend receives and stores the environment 111   // IoAwaitable await_suspend receives and stores the environment
114   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)
115   { 113   {
116   h_.promise().set_environment(env); 114   h_.promise().set_environment(env);
117   // ... rest of suspend logic ... 115   // ... rest of suspend logic ...
118   } 116   }
119   }; 117   };
120   @endcode 118   @endcode
121   119  
122   @par Thread Safety 120   @par Thread Safety
123   The environment is stored during `await_suspend` and read during 121   The environment is stored during `await_suspend` and read during
124   `co_await this_coro::environment`. These occur on the same logical 122   `co_await this_coro::environment`. These occur on the same logical
125   thread of execution, so no synchronization is required. 123   thread of execution, so no synchronization is required.
126   124  
127   @see this_coro::environment, this_coro::executor, 125   @see this_coro::environment, this_coro::executor,
128 - this_coro::stop_token, this_coro::allocator 126 + this_coro::stop_token, this_coro::frame_allocator
129   @see io_env 127   @see io_env
130   @see IoAwaitable 128   @see IoAwaitable
131   */ 129   */
132   template<typename Derived> 130   template<typename Derived>
133   class io_awaitable_promise_base 131   class io_awaitable_promise_base
  132 + : public frame_alloc_mixin
134   { 133   {
135   io_env const* env_ = nullptr; 134   io_env const* env_ = nullptr;
136   mutable std::coroutine_handle<> cont_{std::noop_coroutine()}; 135   mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
137   136  
138 - /** Allocate a coroutine frame.  
139 -  
140 - Uses the thread-local frame allocator set by run_async.  
141 - Falls back to default memory resource if not set.  
142 - Stores the allocator pointer at the end of each frame for  
143 - correct deallocation even when TLS changes. Uses memcpy  
144 - to avoid alignment requirements on the trailing pointer.  
145 - Bypasses virtual dispatch for the recycling allocator.  
146 - */  
147 - static void* operator new(std::size_t size)  
DCB 148 - 4719 {  
149 - static auto* const rmr = get_recycling_memory_resource();  
DCB 150 - 4719  
151 - auto* mr = get_current_frame_allocator();  
DCB 152 - 4719 if(!mr)  
DCB 153 - 4719 mr = std::pmr::get_default_resource();  
DCB 154 - 2718  
155 - auto total = size + sizeof(std::pmr::memory_resource*);  
DCB 156 - 4719 void* raw;  
157 - if(mr == rmr)  
DCB 158 - 4719 raw = static_cast<recycling_memory_resource*>(mr)  
159 - ->allocate_fast(total, alignof(std::max_align_t));  
DCB 160 - 1988 else  
161 - raw = mr->allocate(total, alignof(std::max_align_t));  
DCB 162 - 2731 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));  
DCB 163 - 4719 return raw;  
DCB 164 - 4719 }  
165 -  
166 - /** Deallocate a coroutine frame.  
167 -  
168 - Reads the allocator pointer stored at the end of the frame  
169 - to ensure correct deallocation regardless of current TLS.  
170 - Bypasses virtual dispatch for the recycling allocator.  
171 - */  
172 - static void operator delete(void* ptr, std::size_t size) noexcept  
DCB 173 - 4719 {  
174 - static auto* const rmr = get_recycling_memory_resource();  
DCB 175 - 4719  
176 - std::pmr::memory_resource* mr;  
177 - std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));  
DCB 178 - 4719 auto total = size + sizeof(std::pmr::memory_resource*);  
DCB 179 - 4719 if(mr == rmr)  
DCB 180 - 4719 static_cast<recycling_memory_resource*>(mr)  
181 - ->deallocate_fast(ptr, total, alignof(std::max_align_t));  
DCB 182 - 1988 else  
183 - mr->deallocate(ptr, total, alignof(std::max_align_t));  
DCB 184 - 2731 }  
DCB 185 - 4719  
186   public: 137   public:
HITCBC 187   4719 ~io_awaitable_promise_base() 138   5089 ~io_awaitable_promise_base()
188   { 139   {
189 - // 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 190   4719 if(cont_ != std::noop_coroutine()) 147   5089 if(cont_ != std::noop_coroutine())
HITCBC 191   1 cont_.destroy(); 148   144 cont_.destroy();
HITCBC 192   4719 } 149   5089 }
193   150  
194   //---------------------------------------------------------- 151   //----------------------------------------------------------
195   // Continuation support 152   // Continuation support
196   //---------------------------------------------------------- 153   //----------------------------------------------------------
197   154  
198   /** Store the continuation to resume on completion. 155   /** Store the continuation to resume on completion.
199   156  
200   Call this from your coroutine type's `await_suspend` overload 157   Call this from your coroutine type's `await_suspend` overload
201   to set up the completion path. The `final_suspend` awaiter 158   to set up the completion path. The `final_suspend` awaiter
202   returns this handle via unconditional symmetric transfer. 159   returns this handle via unconditional symmetric transfer.
203   160  
204   @param cont The continuation to resume on completion. 161   @param cont The continuation to resume on completion.
205   */ 162   */
HITCBC 206   4638 void set_continuation(std::coroutine_handle<> cont) noexcept 163   5006 void set_continuation(std::coroutine_handle<> cont) noexcept
207   { 164   {
HITCBC 208   4638 cont_ = cont; 165   5006 cont_ = cont;
HITCBC 209   4638 } 166   5006 }
210   167  
211   /** Return and consume the stored continuation handle. 168   /** Return and consume the stored continuation handle.
212   169  
213   Resets the stored handle to `noop_coroutine()` so the 170   Resets the stored handle to `noop_coroutine()` so the
214   destructor will not double-destroy it. 171   destructor will not double-destroy it.
215   172  
216   @return The continuation for symmetric transfer. 173   @return The continuation for symmetric transfer.
217   */ 174   */
HITCBC 218   4695 std::coroutine_handle<> continuation() const noexcept 175   4922 std::coroutine_handle<> continuation() const noexcept
219   { 176   {
HITCBC 220   4695 return std::exchange(cont_, std::noop_coroutine()); 177   4922 return std::exchange(cont_, std::noop_coroutine());
221   } 178   }
222   179  
223   //---------------------------------------------------------- 180   //----------------------------------------------------------
224   // Environment support 181   // Environment support
225   //---------------------------------------------------------- 182   //----------------------------------------------------------
226   183  
227   /** Store a pointer to the execution environment. 184   /** Store a pointer to the execution environment.
228   185  
229   Call this from your coroutine type's `await_suspend` 186   Call this from your coroutine type's `await_suspend`
230   overload to make the environment available via 187   overload to make the environment available via
231   `co_await this_coro::environment`. The pointed-to 188   `co_await this_coro::environment`. The pointed-to
232   `io_env` must outlive this coroutine. 189   `io_env` must outlive this coroutine.
233   190  
234   @param env The environment to store. 191   @param env The environment to store.
235   */ 192   */
HITCBC 236   4716 void set_environment(io_env const* env) noexcept 193   5086 void set_environment(io_env const* env) noexcept
237   { 194   {
HITCBC 238   4716 env_ = env; 195   5086 env_ = env;
HITCBC 239   4716 } 196   5086 }
240   197  
241   /** Return the stored execution environment. 198   /** Return the stored execution environment.
242   199  
243   @return The environment. 200   @return The environment.
244   */ 201   */
HITCBC 245   15520 io_env const* environment() const noexcept 202   16683 io_env const* environment() const noexcept
246   { 203   {
HITCBC 247   15520 BOOST_CAPY_ASSERT(env_); 204   16683 BOOST_CAPY_ASSERT(env_);
HITCBC 248   15520 return env_; 205   16683 return env_;
249   } 206   }
250   207  
251   /** Transform an awaitable before co_await. 208   /** Transform an awaitable before co_await.
252   209  
253   Override this in your derived promise type to customize how 210   Override this in your derived promise type to customize how
254   awaitables are transformed. The default implementation passes 211   awaitables are transformed. The default implementation passes
255   the awaitable through unchanged. 212   the awaitable through unchanged.
256   213  
257   @param a The awaitable expression from `co_await a`. 214   @param a The awaitable expression from `co_await a`.
258   215  
259   @return The transformed awaitable. 216   @return The transformed awaitable.
260   */ 217   */
261   template<typename A> 218   template<typename A>
262   decltype(auto) transform_awaitable(A&& a) 219   decltype(auto) transform_awaitable(A&& a)
263   { 220   {
264   return std::forward<A>(a); 221   return std::forward<A>(a);
265   } 222   }
266   223  
267   /** Intercept co_await expressions. 224   /** Intercept co_await expressions.
268   225  
269   This function handles @ref this_coro::environment_tag and 226   This function handles @ref this_coro::environment_tag and
270   the fine-grained tags (@ref this_coro::executor_tag, 227   the fine-grained tags (@ref this_coro::executor_tag,
271   @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)
272   specially, returning an awaiter that yields the stored value. 229   specially, returning an awaiter that yields the stored value.
273   All other awaitables are delegated to @ref transform_awaitable. 230   All other awaitables are delegated to @ref transform_awaitable.
274   231  
275   @param t The awaited expression. 232   @param t The awaited expression.
276   233  
277   @return An awaiter for the expression. 234   @return An awaiter for the expression.
278   */ 235   */
279   template<typename T> 236   template<typename T>
HITCBC 280   8659 auto await_transform(T&& t) 237   9249 auto await_transform(T&& t)
281   { 238   {
282   using Tag = std::decay_t<T>; 239   using Tag = std::decay_t<T>;
283   240  
284   if constexpr (std::is_same_v<Tag, this_coro::environment_tag>) 241   if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
285   { 242   {
HITCBC 286   37 BOOST_CAPY_ASSERT(env_); 243   18 BOOST_CAPY_ASSERT(env_);
287   struct awaiter 244   struct awaiter
288   { 245   {
289   io_env const* env_; 246   io_env const* env_;
HITCBC 290   35 bool await_ready() const noexcept { return true; } 247   16 bool await_ready() const noexcept { return true; }
HITCBC 291   2 void await_suspend(std::coroutine_handle<>) const noexcept { } 248   2 void await_suspend(std::coroutine_handle<>) const noexcept { }
HITCBC 292   34 io_env const* await_resume() const noexcept { return env_; } 249   15 io_env const* await_resume() const noexcept { return env_; }
293   }; 250   };
HITCBC 294   37 return awaiter{env_}; 251   18 return awaiter{env_};
295   } 252   }
296   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>)
297   { 254   {
HITCBC 298   3 BOOST_CAPY_ASSERT(env_); 255   4 BOOST_CAPY_ASSERT(env_);
299   struct awaiter 256   struct awaiter
300   { 257   {
301   executor_ref executor_; 258   executor_ref executor_;
HITCBC 302   2 bool await_ready() const noexcept { return true; } 259   3 bool await_ready() const noexcept { return true; }
MISUIC 303   void await_suspend(std::coroutine_handle<>) const noexcept { } 260   void await_suspend(std::coroutine_handle<>) const noexcept { }
HITCBC 304   2 executor_ref await_resume() const noexcept { return executor_; } 261   3 executor_ref await_resume() const noexcept { return executor_; }
305   }; 262   };
HITCBC 306   3 return awaiter{env_->executor}; 263   4 return awaiter{env_->executor};
307   } 264   }
308   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>)
309   { 266   {
HITCBC 310   7 BOOST_CAPY_ASSERT(env_); 267   15 BOOST_CAPY_ASSERT(env_);
311   struct awaiter 268   struct awaiter
312   { 269   {
313   std::stop_token token_; 270   std::stop_token token_;
HITCBC 314   6 bool await_ready() const noexcept { return true; } 271   14 bool await_ready() const noexcept { return true; }
MISUBC 315   void await_suspend(std::coroutine_handle<>) const noexcept { } 272   void await_suspend(std::coroutine_handle<>) const noexcept { }
HITCBC 316   6 std::stop_token await_resume() const noexcept { return token_; } 273   14 std::stop_token await_resume() const noexcept { return token_; }
317   }; 274   };
HITCBC 318   7 return awaiter{env_->stop_token}; 275   15 return awaiter{env_->stop_token};
319   } 276   }
320   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>)
321   { 278   {
HITCBC 322   8 BOOST_CAPY_ASSERT(env_); 279   8 BOOST_CAPY_ASSERT(env_);
323   struct awaiter 280   struct awaiter
324   { 281   {
325   std::pmr::memory_resource* frame_allocator_; 282   std::pmr::memory_resource* frame_allocator_;
HITCBC 326   6 bool await_ready() const noexcept { return true; } 283   6 bool await_ready() const noexcept { return true; }
MISUBC 327   void await_suspend(std::coroutine_handle<>) const noexcept { } 284   void await_suspend(std::coroutine_handle<>) const noexcept { }
HITCBC 328   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_; }
329   }; 286   };
HITCBC 330   8 return awaiter{env_->frame_allocator}; 287   8 return awaiter{env_->frame_allocator};
331   } 288   }
332   else 289   else
333   { 290   {
HITCBC 334   6754 return static_cast<Derived*>(this)->transform_awaitable( 291   7012 return static_cast<Derived*>(this)->transform_awaitable(
HITCBC 335   8604 std::forward<T>(t)); 292   9204 std::forward<T>(t));
336   } 293   }
337   } 294   }
338   }; 295   };
339   296  
340   } // namespace capy 297   } // namespace capy
341   } // namespace boost 298   } // namespace boost
342   299  
343   #endif 300   #endif