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