100.00% Lines (109/109) 100.00% Functions (30/30)
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/context_awaitable.hpp> 16   #include <boost/capy/concept/context_awaitable.hpp>
17   #include <boost/capy/ex/context_awaitable_promise_base.hpp> 17   #include <boost/capy/ex/context_awaitable_promise_base.hpp>
18   #include <boost/capy/ex/await_context.hpp> 18   #include <boost/capy/ex/await_context.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   4 void return_void()
65   { 65   {
HITCBC 66   2 } 66   4 }
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 _ContextAwaitable cancellation_. 125   Supports _ContextAwaitable 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 @ref task, @ref ContextRunnable, @ref ContextAwaitable 129   @see @ref task, @ref ContextRunnable, @ref ContextAwaitable
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 context_awaitable_promise_base. Unlike @ref task::promise_type, 140   @ref context_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 @ref context_awaitable_promise_base, @ref ContextRunnable 150   @see @ref context_awaitable_promise_base, @ref ContextRunnable
151   */ 151   */
152   struct promise_type 152   struct promise_type
153   : context_awaitable_promise_base<promise_type> 153   : context_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   36 promise_type() noexcept 166   38 promise_type() noexcept
HITCBC 167   36 : state_(completion::running) 167   38 : state_(completion::running)
168   { 168   {
HITCBC 169   36 } 169   38 }
170   170  
171   /// Destroy the promise, releasing any stored exception. 171   /// Destroy the promise, releasing any stored exception.
HITCBC 172   36 ~promise_type() 172   38 ~promise_type()
173   { 173   {
HITCBC 174   36 if(state_ == completion::exception || 174   38 if(state_ == completion::exception ||
HITCBC 175   32 state_ == completion::stopped) 175   34 state_ == completion::stopped)
HITCBC 176   20 ep_.~exception_ptr(); 176   20 ep_.~exception_ptr();
HITCBC 177   36 } 177   38 }
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   28 std::exception_ptr exception() const noexcept
192   { 192   {
HITCBC 193   26 if(state_ == completion::exception || 193   28 if(state_ == completion::exception ||
HITCBC 194   20 state_ == completion::stopped) 194   22 state_ == completion::stopped)
HITCBC 195   20 return ep_; 195   20 return ep_;
HITCBC 196   6 return {}; 196   8 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   36 quitter get_return_object() 217   38 quitter get_return_object()
218   { 218   {
219   return quitter{ 219   return quitter{
HITCBC 220   36 std::coroutine_handle<promise_type>::from_promise(*this)}; 220   38 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   36 auto initial_suspend() noexcept 234   38 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   36 bool await_ready() const noexcept 240   38 bool await_ready() const noexcept
241   { 241   {
HITCBC 242   36 return false; 242   38 return false;
243   } 243   }
244   244  
HITCBC 245   36 void await_suspend(std::coroutine_handle<>) const noexcept 245   38 void await_suspend(std::coroutine_handle<>) const noexcept
246   { 246   {
HITCBC 247   36 } 247   38 }
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   34 void await_resume() const 251   36 void await_resume() const
252   { 252   {
HITCBC 253   34 set_current_frame_allocator( 253   36 set_current_frame_allocator(
HITCBC 254   34 p_->environment()->frame_allocator); 254   36 p_->environment()->frame_allocator);
HITCBC 255   34 if(p_->environment()->stop_token.stop_requested()) 255   36 if(p_->environment()->stop_token.stop_requested())
HITCBC 256   2 throw detail::stop_requested_exception{}; 256   3 throw detail::stop_requested_exception{};
HITCBC 257   32 } 257   33 }
258   }; 258   };
HITCBC 259   36 return awaiter{this}; 259   38 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   35 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   35 bool await_ready() const noexcept
278   { 278   {
HITCBC 279   33 return false; 279   35 return false;
280   } 280   }
281   281  
HITCBC 282   33 std::coroutine_handle<> await_suspend( 282   35 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   35 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   35 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 ContextAwaitable. 328   /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable.
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 + Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
  355 + this always returns `false`, and @ref await_suspend asks
  356 + the wrapped awaitable instead. See
  357 + `detail::await_ready_then_suspend`.
  358 +
354   @return The wrapped awaitable's own `await_ready` result: 359   @return The wrapped awaitable's own `await_ready` result:
355 - `true` if no suspension is needed. 360 + `true` if no suspension is needed. Always `false` where
  361 + the workaround is on.
356   */ 362   */
HITCBC 357   23 bool await_ready() noexcept 363   24 bool await_ready() noexcept
358   { 364   {
  365 + #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
  366 + return false;
  367 + #else
HITCBC 359   23 return a_.await_ready(); 368   24 return a_.await_ready();
  369 + #endif
360   } 370   }
361   371  
362   /** Restore the frame allocator, check for stop, then resume the 372   /** Restore the frame allocator, check for stop, then resume the
363   wrapped awaitable. 373   wrapped awaitable.
364   374  
365   Reinstalls the thread-local frame allocator from the stored 375   Reinstalls the thread-local frame allocator from the stored
366   environment, then reads the environment's stop token. If a 376   environment, then reads the environment's stop token. If a
367   stop request is pending, the internal sentinel exception is 377   stop request is pending, the internal sentinel exception is
368   thrown from here. The body therefore never observes the 378   thrown from here. The body therefore never observes the
369   operation's result. It unwinds through its RAII destructors 379   operation's result. It unwinds through its RAII destructors
370   to a stopped completion. This is the one place `quitter` 380   to a stopped completion. This is the one place `quitter`
371   differs from @ref task::promise_type::transform_awaiter. 381   differs from @ref task::promise_type::transform_awaiter.
372   382  
373   @return The wrapped awaitable's await-result, forwarded 383   @return The wrapped awaitable's await-result, forwarded
374   unchanged, when no stop request is pending. 384   unchanged, when no stop request is pending.
375   385  
376   @par Exception Safety 386   @par Exception Safety
377   Throws the library's internal stop sentinel if the 387   Throws the library's internal stop sentinel if the
378   environment's stop token has a stop request pending. The 388   environment's stop token has a stop request pending. The
379   wrapped awaitable's `await_resume` is not called in that 389   wrapped awaitable's `await_resume` is not called in that
380   case. 390   case.
381   */ 391   */
382   // Check the stop token BEFORE the coroutine body 392   // Check the stop token BEFORE the coroutine body
383   // sees the result of the I/O operation. 393   // sees the result of the I/O operation.
HITCBC 384   22 decltype(auto) await_resume() 394   23 decltype(auto) await_resume()
385   { 395   {
HITCBC 386   22 set_current_frame_allocator( 396   23 set_current_frame_allocator(
HITCBC 387   22 p_->environment()->frame_allocator); 397   23 p_->environment()->frame_allocator);
HITCBC 388   22 if(p_->environment()->stop_token.stop_requested()) 398   23 if(p_->environment()->stop_token.stop_requested())
HITCBC 389   14 throw detail::stop_requested_exception{}; 399   14 throw detail::stop_requested_exception{};
HITCBC 390   8 return a_.await_resume(); 400   9 return a_.await_resume();
391   } 401   }
392   402  
393   /** Suspend by calling the wrapped awaitable with the 403   /** Suspend by calling the wrapped awaitable with the
394   environment. 404   environment.
395   405  
396   This is the plain `await_suspend` the compiler calls for the 406   This is the plain `await_suspend` the compiler calls for the
397   nested `co_await`. It forwards to the wrapped awaitable's 407   nested `co_await`. It forwards to the wrapped awaitable's
398   @ref ContextAwaitable overload, supplying the promise's stored 408   @ref ContextAwaitable overload, supplying the promise's stored
399   environment as the second argument. It then hands back 409   environment as the second argument. It then hands back
400   that call's result unchanged, so the wrapped awaitable's 410   that call's result unchanged, so the wrapped awaitable's
401   suspension decision, whatever form it takes, is preserved. 411   suspension decision, whatever form it takes, is preserved.
402   The stop token is not checked here; @ref await_resume checks 412   The stop token is not checked here; @ref await_resume checks
403   it on the way back out. 413   it on the way back out.
404   414  
405   @param h The coroutine performing the `co_await`. 415   @param h The coroutine performing the `co_await`.
406   416  
407   @return Whatever the wrapped awaitable's `await_suspend` 417   @return Whatever the wrapped awaitable's `await_suspend`
408   returns. When that is a `std::coroutine_handle<>`, the 418   returns. When that is a `std::coroutine_handle<>`, the
409   handle is routed through `detail::symmetric_transfer`. 419   handle is routed through `detail::symmetric_transfer`.
410 - On MSVC that helper resumes the handle on the current 420 + Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
411 - stack, and this function returns `void`, so the awaiting 421 + this function instead returns `bool` from
412 - coroutine suspends unconditionally. On every other 422 + `detail::await_ready_then_suspend`. It first asks the
413 - compiler the handle is returned unchanged for symmetric 423 + wrapped awaitable's `await_ready`. When that reports
414 - transfer. 424 + ready, it returns `false` to continue without
  425 + suspending. Otherwise it resumes any returned handle on
  426 + the current stack and returns `true`. Where the
  427 + workaround is off, a returned handle is passed back
  428 + unchanged for symmetric transfer.
415   */ 429   */
416   template<class Promise> 430   template<class Promise>
HITCBC 417   22 auto await_suspend( 431   23 auto await_suspend(
418   std::coroutine_handle<Promise> h) noexcept 432   std::coroutine_handle<Promise> h) noexcept
419   { 433   {
  434 + #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
  435 + return detail::await_ready_then_suspend(
  436 + a_, h, p_->environment());
  437 + #else
420   using R = decltype( 438   using R = decltype(
421   a_.await_suspend(h, p_->environment())); 439   a_.await_suspend(h, p_->environment()));
422   if constexpr (std::is_same_v< 440   if constexpr (std::is_same_v<
423   R, std::coroutine_handle<>>) 441   R, std::coroutine_handle<>>)
HITCBC 424   20 return detail::symmetric_transfer( 442   21 return detail::symmetric_transfer(
HITCBC 425   40 a_.await_suspend(h, p_->environment())); 443   42 a_.await_suspend(h, p_->environment()));
426   else 444   else
HITCBC 427   2 return a_.await_suspend( 445   2 return a_.await_suspend(
HITCBC 428   4 h, p_->environment()); 446   4 h, p_->environment());
  447 + #endif
429   } 448   }
430   }; 449   };
431   450  
432   /** Transform a nested awaitable before `co_await`. 451   /** Transform a nested awaitable before `co_await`.
433   452  
434   Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the 453   Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the
435   coroutine's environment is propagated into it and the stop 454   coroutine's environment is propagated into it and the stop
436   token is checked on resumption. A diagnostic is emitted if the 455   token is checked on resumption. A diagnostic is emitted if the
437   awaitable does not satisfy @ref ContextAwaitable. 456   awaitable does not satisfy @ref ContextAwaitable.
438   457  
439   @param a The awaitable expression from `co_await a`. 458   @param a The awaitable expression from `co_await a`.
440   459  
441   @return A @ref transform_awaiter wrapping `a`. 460   @return A @ref transform_awaiter wrapping `a`.
442   */ 461   */
443   template<class Awaitable> 462   template<class Awaitable>
HITCBC 444   23 auto transform_awaitable(Awaitable&& a) 463   24 auto transform_awaitable(Awaitable&& a)
445   { 464   {
446   using A = std::decay_t<Awaitable>; 465   using A = std::decay_t<Awaitable>;
447   if constexpr (ContextAwaitable<A>) 466   if constexpr (ContextAwaitable<A>)
448   { 467   {
449   return transform_awaiter<Awaitable>{ 468   return transform_awaiter<Awaitable>{
HITCBC 450   43 std::forward<Awaitable>(a), this}; 469   43 std::forward<Awaitable>(a), this};
451   } 470   }
452   else 471   else
453   { 472   {
454   static_assert(sizeof(A) == 0, 473   static_assert(sizeof(A) == 0,
455   "requires ContextAwaitable"); 474   "requires ContextAwaitable");
456   } 475   }
HITCBC 457   20 } 476   19 }
458   }; 477   };
459   478  
460   /** Handle to the owned coroutine frame. 479   /** Handle to the owned coroutine frame.
461   480  
462   Null when the quitter is empty (for example after a move or after 481   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 482   @ref release). Prefer @ref handle to read this; the member is
464   public for use by the coroutine machinery. 483   public for use by the coroutine machinery.
465   */ 484   */
466   std::coroutine_handle<promise_type> h_; 485   std::coroutine_handle<promise_type> h_;
467   486  
468   /** Destroy the quitter and its coroutine frame if owned. 487   /** Destroy the quitter and its coroutine frame if owned.
469   488  
470   @par Preconditions 489   @par Preconditions
471   The frame is not lent: no parent is suspended awaiting it, 490   The frame is not lent: no parent is suspended awaiting it,
472   unless its chain is being unwound after an @ref abandon. 491   unless its chain is being unwound after an @ref abandon.
473   Destroying a lent frame otherwise calls `std::terminate`; end 492   Destroying a lent frame otherwise calls `std::terminate`; end
474   a running chain by requesting stop, or by destroying its 493   a running chain by requesting stop, or by destroying its
475   execution context. 494   execution context.
476   */ 495   */
HITCBC 477   74 ~quitter() 496   76 ~quitter()
478   { 497   {
HITCBC 479   74 destroy_frame(); 498   76 destroy_frame();
HITCBC 480   74 } 499   76 }
481   500  
482   /** Return false; quitters are never immediately ready. 501   /** Return false; quitters are never immediately ready.
483   502  
484   A quitter is lazy and has not started when it is awaited, so the 503   A quitter is lazy and has not started when it is awaited, so the
485   awaiting coroutine always suspends. 504   awaiting coroutine always suspends.
486   505  
487   @return `false`. 506   @return `false`.
488   */ 507   */
HITCBC 489   16 bool await_ready() const noexcept 508   16 bool await_ready() const noexcept
490   { 509   {
HITCBC 491   16 return false; 510   16 return false;
492   } 511   }
493   512  
494   /** Return the result, rethrow exception, or propagate stop. 513   /** Return the result, rethrow exception, or propagate stop.
495   514  
496   When stopped, throws stop_requested_exception so that a 515   When stopped, throws stop_requested_exception so that a
497   parent quitter also stops. A parent task<T> sees this 516   parent quitter also stops. A parent task<T> sees this
498   as an unhandled exception — by design. 517   as an unhandled exception — by design.
499   518  
500   @return The result value for non-void `T`, moved out of the 519   @return The result value for non-void `T`, moved out of the
501   quitter; otherwise `void`. 520   quitter; otherwise `void`.
502   521  
503   @par Exception Safety 522   @par Exception Safety
504   If the coroutine was stopped, the library's internal stop sentinel 523   If the coroutine was stopped, the library's internal stop sentinel
505   is thrown. If the body exited via any other exception, that 524   is thrown. If the body exited via any other exception, that
506   exception is rethrown. 525   exception is rethrown.
507   */ 526   */
HITCBC 508   12 auto await_resume() 527   12 auto await_resume()
509   { 528   {
HITCBC 510   12 lent_ = false; 529   12 lent_ = false;
HITCBC 511   12 if(h_.promise().stopped()) 530   12 if(h_.promise().stopped())
HITCBC 512   6 throw detail::stop_requested_exception{}; 531   6 throw detail::stop_requested_exception{};
HITCBC 513   6 if(h_.promise().state_ == promise_type::completion::exception) 532   6 if(h_.promise().state_ == promise_type::completion::exception)
HITCBC 514   1 std::rethrow_exception(h_.promise().ep_); 533   1 std::rethrow_exception(h_.promise().ep_);
515   if constexpr (! std::is_void_v<T>) 534   if constexpr (! std::is_void_v<T>)
HITCBC 516   4 return std::move(*h_.promise().result_); 535   4 return std::move(*h_.promise().result_);
517   else 536   else
HITCBC 518   1 return; 537   1 return;
519   } 538   }
520   539  
521   /** Start execution with the caller's context. 540   /** Start execution with the caller's context.
522   541  
523   Stores `cont` as the continuation to resume on completion. 542   Stores `cont` as the continuation to resume on completion.
524   Stores `env` as the execution environment propagated to nested 543   Stores `env` as the execution environment propagated to nested
525   `co_await` expressions. Then transfers control into the quitter's 544   `co_await` expressions. Then transfers control into the quitter's
526   coroutine body via the returned handle. 545   coroutine body via the returned handle.
527   546  
528   @param cont The awaiting coroutine to resume when the quitter 547   @param cont The awaiting coroutine to resume when the quitter
529   completes. 548   completes.
530   549  
531   @param env The execution environment (executor, stop token, and 550   @param env The execution environment (executor, stop token, and
532   frame allocator). It must outlive the quitter. 551   frame allocator). It must outlive the quitter.
533   552  
534   @return The quitter's coroutine handle, for symmetric transfer. 553   @return The quitter's coroutine handle, for symmetric transfer.
535   */ 554   */
HITCBC 536   16 std::coroutine_handle<> await_suspend( 555   16 std::coroutine_handle<> await_suspend(
537   std::coroutine_handle<> cont, 556   std::coroutine_handle<> cont,
538   await_context const* env) 557   await_context const* env)
539   { 558   {
HITCBC 540   16 h_.promise().set_continuation(cont); 559   16 h_.promise().set_continuation(cont);
HITCBC 541   16 h_.promise().set_environment(env); 560   16 h_.promise().set_environment(env);
HITCBC 542   16 lent_ = true; 561   16 lent_ = true;
HITCBC 543   16 return h_; 562   16 return h_;
544   } 563   }
545   564  
546   /** Return the coroutine handle. 565   /** Return the coroutine handle.
547   566  
548   @note Do not call `destroy()` on the returned handle while the 567   @note Do not call `destroy()` on the returned handle while the
549   quitter is being awaited: the awaiting frame still holds it and 568   quitter is being awaited: the awaiting frame still holds it and
550   destroys it again, which has undefined behavior. A holder that 569   destroys it again, which has undefined behavior. A holder that
551   gives the coroutine up passes its continuation to @ref abandon 570   gives the coroutine up passes its continuation to @ref abandon
552   instead. (Destroying or 571   instead. (Destroying or
553   move-assigning over the quitter object itself while its frame is 572   move-assigning over the quitter object itself while its frame is
554   lent is a different operation, and calls `std::terminate`.) 573   lent is a different operation, and calls `std::terminate`.)
555   The quitter's lifetime is normally managed by `run_async`, `run`, 574   The quitter's lifetime is normally managed by `run_async`, `run`,
556   or the awaiting parent. For cooperative cancellation, use 575   or the awaiting parent. For cooperative cancellation, use
557   `std::stop_token`. 576   `std::stop_token`.
558   577  
559   @note The operation, queue or primitive holding a suspended 578   @note The operation, queue or primitive holding a suspended
560   coroutine gives it up by passing the continuation it keeps to 579   coroutine gives it up by passing the continuation it keeps to
561   @ref abandon. `handle()` belongs to the owning quitter object. 580   @ref abandon. `handle()` belongs to the owning quitter object.
562   581  
563   @return The coroutine handle. 582   @return The coroutine handle.
564   */ 583   */
HITCBC 565   20 std::coroutine_handle<promise_type> handle() const noexcept 584   22 std::coroutine_handle<promise_type> handle() const noexcept
566   { 585   {
HITCBC 567   20 return h_; 586   22 return h_;
568   } 587   }
569   588  
570   /** Release ownership of the coroutine frame. 589   /** Release ownership of the coroutine frame.
571   590  
572   @note The caller may call `destroy()` on the released handle 591   @note The caller may call `destroy()` on the released handle
573   only when the quitter has not started or has fully completed. 592   only when the quitter has not started or has fully completed.
574   Destroying the released handle of a quitter that is being 593   Destroying the released handle of a quitter that is being
575   awaited is not supported and has undefined behavior. 594   awaited is not supported and has undefined behavior.
576   595  
577   @par Postconditions 596   @par Postconditions
578   `handle()` returns a null handle. Callers needing the 597   `handle()` returns a null handle. Callers needing the
579   original handle must save it, via @ref handle, before 598   original handle must save it, via @ref handle, before
580   calling this. 599   calling this.
581   */ 600   */
HITCBC 582   18 void release() noexcept 601   20 void release() noexcept
583   { 602   {
HITCBC 584   18 h_ = nullptr; 603   20 h_ = nullptr;
HITCBC 585   18 } 604   20 }
586   605  
587   /** Copy construction is disabled; a quitter uniquely owns its frame. 606   /** Copy construction is disabled; a quitter uniquely owns its frame.
588   607  
589   @param other The quitter that would be copied. 608   @param other The quitter that would be copied.
590   */ 609   */
591   quitter(quitter const& other) = delete; 610   quitter(quitter const& other) = delete;
592   611  
593   /** Copy assignment is disabled; a quitter uniquely owns its frame. 612   /** Copy assignment is disabled; a quitter uniquely owns its frame.
594   613  
595   @param other The quitter that would be assigned from. 614   @param other The quitter that would be assigned from.
596   615  
597   @return A reference to `*this`. 616   @return A reference to `*this`.
598   */ 617   */
599   quitter& operator=(quitter const& other) = delete; 618   quitter& operator=(quitter const& other) = delete;
600   619  
601   /** Construct by moving, transferring ownership. 620   /** Construct by moving, transferring ownership.
602   621  
603   @par Postconditions 622   @par Postconditions
604   `other` is empty and must not be awaited. 623   `other` is empty and must not be awaited.
605   624  
606   @param other The quitter to move from. 625   @param other The quitter to move from.
607   */ 626   */
HITCBC 608   38 quitter(quitter&& other) noexcept 627   38 quitter(quitter&& other) noexcept
HITCBC 609   38 : h_(std::exchange(other.h_, nullptr)) 628   38 : h_(std::exchange(other.h_, nullptr))
610   { 629   {
HITCBC 611   38 } 630   38 }
612   631  
613   /** Assign by moving, transferring ownership. 632   /** Assign by moving, transferring ownership.
614   633  
615   If this quitter already owns a coroutine frame, that frame is 634   If this quitter already owns a coroutine frame, that frame is
616   destroyed first. Self-assignment is a no-op. 635   destroyed first. Self-assignment is a no-op.
617   636  
618   @par Preconditions 637   @par Preconditions
619   The frame being replaced is not lent: no parent is suspended 638   The frame being replaced is not lent: no parent is suspended
620   awaiting it. Replacing a lent frame calls `std::terminate`. 639   awaiting it. Replacing a lent frame calls `std::terminate`.
621   640  
622   @par Postconditions 641   @par Postconditions
623   `other` is empty and must not be awaited. 642   `other` is empty and must not be awaited.
624   643  
625   @param other The quitter to move from. 644   @param other The quitter to move from.
626   645  
627   @return A reference to `*this`. 646   @return A reference to `*this`.
628   */ 647   */
HITCBC 629   1 quitter& operator=(quitter&& other) noexcept 648   1 quitter& operator=(quitter&& other) noexcept
630   { 649   {
HITCBC 631   1 if(this != &other) 650   1 if(this != &other)
632   { 651   {
HITCBC 633   1 destroy_frame(); 652   1 destroy_frame();
HITCBC 634   1 h_ = std::exchange(other.h_, nullptr); 653   1 h_ = std::exchange(other.h_, nullptr);
635   } 654   }
HITCBC 636   1 return *this; 655   1 return *this;
637   } 656   }
638   657  
639   private: 658   private:
640   // Set while a parent is suspended awaiting this quitter. 659   // Set while a parent is suspended awaiting this quitter.
641   bool lent_ = false; 660   bool lent_ = false;
642   661  
HITCBC 643   36 explicit quitter(std::coroutine_handle<promise_type> h) 662   38 explicit quitter(std::coroutine_handle<promise_type> h)
HITCBC 644   36 : h_(h) 663   38 : h_(h)
645   { 664   {
HITCBC 646   36 } 665   38 }
647   666  
HITCBC 648   75 void destroy_frame() noexcept 667   77 void destroy_frame() noexcept
649   { 668   {
HITCBC 650   75 if(!h_) 669   77 if(!h_)
HITCBC 651   57 return; 670   59 return;
HITCBC 652   18 if(lent_ && !h_.done()) 671   18 if(lent_ && !h_.done())
653   { 672   {
654   // Only the unwind of an abandoned chain destroys a frame 673   // Only the unwind of an abandoned chain destroys a frame
655   // that is still awaited. 674   // that is still awaited.
HITCBC 656   1 auto* owner = h_.promise().environment()->owner; 675   1 auto* owner = h_.promise().environment()->owner;
HITCBC 657   1 if(!owner || !owner->abandoned()) 676   1 if(!owner || !owner->abandoned())
658   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process 677   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
659   } 678   }
HITCBC 660   18 std::exchange(h_, nullptr).destroy(); 679   18 std::exchange(h_, nullptr).destroy();
HITCBC 661   18 lent_ = false; 680   18 lent_ = false;
662   } 681   }
663   }; 682   };
664   683  
665   } // namespace capy 684   } // namespace capy
666   } // namespace boost 685   } // namespace boost
667   686  
668   #endif 687   #endif