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 an orphaned continuation, e.g. 140   // Abnormal teardown: destroy an orphaned continuation, e.g.
141   // a run_async trampoline when the task is destroyed before 141   // a run_async trampoline when the task is destroyed before
142   // reaching final_suspend. Callers must not destroy a task 142   // reaching final_suspend. Callers must not destroy a task
143   // via handle().destroy() while it is being awaited by a 143   // via handle().destroy() while it is being awaited by a
144   // parent coroutine: that puts cont_ under another owner 144   // parent coroutine: that puts cont_ under another owner
145   // and would produce a double-destroy from this branch. See 145   // and would produce a double-destroy from this branch. See
146   // task::handle() / quitter::handle() for the contract. 146   // task::handle() / quitter::handle() for the contract.
HITCBC 147   5065 if(cont_ != std::noop_coroutine()) 147   5065 if(cont_ != std::noop_coroutine())
HITCBC 148   164 cont_.destroy(); 148   141 cont_.destroy();
HITCBC 149   5065 } 149   5065 }
150   150  
151   //---------------------------------------------------------- 151   //----------------------------------------------------------
152   // Continuation support 152   // Continuation support
153   //---------------------------------------------------------- 153   //----------------------------------------------------------
154   154  
155   /** Store the continuation to resume on completion. 155   /** Store the continuation to resume on completion.
156   156  
157   Call this from your coroutine type's `await_suspend` overload 157   Call this from your coroutine type's `await_suspend` overload
158   to set up the completion path. The `final_suspend` awaiter 158   to set up the completion path. The `final_suspend` awaiter
159   returns this handle via unconditional symmetric transfer. 159   returns this handle via unconditional symmetric transfer.
160   160  
161   @param cont The continuation to resume on completion. 161   @param cont The continuation to resume on completion.
162   */ 162   */
HITCBC 163   4982 void set_continuation(std::coroutine_handle<> cont) noexcept 163   4982 void set_continuation(std::coroutine_handle<> cont) noexcept
164   { 164   {
HITCBC 165   4982 cont_ = cont; 165   4982 cont_ = cont;
HITCBC 166   4982 } 166   4982 }
167   167  
168   /** Return and consume the stored continuation handle. 168   /** Return and consume the stored continuation handle.
169   169  
170   Resets the stored handle to `noop_coroutine()` so the 170   Resets the stored handle to `noop_coroutine()` so the
171   destructor will not double-destroy it. 171   destructor will not double-destroy it.
172   172  
173   @return The continuation for symmetric transfer. 173   @return The continuation for symmetric transfer.
174   */ 174   */
HITCBC 175   4878 std::coroutine_handle<> continuation() const noexcept 175   4901 std::coroutine_handle<> continuation() const noexcept
176   { 176   {
HITCBC 177   4878 return std::exchange(cont_, std::noop_coroutine()); 177   4901 return std::exchange(cont_, std::noop_coroutine());
178   } 178   }
179   179  
180   //---------------------------------------------------------- 180   //----------------------------------------------------------
181   // Environment support 181   // Environment support
182   //---------------------------------------------------------- 182   //----------------------------------------------------------
183   183  
184   /** Store a pointer to the execution environment. 184   /** Store a pointer to the execution environment.
185   185  
186   Call this from your coroutine type's `await_suspend` 186   Call this from your coroutine type's `await_suspend`
187   overload to make the environment available via 187   overload to make the environment available via
188   `co_await this_coro::environment`. The pointed-to 188   `co_await this_coro::environment`. The pointed-to
189   `io_env` must outlive this coroutine. 189   `io_env` must outlive this coroutine.
190   190  
191   @param env The environment to store. 191   @param env The environment to store.
192   */ 192   */
HITCBC 193   5062 void set_environment(io_env const* env) noexcept 193   5062 void set_environment(io_env const* env) noexcept
194   { 194   {
HITCBC 195   5062 env_ = env; 195   5062 env_ = env;
HITCBC 196   5062 } 196   5062 }
197   197  
198   /** Return the stored execution environment. 198   /** Return the stored execution environment.
199   199  
200   @return The environment. 200   @return The environment.
201   */ 201   */
HITCBC 202   16603 io_env const* environment() const noexcept 202   16626 io_env const* environment() const noexcept
203   { 203   {
HITCBC 204   16603 BOOST_CAPY_ASSERT(env_); 204   16626 BOOST_CAPY_ASSERT(env_);
HITCBC 205   16603 return env_; 205   16626 return env_;
206   } 206   }
207   207  
208   /** Transform an awaitable before co_await. 208   /** Transform an awaitable before co_await.
209   209  
210   Override this in your derived promise type to customize how 210   Override this in your derived promise type to customize how
211   awaitables are transformed. The default implementation passes 211   awaitables are transformed. The default implementation passes
212   the awaitable through unchanged. 212   the awaitable through unchanged.
213   213  
214   @param a The awaitable expression from `co_await a`. 214   @param a The awaitable expression from `co_await a`.
215   215  
216   @return The transformed awaitable. 216   @return The transformed awaitable.
217   */ 217   */
218   template<typename A> 218   template<typename A>
219   decltype(auto) transform_awaitable(A&& a) 219   decltype(auto) transform_awaitable(A&& a)
220   { 220   {
221   return std::forward<A>(a); 221   return std::forward<A>(a);
222   } 222   }
223   223  
224   /** Intercept co_await expressions. 224   /** Intercept co_await expressions.
225   225  
226   This function handles @ref this_coro::environment_tag and 226   This function handles @ref this_coro::environment_tag and
227   the fine-grained tags (@ref this_coro::executor_tag, 227   the fine-grained tags (@ref this_coro::executor_tag,
228   @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)
229   specially, returning an awaiter that yields the stored value. 229   specially, returning an awaiter that yields the stored value.
230   All other awaitables are delegated to @ref transform_awaitable. 230   All other awaitables are delegated to @ref transform_awaitable.
231   231  
232   @param t The awaited expression. 232   @param t The awaited expression.
233   233  
234   @return An awaiter for the expression. 234   @return An awaiter for the expression.
235   */ 235   */
236   template<typename T> 236   template<typename T>
HITCBC 237   9225 auto await_transform(T&& t) 237   9225 auto await_transform(T&& t)
238   { 238   {
239   using Tag = std::decay_t<T>; 239   using Tag = std::decay_t<T>;
240   240  
241   if constexpr (std::is_same_v<Tag, this_coro::environment_tag>) 241   if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
242   { 242   {
HITCBC 243   18 BOOST_CAPY_ASSERT(env_); 243   18 BOOST_CAPY_ASSERT(env_);
244   struct awaiter 244   struct awaiter
245   { 245   {
246   io_env const* env_; 246   io_env const* env_;
HITCBC 247   16 bool await_ready() const noexcept { return true; } 247   16 bool await_ready() const noexcept { return true; }
HITCBC 248   2 void await_suspend(std::coroutine_handle<>) const noexcept { } 248   2 void await_suspend(std::coroutine_handle<>) const noexcept { }
HITCBC 249   15 io_env const* await_resume() const noexcept { return env_; } 249   15 io_env const* await_resume() const noexcept { return env_; }
250   }; 250   };
HITCBC 251   18 return awaiter{env_}; 251   18 return awaiter{env_};
252   } 252   }
253   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>)
254   { 254   {
HITCBC 255   4 BOOST_CAPY_ASSERT(env_); 255   4 BOOST_CAPY_ASSERT(env_);
256   struct awaiter 256   struct awaiter
257   { 257   {
258   executor_ref executor_; 258   executor_ref executor_;
HITCBC 259   3 bool await_ready() const noexcept { return true; } 259   3 bool await_ready() const noexcept { return true; }
MISUBC 260   void await_suspend(std::coroutine_handle<>) const noexcept { } 260   void await_suspend(std::coroutine_handle<>) const noexcept { }
HITCBC 261   3 executor_ref await_resume() const noexcept { return executor_; } 261   3 executor_ref await_resume() const noexcept { return executor_; }
262   }; 262   };
HITCBC 263   4 return awaiter{env_->executor}; 263   4 return awaiter{env_->executor};
264   } 264   }
265   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>)
266   { 266   {
HITCBC 267   15 BOOST_CAPY_ASSERT(env_); 267   15 BOOST_CAPY_ASSERT(env_);
268   struct awaiter 268   struct awaiter
269   { 269   {
270   std::stop_token token_; 270   std::stop_token token_;
HITCBC 271   14 bool await_ready() const noexcept { return true; } 271   14 bool await_ready() const noexcept { return true; }
MISUBC 272   void await_suspend(std::coroutine_handle<>) const noexcept { } 272   void await_suspend(std::coroutine_handle<>) const noexcept { }
HITCBC 273   14 std::stop_token await_resume() const noexcept { return token_; } 273   14 std::stop_token await_resume() const noexcept { return token_; }
274   }; 274   };
HITCBC 275   15 return awaiter{env_->stop_token}; 275   15 return awaiter{env_->stop_token};
276   } 276   }
277   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>)
278   { 278   {
HITCBC 279   8 BOOST_CAPY_ASSERT(env_); 279   8 BOOST_CAPY_ASSERT(env_);
280   struct awaiter 280   struct awaiter
281   { 281   {
282   std::pmr::memory_resource* frame_allocator_; 282   std::pmr::memory_resource* frame_allocator_;
HITCBC 283   6 bool await_ready() const noexcept { return true; } 283   6 bool await_ready() const noexcept { return true; }
MISUBC 284   void await_suspend(std::coroutine_handle<>) const noexcept { } 284   void await_suspend(std::coroutine_handle<>) const noexcept { }
HITCBC 285   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_; }
286   }; 286   };
HITCBC 287   8 return awaiter{env_->frame_allocator}; 287   8 return awaiter{env_->frame_allocator};
288   } 288   }
289   else 289   else
290   { 290   {
HITCBC 291   7000 return static_cast<Derived*>(this)->transform_awaitable( 291   7000 return static_cast<Derived*>(this)->transform_awaitable(
HITCBC 292   9180 std::forward<T>(t)); 292   9180 std::forward<T>(t));
293   } 293   }
294   } 294   }
295   }; 295   };
296   296  
297   } // namespace capy 297   } // namespace capy
298   } // namespace boost 298   } // namespace boost
299   299  
300   #endif 300   #endif