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