100.00% Lines (95/95) 100.00% Functions (28/28)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
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_QUITTER_HPP 10   #ifndef BOOST_CAPY_QUITTER_HPP
11   #define BOOST_CAPY_QUITTER_HPP 11   #define BOOST_CAPY_QUITTER_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/detail/stop_requested_exception.hpp> 14   #include <boost/capy/detail/stop_requested_exception.hpp>
15   #include <boost/capy/concept/executor.hpp> 15   #include <boost/capy/concept/executor.hpp>
16   #include <boost/capy/concept/io_awaitable.hpp> 16   #include <boost/capy/concept/io_awaitable.hpp>
17   #include <boost/capy/ex/io_awaitable_promise_base.hpp> 17   #include <boost/capy/ex/io_awaitable_promise_base.hpp>
18   #include <boost/capy/ex/io_env.hpp> 18   #include <boost/capy/ex/io_env.hpp>
19   #include <boost/capy/ex/frame_allocator.hpp> 19   #include <boost/capy/ex/frame_allocator.hpp>
20   #include <boost/capy/detail/await_suspend_helper.hpp> 20   #include <boost/capy/detail/await_suspend_helper.hpp>
21   21  
22   #include <exception> 22   #include <exception>
23   #include <optional> 23   #include <optional>
24   #include <type_traits> 24   #include <type_traits>
25   #include <utility> 25   #include <utility>
26   26  
27   /* Stop-aware coroutine task. 27   /* Stop-aware coroutine task.
28   28  
29   quitter<T> is identical to task<T> except that when the stop token 29   quitter<T> is identical to task<T> except that when the stop token
30   is triggered, the coroutine body never sees the cancellation. The 30   is triggered, the coroutine body never sees the cancellation. The
31   promise intercepts it on resume (in transform_awaiter::await_resume) 31   promise intercepts it on resume (in transform_awaiter::await_resume)
32   and throws a sentinel exception that unwinds through RAII destructors 32   and throws a sentinel exception that unwinds through RAII destructors
33   to final_suspend. The parent sees a "stopped" completion. 33   to final_suspend. The parent sees a "stopped" completion.
34   34  
35   See doc/quitter.md for the full design rationale. */ 35   See doc/quitter.md for the full design rationale. */
36   36  
37   namespace boost { 37   namespace boost {
38   namespace capy { 38   namespace capy {
39   39  
40   namespace detail { 40   namespace detail {
41   41  
42   // Reuse the same return-value storage as task<T>. 42   // Reuse the same return-value storage as task<T>.
43   // task_return_base is defined in task.hpp, but quitter needs its own 43   // task_return_base is defined in task.hpp, but quitter needs its own
44   // copy to avoid a header dependency on task.hpp. 44   // copy to avoid a header dependency on task.hpp.
45   template<typename T> 45   template<typename T>
46   struct quitter_return_base 46   struct quitter_return_base
47   { 47   {
48   std::optional<T> result_; 48   std::optional<T> result_;
49   49  
HITCBC 50   11 void return_value(T value) 50   11 void return_value(T value)
51   { 51   {
HITCBC 52   11 result_ = std::move(value); 52   11 result_ = std::move(value);
HITCBC 53   11 } 53   11 }
54   54  
HITCBC 55   5 T&& result() noexcept 55   5 T&& result() noexcept
56   { 56   {
HITCBC 57   5 return std::move(*result_); 57   5 return std::move(*result_);
58   } 58   }
59   }; 59   };
60   60  
61   template<> 61   template<>
62   struct quitter_return_base<void> 62   struct quitter_return_base<void>
63   { 63   {
HITCBC 64   2 void return_void() 64   2 void return_void()
65   { 65   {
HITCBC 66   2 } 66   2 }
67   }; 67   };
68   68  
69   } // namespace detail 69   } // namespace detail
70   70  
71   /** Defers a coroutine body until awaited, then unwinds it early on a stop request. 71   /** Defers a coroutine body until awaited, then unwinds it early on a stop request.
72   72  
73   When the stop token is triggered, the next `co_await` inside the 73   When the stop token is triggered, the next `co_await` inside the
74   coroutine short-circuits: the body never sees the result and RAII 74   coroutine short-circuits: the body never sees the result and RAII
75   destructors run normally. The parent observes a "stopped" 75   destructors run normally. The parent observes a "stopped"
76   completion via @ref promise_type::stopped. 76   completion via @ref promise_type::stopped.
77   77  
78   Everything else — frame allocation, environment propagation, 78   Everything else — frame allocation, environment propagation,
79   symmetric transfer, move semantics — is identical to @ref task. 79   symmetric transfer, move semantics — is identical to @ref task.
80   80  
81   @par Await-effects 81   @par Await-effects
82   82  
83   Let `q` be a `quitter<T>`. `co_await q` always suspends the awaiting 83   Let `q` be a `quitter<T>`. `co_await q` always suspends the awaiting
84   coroutine, then transfers control directly into the quitter's 84   coroutine, then transfers control directly into the quitter's
85   coroutine body on the current thread; no executor operation is 85   coroutine body on the current thread; no executor operation is
86   posted. The quitter records the caller's environment (executor, stop 86   posted. The quitter records the caller's environment (executor, stop
87   token, and frame allocator) by pointer rather than copying it. It 87   token, and frame allocator) by pointer rather than copying it. It
88   propagates that environment to every `co_await` inside the body. 88   propagates that environment to every `co_await` inside the body.
89   89  
90   Unlike @ref task, the stop token is checked at every point where the 90   Unlike @ref task, the stop token is checked at every point where the
91   body would resume. Those points are before the body's first 91   body would resume. Those points are before the body's first
92   statement, and again each time an awaited operation resumes it. If a 92   statement, and again each time an awaited operation resumes it. If a
93   stop request is pending, the body is not resumed. An internal 93   stop request is pending, the body is not resumed. An internal
94   sentinel exception unwinds it instead, so RAII destructors run, and 94   sentinel exception unwinds it instead, so RAII destructors run, and
95   the coroutine completes as stopped. 95   the coroutine completes as stopped.
96   96  
97   The body runs until it returns, exits via an exception, or is unwound 97   The body runs until it returns, exits via an exception, or is unwound
98   by a stop request. Control then transfers directly back to the 98   by a stop request. Control then transfers directly back to the
99   awaiting coroutine, again without an executor operation. 99   awaiting coroutine, again without an executor operation.
100   100  
101   @par Await-returns 101   @par Await-returns
102   The value the body passed to `co_return`, moved out of the quitter, 102   The value the body passed to `co_return`, moved out of the quitter,
103   or nothing when `T` is `void`. 103   or nothing when `T` is `void`.
104   104  
105   If the body exits via an unhandled exception, that exception is 105   If the body exits via an unhandled exception, that exception is
106   rethrown instead. 106   rethrown instead.
107   107  
108   If the coroutine completed as stopped, the internal sentinel 108   If the coroutine completed as stopped, the internal sentinel
109   exception is thrown instead of await-returning. Awaiting a stopped 109   exception is thrown instead of await-returning. Awaiting a stopped
110   `quitter` from another `quitter` therefore stops that one too. A 110   `quitter` from another `quitter` therefore stops that one too. A
111   @ref task awaiting it sees the sentinel as an unhandled exception in 111   @ref task awaiting it sees the sentinel as an unhandled exception in
112   its own body. When a quitter is started by `run_async`, a stopped 112   its own body. When a quitter is started by `run_async`, a stopped
113   completion reaches the error handler as the sentinel 113   completion reaches the error handler as the sentinel
114   `std::exception_ptr`, not the value handler. 114   `std::exception_ptr`, not the value handler.
115   115  
116   @par Await-postcondition 116   @par Await-postcondition
117   The quitter's coroutine has run to completion and is suspended at its 117   The quitter's coroutine has run to completion and is suspended at its
118   final suspend point; the body's RAII destructors have run. Exactly 118   final suspend point; the body's RAII destructors have run. Exactly
119   one of the following holds: the body returned a value; the body 119   one of the following holds: the body returned a value; the body
120   exited via an exception; or `handle().promise().stopped()` returns 120   exited via an exception; or `handle().promise().stopped()` returns
121   `true`. When the body returned a value, the await moved it out, so a 121   `true`. When the body returned a value, the await moved it out, so a
122   quitter must not be awaited twice. 122   quitter must not be awaited twice.
123   123  
124   @par Remarks 124   @par Remarks
125   Supports _IoAwaitable cancellation_. 125   Supports _IoAwaitable cancellation_.
126   126  
127   @tparam T The result type. Use `quitter<>` for `quitter<void>`. 127   @tparam T The result type. Use `quitter<>` for `quitter<void>`.
128   128  
129   @see task, IoRunnable, IoAwaitable 129   @see task, IoRunnable, IoAwaitable
130   */ 130   */
131   template<typename T = void> 131   template<typename T = void>
132   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 132   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
133   quitter 133   quitter
134   { 134   {
135   /** Stores `quitter<T>`'s result and unwinds the body when the stop token fires. 135   /** Stores `quitter<T>`'s result and unwinds the body when the stop token fires.
136   136  
137   This is the promise object the compiler associates with a 137   This is the promise object the compiler associates with a
138   `quitter<T>` coroutine. It satisfies the coroutine promise 138   `quitter<T>` coroutine. It satisfies the coroutine promise
139   requirements and participates in the I/O awaitable protocol via 139   requirements and participates in the I/O awaitable protocol via
140   @ref io_awaitable_promise_base. Unlike @ref task::promise_type, 140   @ref io_awaitable_promise_base. Unlike @ref task::promise_type,
141   its `transform_awaitable` checks the stop token before each 141   its `transform_awaitable` checks the stop token before each
142   awaited result reaches the body. A pending stop request throws an 142   awaited result reaches the body. A pending stop request throws an
143   internal sentinel exception that unwinds to a "stopped" 143   internal sentinel exception that unwinds to a "stopped"
144   completion. It is part of the coroutine machinery and is not 144   completion. It is part of the coroutine machinery and is not
145   intended to be used directly by callers. 145   intended to be used directly by callers.
146   146  
147   Result storage and `return_value`/`return_void` are provided by 147   Result storage and `return_value`/`return_void` are provided by
148   `detail::quitter_return_base<T>`. 148   `detail::quitter_return_base<T>`.
149   149  
150   @see io_awaitable_promise_base, IoRunnable 150   @see io_awaitable_promise_base, IoRunnable
151   */ 151   */
152   struct promise_type 152   struct promise_type
153   : io_awaitable_promise_base<promise_type> 153   : io_awaitable_promise_base<promise_type>
154   , detail::quitter_return_base<T> 154   , detail::quitter_return_base<T>
155   { 155   {
156   private: 156   private:
157   friend quitter; 157   friend quitter;
158   158  
159   enum class completion { running, value, exception, stopped }; 159   enum class completion { running, value, exception, stopped };
160   160  
161   union { std::exception_ptr ep_; }; 161   union { std::exception_ptr ep_; };
162   completion state_; 162   completion state_;
163   163  
164   public: 164   public:
165   /// Construct the promise in the running state. 165   /// Construct the promise in the running state.
HITCBC 166   33 promise_type() noexcept 166   33 promise_type() noexcept
HITCBC 167   33 : state_(completion::running) 167   33 : state_(completion::running)
168   { 168   {
HITCBC 169   33 } 169   33 }
170   170  
171   /// Destroy the promise, releasing any stored exception. 171   /// Destroy the promise, releasing any stored exception.
HITCBC 172   33 ~promise_type() 172   33 ~promise_type()
173   { 173   {
HITCBC 174   33 if(state_ == completion::exception || 174   33 if(state_ == completion::exception ||
HITCBC 175   29 state_ == completion::stopped) 175   29 state_ == completion::stopped)
HITCBC 176   20 ep_.~exception_ptr(); 176   20 ep_.~exception_ptr();
HITCBC 177   33 } 177   33 }
178   178  
179   /** Return a non-null exception_ptr when the coroutine threw 179   /** Return a non-null exception_ptr when the coroutine threw
180   or was stopped. 180   or was stopped.
181   181  
182   Stopped quitters report the sentinel 182   Stopped quitters report the sentinel
183   stop_requested_exception so that run_async routes to 183   stop_requested_exception so that run_async routes to
184   the error handler instead of accessing a non-existent 184   the error handler instead of accessing a non-existent
185   result. 185   result.
186   186  
187   @return The stored exception if the coroutine exited via an 187   @return The stored exception if the coroutine exited via an
188   exception or was stopped, otherwise a null 188   exception or was stopped, otherwise a null
189   `std::exception_ptr`. 189   `std::exception_ptr`.
190   */ 190   */
HITCBC 191   26 std::exception_ptr exception() const noexcept 191   26 std::exception_ptr exception() const noexcept
192   { 192   {
HITCBC 193   26 if(state_ == completion::exception || 193   26 if(state_ == completion::exception ||
HITCBC 194   20 state_ == completion::stopped) 194   20 state_ == completion::stopped)
HITCBC 195   20 return ep_; 195   20 return ep_;
HITCBC 196   6 return {}; 196   6 return {};
197   } 197   }
198   198  
199   /** True when the coroutine was stopped via the stop token. 199   /** True when the coroutine was stopped via the stop token.
200   200  
201   @return `true` if the body was unwound by a stop request; 201   @return `true` if the body was unwound by a stop request;
202   `false` if it returned a value or exited via any other 202   `false` if it returned a value or exited via any other
203   exception. 203   exception.
204   */ 204   */
HITCBC 205   12 bool stopped() const noexcept 205   12 bool stopped() const noexcept
206   { 206   {
HITCBC 207   12 return state_ == completion::stopped; 207   12 return state_ == completion::stopped;
208   } 208   }
209   209  
210   /** Return the owning `quitter` for this coroutine. 210   /** Return the owning `quitter` for this coroutine.
211   211  
212   Called by the compiler to produce the object returned to the 212   Called by the compiler to produce the object returned to the
213   caller when the coroutine is created. 213   caller when the coroutine is created.
214   214  
215   @return A `quitter` owning the coroutine frame. 215   @return A `quitter` owning the coroutine frame.
216   */ 216   */
HITCBC 217   33 quitter get_return_object() 217   33 quitter get_return_object()
218   { 218   {
219   return quitter{ 219   return quitter{
HITCBC 220   33 std::coroutine_handle<promise_type>::from_promise(*this)}; 220   33 std::coroutine_handle<promise_type>::from_promise(*this)};
221   } 221   }
222   222  
223   /** Return the initial-suspend awaiter. 223   /** Return the initial-suspend awaiter.
224   224  
225   The coroutine always suspends at the initial suspend point, 225   The coroutine always suspends at the initial suspend point,
226   so the body does not start until the quitter is awaited. When 226   so the body does not start until the quitter is awaited. When
227   the body is resumed, the awaiter restores the thread-local 227   the body is resumed, the awaiter restores the thread-local
228   frame allocator. It then throws the internal sentinel 228   frame allocator. It then throws the internal sentinel
229   exception if stop is already requested, so the body never 229   exception if stop is already requested, so the body never
230   runs and the coroutine completes as stopped. 230   runs and the coroutine completes as stopped.
231   231  
232   @return An awaiter that suspends unconditionally. 232   @return An awaiter that suspends unconditionally.
233   */ 233   */
HITCBC 234   33 auto initial_suspend() noexcept 234   33 auto initial_suspend() noexcept
235   { 235   {
236   struct awaiter 236   struct awaiter
237   { 237   {
238   promise_type* p_; 238   promise_type* p_;
239   239  
HITCBC 240   33 bool await_ready() const noexcept 240   33 bool await_ready() const noexcept
241   { 241   {
HITCBC 242   33 return false; 242   33 return false;
243   } 243   }
244   244  
HITCBC 245   33 void await_suspend(std::coroutine_handle<>) const noexcept 245   33 void await_suspend(std::coroutine_handle<>) const noexcept
246   { 246   {
HITCBC 247   33 } 247   33 }
248   248  
249   // Potentially-throwing: checks the stop token before 249   // Potentially-throwing: checks the stop token before
250   // the coroutine body executes its first statement. 250   // the coroutine body executes its first statement.
HITCBC 251   33 void await_resume() const 251   33 void await_resume() const
252   { 252   {
HITCBC 253   33 set_current_frame_allocator( 253   33 set_current_frame_allocator(
HITCBC 254   33 p_->environment()->frame_allocator); 254   33 p_->environment()->frame_allocator);
HITCBC 255   33 if(p_->environment()->stop_token.stop_requested()) 255   33 if(p_->environment()->stop_token.stop_requested())
HITCBC 256   3 throw detail::stop_requested_exception{}; 256   3 throw detail::stop_requested_exception{};
HITCBC 257   30 } 257   30 }
258   }; 258   };
HITCBC 259   33 return awaiter{this}; 259   33 return awaiter{this};
260   } 260   }
261   261  
262   /** Return the final-suspend awaiter. 262   /** Return the final-suspend awaiter.
263   263  
264   The coroutine always suspends at the final suspend point. The 264   The coroutine always suspends at the final suspend point. The
265   awaiter's `await_suspend` performs symmetric transfer to the 265   awaiter's `await_suspend` performs symmetric transfer to the
266   stored continuation, resuming the awaiting coroutine. 266   stored continuation, resuming the awaiting coroutine.
267   267  
268   @return An awaiter that suspends and transfers to the 268   @return An awaiter that suspends and transfers to the
269   continuation. 269   continuation.
270   */ 270   */
HITCBC 271   33 auto final_suspend() noexcept 271   33 auto final_suspend() noexcept
272   { 272   {
273   struct awaiter 273   struct awaiter
274   { 274   {
275   promise_type* p_; 275   promise_type* p_;
276   276  
HITCBC 277   33 bool await_ready() const noexcept 277   33 bool await_ready() const noexcept
278   { 278   {
HITCBC 279   33 return false; 279   33 return false;
280   } 280   }
281   281  
HITCBC 282   33 std::coroutine_handle<> await_suspend( 282   33 std::coroutine_handle<> await_suspend(
283   std::coroutine_handle<>) const noexcept 283   std::coroutine_handle<>) const noexcept
284   { 284   {
HITCBC 285   33 return p_->continuation(); 285   33 return p_->continuation();
286   } 286   }
287   287  
288   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 288   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
289   }; 289   };
HITCBC 290   33 return awaiter{this}; 290   33 return awaiter{this};
291   } 291   }
292   292  
293   /** Capture the in-flight exception from the coroutine body. 293   /** Capture the in-flight exception from the coroutine body.
294   294  
295   Called by the compiler when the coroutine body exits via an 295   Called by the compiler when the coroutine body exits via an
296   unhandled exception. The internal stop sentinel is recorded as 296   unhandled exception. The internal stop sentinel is recorded as
297   a stopped completion; any other exception is recorded as an 297   a stopped completion; any other exception is recorded as an
298   exception completion. The stored exception is surfaced (or 298   exception completion. The stored exception is surfaced (or
299   routed to the error handler) when the quitter is awaited or run. 299   routed to the error handler) when the quitter is awaited or run.
300   */ 300   */
HITCBC 301   20 void unhandled_exception() 301   20 void unhandled_exception()
302   { 302   {
303   try 303   try
304   { 304   {
HITCBC 305   20 throw; 305   20 throw;
306   } 306   }
HITCBC 307   20 catch(detail::stop_requested_exception const&) 307   20 catch(detail::stop_requested_exception const&)
308   { 308   {
309   // Store the exception_ptr so that run_async's 309   // Store the exception_ptr so that run_async's
310   // invoke_impl routes to the error handler 310   // invoke_impl routes to the error handler
311   // instead of accessing a non-existent result. 311   // instead of accessing a non-existent result.
HITCBC 312   16 new (&ep_) std::exception_ptr( 312   16 new (&ep_) std::exception_ptr(
313   std::current_exception()); 313   std::current_exception());
HITCBC 314   16 state_ = completion::stopped; 314   16 state_ = completion::stopped;
315   } 315   }
HITCBC 316   4 catch(...) 316   4 catch(...)
317   { 317   {
HITCBC 318   4 new (&ep_) std::exception_ptr( 318   4 new (&ep_) std::exception_ptr(
319   std::current_exception()); 319   std::current_exception());
HITCBC 320   4 state_ = completion::exception; 320   4 state_ = completion::exception;
321   } 321   }
HITCBC 322   20 } 322   20 }
323   323  
324   //------------------------------------------------------ 324   //------------------------------------------------------
325   // transform_awaitable — the key difference from task<T> 325   // transform_awaitable — the key difference from task<T>
326   //------------------------------------------------------ 326   //------------------------------------------------------
327   327  
328   /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable. 328   /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
329   329  
330   Forwards the environment to the inner awaitable's 330   Forwards the environment to the inner awaitable's
331   environment-taking `await_suspend` and restores the 331   environment-taking `await_suspend` and restores the
332   thread-local frame allocator before the body resumes. Unlike 332   thread-local frame allocator before the body resumes. Unlike
333   `task`'s, it also checks the stop token on resumption. A 333   `task`'s, it also checks the stop token on resumption. A
334   pending stop request throws the internal sentinel, so the body 334   pending stop request throws the internal sentinel, so the body
335   unwinds before it observes the I/O result. 335   unwinds before it observes the I/O result.
336   336  
337   @tparam Awaitable The awaitable being transformed. 337   @tparam Awaitable The awaitable being transformed.
338   */ 338   */
339   template<class Awaitable> 339   template<class Awaitable>
340   struct transform_awaiter 340   struct transform_awaiter
341   { 341   {
342   /// The wrapped awaitable, decayed and stored by value. 342   /// The wrapped awaitable, decayed and stored by value.
343   std::decay_t<Awaitable> a_; 343   std::decay_t<Awaitable> a_;
344   344  
345   /// The promise of the coroutine performing the `co_await`. 345   /// The promise of the coroutine performing the `co_await`.
346   promise_type* p_; 346   promise_type* p_;
347   347  
348   /** Report whether the wrapped awaitable is already complete. 348   /** Report whether the wrapped awaitable is already complete.
349   349  
350   The stop token is not checked here. A stop request that 350   The stop token is not checked here. A stop request that
351   arrives before an already-complete operation is observed by 351   arrives before an already-complete operation is observed by
352   @ref await_resume, which runs in either case. 352   @ref await_resume, which runs in either case.
353   353  
354   @return The wrapped awaitable's own `await_ready` result: 354   @return The wrapped awaitable's own `await_ready` result:
355   `true` if no suspension is needed. 355   `true` if no suspension is needed.
356   */ 356   */
HITCBC 357   21 bool await_ready() noexcept 357   21 bool await_ready() noexcept
358   { 358   {
HITCBC 359   21 return a_.await_ready(); 359   21 return a_.await_ready();
360   } 360   }
361   361  
362   /** Restore the frame allocator, check for stop, then resume the 362   /** Restore the frame allocator, check for stop, then resume the
363   wrapped awaitable. 363   wrapped awaitable.
364   364  
365   Reinstalls the thread-local frame allocator from the stored 365   Reinstalls the thread-local frame allocator from the stored
366   environment, then reads the environment's stop token. If a 366   environment, then reads the environment's stop token. If a
367   stop request is pending, the internal sentinel exception is 367   stop request is pending, the internal sentinel exception is
368   thrown from here. The body therefore never observes the 368   thrown from here. The body therefore never observes the
369   operation's result. It unwinds through its RAII destructors 369   operation's result. It unwinds through its RAII destructors
370   to a stopped completion. This is the one place `quitter` 370   to a stopped completion. This is the one place `quitter`
371   differs from @ref task::promise_type::transform_awaiter. 371   differs from @ref task::promise_type::transform_awaiter.
372   372  
373   @return The wrapped awaitable's await-result, forwarded 373   @return The wrapped awaitable's await-result, forwarded
374   unchanged, when no stop request is pending. 374   unchanged, when no stop request is pending.
375   375  
376   @par Exception Safety 376   @par Exception Safety
377   Throws the library's internal stop sentinel if the 377   Throws the library's internal stop sentinel if the
378   environment's stop token has a stop request pending. The 378   environment's stop token has a stop request pending. The
379   wrapped awaitable's `await_resume` is not called in that 379   wrapped awaitable's `await_resume` is not called in that
380   case. 380   case.
381   */ 381   */
382   // Check the stop token BEFORE the coroutine body 382   // Check the stop token BEFORE the coroutine body
383   // sees the result of the I/O operation. 383   // sees the result of the I/O operation.
HITCBC 384   21 decltype(auto) await_resume() 384   21 decltype(auto) await_resume()
385   { 385   {
HITCBC 386   21 set_current_frame_allocator( 386   21 set_current_frame_allocator(
HITCBC 387   21 p_->environment()->frame_allocator); 387   21 p_->environment()->frame_allocator);
HITCBC 388   21 if(p_->environment()->stop_token.stop_requested()) 388   21 if(p_->environment()->stop_token.stop_requested())
HITCBC 389   13 throw detail::stop_requested_exception{}; 389   13 throw detail::stop_requested_exception{};
HITCBC 390   8 return a_.await_resume(); 390   8 return a_.await_resume();
391   } 391   }
392   392  
393   /** Suspend by calling the wrapped awaitable with the 393   /** Suspend by calling the wrapped awaitable with the
394   environment. 394   environment.
395   395  
396   This is the plain `await_suspend` the compiler calls for the 396   This is the plain `await_suspend` the compiler calls for the
397   nested `co_await`. It forwards to the wrapped awaitable's 397   nested `co_await`. It forwards to the wrapped awaitable's
398   @ref IoAwaitable overload, supplying the promise's stored 398   @ref IoAwaitable overload, supplying the promise's stored
399   environment as the second argument. It then hands back 399   environment as the second argument. It then hands back
400   that call's result unchanged, so the wrapped awaitable's 400   that call's result unchanged, so the wrapped awaitable's
401   suspension decision, whatever form it takes, is preserved. 401   suspension decision, whatever form it takes, is preserved.
402   The stop token is not checked here; @ref await_resume checks 402   The stop token is not checked here; @ref await_resume checks
403   it on the way back out. 403   it on the way back out.
404   404  
405   @param h The coroutine performing the `co_await`. 405   @param h The coroutine performing the `co_await`.
406   406  
407   @return Whatever the wrapped awaitable's `await_suspend` 407   @return Whatever the wrapped awaitable's `await_suspend`
408   returns. When that is a `std::coroutine_handle<>`, the 408   returns. When that is a `std::coroutine_handle<>`, the
409   handle is routed through `detail::symmetric_transfer`. 409   handle is routed through `detail::symmetric_transfer`.
410   On MSVC that helper resumes the handle on the current 410   On MSVC that helper resumes the handle on the current
411   stack, and this function returns `void`, so the awaiting 411   stack, and this function returns `void`, so the awaiting
412   coroutine suspends unconditionally. On every other 412   coroutine suspends unconditionally. On every other
413   compiler the handle is returned unchanged for symmetric 413   compiler the handle is returned unchanged for symmetric
414   transfer. 414   transfer.
415   */ 415   */
416   template<class Promise> 416   template<class Promise>
HITCBC 417   20 auto await_suspend( 417   20 auto await_suspend(
418   std::coroutine_handle<Promise> h) noexcept 418   std::coroutine_handle<Promise> h) noexcept
419   { 419   {
420   using R = decltype( 420   using R = decltype(
421   a_.await_suspend(h, p_->environment())); 421   a_.await_suspend(h, p_->environment()));
422   if constexpr (std::is_same_v< 422   if constexpr (std::is_same_v<
423   R, std::coroutine_handle<>>) 423   R, std::coroutine_handle<>>)
HITCBC 424   18 return detail::symmetric_transfer( 424   18 return detail::symmetric_transfer(
HITCBC 425   36 a_.await_suspend(h, p_->environment())); 425   36 a_.await_suspend(h, p_->environment()));
426   else 426   else
HITCBC 427   2 return a_.await_suspend( 427   2 return a_.await_suspend(
HITCBC 428   4 h, p_->environment()); 428   4 h, p_->environment());
429   } 429   }
430   }; 430   };
431   431  
432   /** Transform a nested awaitable before `co_await`. 432   /** Transform a nested awaitable before `co_await`.
433   433  
434   Wraps an @ref IoAwaitable in a @ref transform_awaiter so the 434   Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
435   coroutine's environment is propagated into it and the stop 435   coroutine's environment is propagated into it and the stop
436   token is checked on resumption. A diagnostic is emitted if the 436   token is checked on resumption. A diagnostic is emitted if the
437   awaitable does not satisfy @ref IoAwaitable. 437   awaitable does not satisfy @ref IoAwaitable.
438   438  
439   @param a The awaitable expression from `co_await a`. 439   @param a The awaitable expression from `co_await a`.
440   440  
441   @return A @ref transform_awaiter wrapping `a`. 441   @return A @ref transform_awaiter wrapping `a`.
442   */ 442   */
443   template<class Awaitable> 443   template<class Awaitable>
HITCBC 444   21 auto transform_awaitable(Awaitable&& a) 444   21 auto transform_awaitable(Awaitable&& a)
445   { 445   {
446   using A = std::decay_t<Awaitable>; 446   using A = std::decay_t<Awaitable>;
447   if constexpr (IoAwaitable<A>) 447   if constexpr (IoAwaitable<A>)
448   { 448   {
449   return transform_awaiter<Awaitable>{ 449   return transform_awaiter<Awaitable>{
HITCBC 450   39 std::forward<Awaitable>(a), this}; 450   39 std::forward<Awaitable>(a), this};
451   } 451   }
452   else 452   else
453   { 453   {
454   static_assert(sizeof(A) == 0, 454   static_assert(sizeof(A) == 0,
455   "requires IoAwaitable"); 455   "requires IoAwaitable");
456   } 456   }
HITCBC 457   18 } 457   18 }
458   }; 458   };
459   459  
460   /** Handle to the owned coroutine frame. 460   /** Handle to the owned coroutine frame.
461   461  
462   Null when the quitter is empty (for example after a move or after 462   Null when the quitter is empty (for example after a move or after
463   @ref release). Prefer @ref handle to read this; the member is 463   @ref release). Prefer @ref handle to read this; the member is
464   public for use by the coroutine machinery. 464   public for use by the coroutine machinery.
465   */ 465   */
466   std::coroutine_handle<promise_type> h_; 466   std::coroutine_handle<promise_type> h_;
467   467  
468   /// Destroy the quitter and its coroutine frame if owned. 468   /// Destroy the quitter and its coroutine frame if owned.
HITCBC 469   82 ~quitter() 469   82 ~quitter()
470   { 470   {
HITCBC 471   82 if(h_) 471   82 if(h_)
HITCBC 472   15 h_.destroy(); 472   15 h_.destroy();
HITCBC 473   82 } 473   82 }
474   474  
475   /** Return false; quitters are never immediately ready. 475   /** Return false; quitters are never immediately ready.
476   476  
477   A quitter is lazy and has not started when it is awaited, so the 477   A quitter is lazy and has not started when it is awaited, so the
478   awaiting coroutine always suspends. 478   awaiting coroutine always suspends.
479   479  
480   @return `false`. 480   @return `false`.
481   */ 481   */
HITCBC 482   15 bool await_ready() const noexcept 482   15 bool await_ready() const noexcept
483   { 483   {
HITCBC 484   15 return false; 484   15 return false;
485   } 485   }
486   486  
487   /** Return the result, rethrow exception, or propagate stop. 487   /** Return the result, rethrow exception, or propagate stop.
488   488  
489   When stopped, throws stop_requested_exception so that a 489   When stopped, throws stop_requested_exception so that a
490   parent quitter also stops. A parent task<T> sees this 490   parent quitter also stops. A parent task<T> sees this
491   as an unhandled exception — by design. 491   as an unhandled exception — by design.
492   492  
493   @return The result value for non-void `T`, moved out of the 493   @return The result value for non-void `T`, moved out of the
494   quitter; otherwise `void`. 494   quitter; otherwise `void`.
495   495  
496   @par Exception Safety 496   @par Exception Safety
497   If the coroutine was stopped, the library's internal stop sentinel 497   If the coroutine was stopped, the library's internal stop sentinel
498   is thrown. If the body exited via any other exception, that 498   is thrown. If the body exited via any other exception, that
499   exception is rethrown. 499   exception is rethrown.
500   */ 500   */
HITCBC 501   12 auto await_resume() 501   12 auto await_resume()
502   { 502   {
HITCBC 503   12 if(h_.promise().stopped()) 503   12 if(h_.promise().stopped())
HITCBC 504   6 throw detail::stop_requested_exception{}; 504   6 throw detail::stop_requested_exception{};
HITCBC 505   6 if(h_.promise().state_ == promise_type::completion::exception) 505   6 if(h_.promise().state_ == promise_type::completion::exception)
HITCBC 506   1 std::rethrow_exception(h_.promise().ep_); 506   1 std::rethrow_exception(h_.promise().ep_);
507   if constexpr (! std::is_void_v<T>) 507   if constexpr (! std::is_void_v<T>)
HITCBC 508   4 return std::move(*h_.promise().result_); 508   4 return std::move(*h_.promise().result_);
509   else 509   else
HITCBC 510   1 return; 510   1 return;
511   } 511   }
512   512  
513   /** Start execution with the caller's context. 513   /** Start execution with the caller's context.
514   514  
515   Stores `cont` as the continuation to resume on completion. 515   Stores `cont` as the continuation to resume on completion.
516   Stores `env` as the execution environment propagated to nested 516   Stores `env` as the execution environment propagated to nested
517   `co_await` expressions. Then transfers control into the quitter's 517   `co_await` expressions. Then transfers control into the quitter's
518   coroutine body via the returned handle. 518   coroutine body via the returned handle.
519   519  
520   @param cont The awaiting coroutine to resume when the quitter 520   @param cont The awaiting coroutine to resume when the quitter
521   completes. 521   completes.
522   522  
523   @param env The execution environment (executor, stop token, and 523   @param env The execution environment (executor, stop token, and
524   frame allocator). It must outlive the quitter. 524   frame allocator). It must outlive the quitter.
525   525  
526   @return The quitter's coroutine handle, for symmetric transfer. 526   @return The quitter's coroutine handle, for symmetric transfer.
527   */ 527   */
HITCBC 528   15 std::coroutine_handle<> await_suspend( 528   15 std::coroutine_handle<> await_suspend(
529   std::coroutine_handle<> cont, 529   std::coroutine_handle<> cont,
530   io_env const* env) 530   io_env const* env)
531   { 531   {
HITCBC 532   15 h_.promise().set_continuation(cont); 532   15 h_.promise().set_continuation(cont);
HITCBC 533   15 h_.promise().set_environment(env); 533   15 h_.promise().set_environment(env);
HITCBC 534   15 return h_; 534   15 return h_;
535   } 535   }
536   536  
537   /** Return the coroutine handle. 537   /** Return the coroutine handle.
538   538  
539   @note Do not call `destroy()` on the returned handle while 539   @note Do not call `destroy()` on the returned handle while
540   the quitter is being awaited. The quitter's lifetime is 540   the quitter is being awaited. The quitter's lifetime is
541   normally managed by `run_async`, `run`, or the awaiting 541   normally managed by `run_async`, `run`, or the awaiting
542   parent. Manually destroying a suspended quitter that another 542   parent. Manually destroying a suspended quitter that another
543   coroutine is awaiting produces undefined behavior. For 543   coroutine is awaiting produces undefined behavior. For
544   cooperative cancellation, use `std::stop_token`. 544   cooperative cancellation, use `std::stop_token`.
545   545  
546   @return The coroutine handle. 546   @return The coroutine handle.
547   */ 547   */
HITCBC 548   20 std::coroutine_handle<promise_type> handle() const noexcept 548   20 std::coroutine_handle<promise_type> handle() const noexcept
549   { 549   {
HITCBC 550   20 return h_; 550   20 return h_;
551   } 551   }
552   552  
553   /** Release ownership of the coroutine frame. 553   /** Release ownership of the coroutine frame.
554   554  
555   @note The caller may call `destroy()` on the released handle 555   @note The caller may call `destroy()` on the released handle
556   only when the quitter has not started or has fully completed. 556   only when the quitter has not started or has fully completed.
557   Destroying a suspended quitter that is being awaited produces 557   Destroying a suspended quitter that is being awaited produces
558   undefined behavior. 558   undefined behavior.
559   559  
560   @par Postconditions 560   @par Postconditions
561   `handle()` returns a null handle. Callers needing the 561   `handle()` returns a null handle. Callers needing the
562   original handle must save it, via @ref handle, before 562   original handle must save it, via @ref handle, before
563   calling this. 563   calling this.
564   */ 564   */
HITCBC 565   18 void release() noexcept 565   18 void release() noexcept
566   { 566   {
HITCBC 567   18 h_ = nullptr; 567   18 h_ = nullptr;
HITCBC 568   18 } 568   18 }
569   569  
570   /** Copy construction is disabled; a quitter uniquely owns its frame. 570   /** Copy construction is disabled; a quitter uniquely owns its frame.
571   571  
572   @param other The quitter that would be copied. 572   @param other The quitter that would be copied.
573   */ 573   */
574   quitter(quitter const& other) = delete; 574   quitter(quitter const& other) = delete;
575   575  
576   /** Copy assignment is disabled; a quitter uniquely owns its frame. 576   /** Copy assignment is disabled; a quitter uniquely owns its frame.
577   577  
578   @param other The quitter that would be assigned from. 578   @param other The quitter that would be assigned from.
579   579  
580   @return A reference to `*this`. 580   @return A reference to `*this`.
581   */ 581   */
582   quitter& operator=(quitter const& other) = delete; 582   quitter& operator=(quitter const& other) = delete;
583   583  
584   /** Construct by moving, transferring ownership. 584   /** Construct by moving, transferring ownership.
585   585  
586   @par Postconditions 586   @par Postconditions
587   `other` is empty and must not be awaited. 587   `other` is empty and must not be awaited.
588   588  
589   @param other The quitter to move from. 589   @param other The quitter to move from.
590   */ 590   */
HITCBC 591   49 quitter(quitter&& other) noexcept 591   49 quitter(quitter&& other) noexcept
HITCBC 592   49 : h_(std::exchange(other.h_, nullptr)) 592   49 : h_(std::exchange(other.h_, nullptr))
593   { 593   {
HITCBC 594   49 } 594   49 }
595   595  
596   /** Assign by moving, transferring ownership. 596   /** Assign by moving, transferring ownership.
597   597  
598   If this quitter already owns a coroutine frame, that frame is 598   If this quitter already owns a coroutine frame, that frame is
599   destroyed first. Self-assignment is a no-op. 599   destroyed first. Self-assignment is a no-op.
600   600  
601   @par Postconditions 601   @par Postconditions
602   `other` is empty and must not be awaited. 602   `other` is empty and must not be awaited.
603   603  
604   @param other The quitter to move from. 604   @param other The quitter to move from.
605   605  
606   @return A reference to `*this`. 606   @return A reference to `*this`.
607   */ 607   */
608   quitter& operator=(quitter&& other) noexcept 608   quitter& operator=(quitter&& other) noexcept
609   { 609   {
610   if(this != &other) 610   if(this != &other)
611   { 611   {
612   if(h_) 612   if(h_)
613   h_.destroy(); 613   h_.destroy();
614   h_ = std::exchange(other.h_, nullptr); 614   h_ = std::exchange(other.h_, nullptr);
615   } 615   }
616   return *this; 616   return *this;
617   } 617   }
618   618  
619   private: 619   private:
HITCBC 620   33 explicit quitter(std::coroutine_handle<promise_type> h) 620   33 explicit quitter(std::coroutine_handle<promise_type> h)
HITCBC 621   33 : h_(h) 621   33 : h_(h)
622   { 622   {
HITCBC 623   33 } 623   33 }
624   }; 624   };
625   625  
626   } // namespace capy 626   } // namespace capy
627   } // namespace boost 627   } // namespace boost
628   628  
629   #endif 629   #endif