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