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