100.00% Lines (125/125) 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/cond.hpp> 15   #include <boost/capy/cond.hpp>
16   #include <boost/capy/results.hpp> 16   #include <boost/capy/results.hpp>
17   #include <boost/capy/concept/executor.hpp> 17   #include <boost/capy/concept/executor.hpp>
18   #include <boost/capy/concept/context_awaitable.hpp> 18   #include <boost/capy/concept/context_awaitable.hpp>
19   #include <boost/capy/ex/context_awaitable_promise_base.hpp> 19   #include <boost/capy/ex/context_awaitable_promise_base.hpp>
20   #include <boost/capy/ex/await_context.hpp> 20   #include <boost/capy/ex/await_context.hpp>
21   #include <boost/capy/ex/frame_allocator.hpp> 21   #include <boost/capy/ex/frame_allocator.hpp>
22   #include <boost/capy/detail/await_suspend_helper.hpp> 22   #include <boost/capy/detail/await_suspend_helper.hpp>
23   23  
24   #include <exception> 24   #include <exception>
25   #include <optional> 25   #include <optional>
26   #include <tuple> 26   #include <tuple>
27   #include <type_traits> 27   #include <type_traits>
28   #include <utility> 28   #include <utility>
29   29  
30   /* Stop-aware coroutine task. 30   /* Stop-aware coroutine task.
31   31  
32   quitter<T> is identical to task<T> except that a stop request ends 32   quitter<T> is identical to task<T> except that a stop request ends
33   the body without the body checking an error code. The promise's 33   the body without the body checking an error code. The promise's
34   transform_awaiter checks the stop token before it touches an awaited 34   transform_awaiter checks the stop token before it touches an awaited
35   operation, and again when an operation that completed 35   operation, and again when an operation that completed
36   asynchronously resumes the body. A pending stop there throws a 36   asynchronously resumes the body. A pending stop there throws a
37   sentinel exception that unwinds through RAII destructors to 37   sentinel exception that unwinds through RAII destructors to
38   final_suspend, and the parent sees a "stopped" completion. An 38   final_suspend, and the parent sees a "stopped" completion. An
39   operation that completes synchronously always has its await_resume 39   operation that completes synchronously always has its await_resume
40   called, because its await_ready or await_suspend may already have 40   called, because its await_ready or await_suspend may already have
41   acquired something. A results<> it returns holding cond::canceled 41   acquired something. A results<> it returns holding cond::canceled
42   while the stop is pending acquired nothing, so the stop throws there; 42   while the stop is pending acquired nothing, so the stop throws there;
43   any other result reaches the body, and the stop takes effect at the 43   any other result reaches the body, and the stop takes effect at the
44   next co_await. 44   next co_await.
45   45  
46   See doc/quitter.md for the full design rationale. */ 46   See doc/quitter.md for the full design rationale. */
47   47  
48   namespace boost { 48   namespace boost {
49   namespace capy { 49   namespace capy {
50   50  
51   namespace detail { 51   namespace detail {
52   52  
53   // Reuse the same return-value storage as task<T>. 53   // Reuse the same return-value storage as task<T>.
54   // task_return_base is defined in task.hpp, but quitter needs its own 54   // task_return_base is defined in task.hpp, but quitter needs its own
55   // copy to avoid a header dependency on task.hpp. 55   // copy to avoid a header dependency on task.hpp.
56   template<typename T> 56   template<typename T>
57   struct quitter_return_base 57   struct quitter_return_base
58   { 58   {
59   std::optional<T> result_; 59   std::optional<T> result_;
60   60  
HITCBC 61   11 void return_value(T value) 61   11 void return_value(T value)
62   { 62   {
HITCBC 63   11 result_ = std::move(value); 63   11 result_ = std::move(value);
HITCBC 64   11 } 64   11 }
65   65  
HITCBC 66   5 T&& result() noexcept 66   5 T&& result() noexcept
67   { 67   {
HITCBC 68   5 return std::move(*result_); 68   5 return std::move(*result_);
69   } 69   }
70   }; 70   };
71   71  
72   template<> 72   template<>
73   struct quitter_return_base<void> 73   struct quitter_return_base<void>
74   { 74   {
HITCBC 75   8 void return_void() 75   8 void return_void()
76   { 76   {
HITCBC 77   8 } 77   8 }
78   }; 78   };
79   79  
80   } // namespace detail 80   } // namespace detail
81   81  
82   /** Defers a coroutine body until awaited, then unwinds it early on a stop request. 82   /** Defers a coroutine body until awaited, then unwinds it early on a stop request.
83   83  
84   When the stop token is triggered, the body is unwound at a 84   When the stop token is triggered, the body is unwound at a
85   `co_await` instead of resuming, and RAII destructors run normally. 85   `co_await` instead of resuming, and RAII destructors run normally.
86   Await-effects below says at which one. The parent observes a "stopped" 86   Await-effects below says at which one. The parent observes a "stopped"
87   completion via @ref promise_type::stopped. 87   completion via @ref promise_type::stopped.
88   88  
89   Everything else — frame allocation, environment propagation, 89   Everything else — frame allocation, environment propagation,
90   symmetric transfer, move semantics — is identical to @ref task. 90   symmetric transfer, move semantics — is identical to @ref task.
91   91  
92   @par Await-effects 92   @par Await-effects
93   93  
94   Let `q` be a `quitter<T>`. `co_await q` always suspends the awaiting 94   Let `q` be a `quitter<T>`. `co_await q` always suspends the awaiting
95   coroutine, then transfers control directly into the quitter's 95   coroutine, then transfers control directly into the quitter's
96   coroutine body on the current thread; no executor operation is 96   coroutine body on the current thread; no executor operation is
97   posted. The quitter records the caller's environment (executor, stop 97   posted. The quitter records the caller's environment (executor, stop
98   token, and frame allocator) by pointer rather than copying it. It 98   token, and frame allocator) by pointer rather than copying it. It
99   propagates that environment to every `co_await` inside the body. 99   propagates that environment to every `co_await` inside the body.
100   100  
101   Unlike @ref task, the stop token is checked before the body's first 101   Unlike @ref task, the stop token is checked before the body's first
102   statement, before each awaited operation is started, and when an 102   statement, before each awaited operation is started, and when an
103   operation that completed asynchronously resumes the body. If a stop 103   operation that completed asynchronously resumes the body. If a stop
104   request is pending at those points, the body is not resumed. An 104   request is pending at those points, the body is not resumed. An
105   internal sentinel exception unwinds it instead, so RAII destructors 105   internal sentinel exception unwinds it instead, so RAII destructors
106   run, and the coroutine completes as stopped. 106   run, and the coroutine completes as stopped.
107   107  
108   An awaited operation that completes synchronously always has its 108   An awaited operation that completes synchronously always has its
109   `await_resume` called, even if a stop request arrives at the same 109   `await_resume` called, even if a stop request arrives at the same
110   time. Its `await_ready` or `await_suspend` may already have acquired 110   time. Its `await_ready` or `await_suspend` may already have acquired
111   a resource, such as `async_mutex`'s lock, that only its 111   a resource, such as `async_mutex`'s lock, that only its
112   `await_resume` hands over. If that result is a @ref results holding 112   `await_resume` hands over. If that result is a @ref results holding
113   `cond::canceled` while a stop is pending, the operation saw the stop 113   `cond::canceled` while a stop is pending, the operation saw the stop
114   first and acquired nothing, so the body is unwound there. Any other 114   first and acquired nothing, so the body is unwound there. Any other
115   result is handed to the body, and the stop takes effect at the next 115   result is handed to the body, and the stop takes effect at the next
116   `co_await`. An operation whose result is not a @ref results can 116   `co_await`. An operation whose result is not a @ref results can
117   therefore hand the body one result it produced after seeing the 117   therefore hand the body one result it produced after seeing the
118   stop. 118   stop.
119   119  
120   The body runs until it returns, exits via an exception, or is unwound 120   The body runs until it returns, exits via an exception, or is unwound
121   by a stop request. Control then transfers directly back to the 121   by a stop request. Control then transfers directly back to the
122   awaiting coroutine, again without an executor operation. 122   awaiting coroutine, again without an executor operation.
123   123  
124   @par Await-returns 124   @par Await-returns
125   The value the body passed to `co_return`, moved out of the quitter, 125   The value the body passed to `co_return`, moved out of the quitter,
126   or nothing when `T` is `void`. 126   or nothing when `T` is `void`.
127   127  
128   If the body exits via an unhandled exception, that exception is 128   If the body exits via an unhandled exception, that exception is
129   rethrown instead. 129   rethrown instead.
130   130  
131   If the coroutine completed as stopped, the internal sentinel 131   If the coroutine completed as stopped, the internal sentinel
132   exception is thrown instead of await-returning. Awaiting a stopped 132   exception is thrown instead of await-returning. Awaiting a stopped
133   `quitter` from another `quitter` therefore stops that one too. A 133   `quitter` from another `quitter` therefore stops that one too. A
134   @ref task awaiting it sees the sentinel as an unhandled exception in 134   @ref task awaiting it sees the sentinel as an unhandled exception in
135   its own body. When a quitter is started by `run_async`, a stopped 135   its own body. When a quitter is started by `run_async`, a stopped
136   completion reaches the error handler as the sentinel 136   completion reaches the error handler as the sentinel
137   `std::exception_ptr`, not the value handler. 137   `std::exception_ptr`, not the value handler.
138   138  
139   @par Await-postcondition 139   @par Await-postcondition
140   The quitter's coroutine has run to completion and is suspended at its 140   The quitter's coroutine has run to completion and is suspended at its
141   final suspend point; the body's RAII destructors have run. Exactly 141   final suspend point; the body's RAII destructors have run. Exactly
142   one of the following holds: the body returned a value; the body 142   one of the following holds: the body returned a value; the body
143   exited via an exception; or `handle().promise().stopped()` returns 143   exited via an exception; or `handle().promise().stopped()` returns
144   `true`. When the body returned a value, the await moved it out, so a 144   `true`. When the body returned a value, the await moved it out, so a
145   quitter must not be awaited twice. 145   quitter must not be awaited twice.
146   146  
147   @par Remarks 147   @par Remarks
148   Supports _ContextAwaitable cancellation_. 148   Supports _ContextAwaitable cancellation_.
149   149  
150   @tparam T The result type. Use `quitter<>` for `quitter<void>`. 150   @tparam T The result type. Use `quitter<>` for `quitter<void>`.
151   151  
152   @see @ref task, @ref ContextRunnable, @ref ContextAwaitable 152   @see @ref task, @ref ContextRunnable, @ref ContextAwaitable
153   */ 153   */
154   template<typename T = void> 154   template<typename T = void>
155   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 155   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
156   quitter 156   quitter
157   { 157   {
158   /** Stores `quitter<T>`'s result and unwinds the body when the stop token fires. 158   /** Stores `quitter<T>`'s result and unwinds the body when the stop token fires.
159   159  
160   This is the promise object the compiler associates with a 160   This is the promise object the compiler associates with a
161   `quitter<T>` coroutine. It satisfies the coroutine promise 161   `quitter<T>` coroutine. It satisfies the coroutine promise
162   requirements and participates in the I/O awaitable protocol via 162   requirements and participates in the I/O awaitable protocol via
163   @ref context_awaitable_promise_base. Unlike @ref task::promise_type, 163   @ref context_awaitable_promise_base. Unlike @ref task::promise_type,
164   its `transform_awaitable` checks the stop token before each 164   its `transform_awaitable` checks the stop token before each
165   awaited result reaches the body. A pending stop request throws an 165   awaited result reaches the body. A pending stop request throws an
166   internal sentinel exception that unwinds to a "stopped" 166   internal sentinel exception that unwinds to a "stopped"
167   completion. It is part of the coroutine machinery and is not 167   completion. It is part of the coroutine machinery and is not
168   intended to be used directly by callers. 168   intended to be used directly by callers.
169   169  
170   Result storage and `return_value`/`return_void` are provided by 170   Result storage and `return_value`/`return_void` are provided by
171   `detail::quitter_return_base<T>`. 171   `detail::quitter_return_base<T>`.
172   172  
173   @see @ref context_awaitable_promise_base, @ref ContextRunnable 173   @see @ref context_awaitable_promise_base, @ref ContextRunnable
174   */ 174   */
175   struct promise_type 175   struct promise_type
176   : context_awaitable_promise_base<promise_type> 176   : context_awaitable_promise_base<promise_type>
177   , detail::quitter_return_base<T> 177   , detail::quitter_return_base<T>
178   { 178   {
179   private: 179   private:
180   friend quitter; 180   friend quitter;
181   181  
182   enum class completion { running, value, exception, stopped }; 182   enum class completion { running, value, exception, stopped };
183   183  
184   union { std::exception_ptr ep_; }; 184   union { std::exception_ptr ep_; };
185   completion state_; 185   completion state_;
186   186  
187   public: 187   public:
188   /// Construct the promise in the running state. 188   /// Construct the promise in the running state.
HITCBC 189   45 promise_type() noexcept 189   45 promise_type() noexcept
HITCBC 190   45 : state_(completion::running) 190   45 : state_(completion::running)
191   { 191   {
HITCBC 192   45 } 192   45 }
193   193  
194   /// Destroy the promise, releasing any stored exception. 194   /// Destroy the promise, releasing any stored exception.
HITCBC 195   45 ~promise_type() 195   45 ~promise_type()
196   { 196   {
HITCBC 197   45 if(state_ == completion::exception || 197   45 if(state_ == completion::exception ||
HITCBC 198   41 state_ == completion::stopped) 198   41 state_ == completion::stopped)
HITCBC 199   23 ep_.~exception_ptr(); 199   23 ep_.~exception_ptr();
HITCBC 200   45 } 200   45 }
201   201  
202   /** Return a non-null exception_ptr when the coroutine threw 202   /** Return a non-null exception_ptr when the coroutine threw
203   or was stopped. 203   or was stopped.
204   204  
205   Stopped quitters report the sentinel 205   Stopped quitters report the sentinel
206   stop_requested_exception so that run_async routes to 206   stop_requested_exception so that run_async routes to
207   the error handler instead of accessing a non-existent 207   the error handler instead of accessing a non-existent
208   result. 208   result.
209   209  
210   @return The stored exception if the coroutine exited via an 210   @return The stored exception if the coroutine exited via an
211   exception or was stopped, otherwise a null 211   exception or was stopped, otherwise a null
212   `std::exception_ptr`. 212   `std::exception_ptr`.
213   */ 213   */
HITCBC 214   38 std::exception_ptr exception() const noexcept 214   38 std::exception_ptr exception() const noexcept
215   { 215   {
HITCBC 216   38 if(state_ == completion::exception || 216   38 if(state_ == completion::exception ||
HITCBC 217   32 state_ == completion::stopped) 217   32 state_ == completion::stopped)
HITCBC 218   26 return ep_; 218   26 return ep_;
HITCBC 219   12 return {}; 219   12 return {};
220   } 220   }
221   221  
222   /** True when the coroutine was stopped via the stop token. 222   /** True when the coroutine was stopped via the stop token.
223   223  
224   @return `true` if the body was unwound by a stop request; 224   @return `true` if the body was unwound by a stop request;
225   `false` if it returned a value or exited via any other 225   `false` if it returned a value or exited via any other
226   exception. 226   exception.
227   */ 227   */
HITCBC 228   12 bool stopped() const noexcept 228   12 bool stopped() const noexcept
229   { 229   {
HITCBC 230   12 return state_ == completion::stopped; 230   12 return state_ == completion::stopped;
231   } 231   }
232   232  
233   /** Return the owning `quitter` for this coroutine. 233   /** Return the owning `quitter` for this coroutine.
234   234  
235   Called by the compiler to produce the object returned to the 235   Called by the compiler to produce the object returned to the
236   caller when the coroutine is created. 236   caller when the coroutine is created.
237   237  
238   @return A `quitter` owning the coroutine frame. 238   @return A `quitter` owning the coroutine frame.
239   */ 239   */
HITCBC 240   45 quitter get_return_object() 240   45 quitter get_return_object()
241   { 241   {
242   return quitter{ 242   return quitter{
HITCBC 243   45 std::coroutine_handle<promise_type>::from_promise(*this)}; 243   45 std::coroutine_handle<promise_type>::from_promise(*this)};
244   } 244   }
245   245  
246   /** Return the initial-suspend awaiter. 246   /** Return the initial-suspend awaiter.
247   247  
248   The coroutine always suspends at the initial suspend point, 248   The coroutine always suspends at the initial suspend point,
249   so the body does not start until the quitter is awaited. When 249   so the body does not start until the quitter is awaited. When
250   the body is resumed, the awaiter restores the thread-local 250   the body is resumed, the awaiter restores the thread-local
251   frame allocator. It then throws the internal sentinel 251   frame allocator. It then throws the internal sentinel
252   exception if stop is already requested, so the body never 252   exception if stop is already requested, so the body never
253   runs and the coroutine completes as stopped. 253   runs and the coroutine completes as stopped.
254   254  
255   @return An awaiter that suspends unconditionally. 255   @return An awaiter that suspends unconditionally.
256   */ 256   */
HITCBC 257   45 auto initial_suspend() noexcept 257   45 auto initial_suspend() noexcept
258   { 258   {
259   struct awaiter 259   struct awaiter
260   { 260   {
261   promise_type* p_; 261   promise_type* p_;
262   262  
HITCBC 263   45 bool await_ready() const noexcept 263   45 bool await_ready() const noexcept
264   { 264   {
HITCBC 265   45 return false; 265   45 return false;
266   } 266   }
267   267  
HITCBC 268   45 void await_suspend(std::coroutine_handle<>) const noexcept 268   45 void await_suspend(std::coroutine_handle<>) const noexcept
269   { 269   {
HITCBC 270   45 } 270   45 }
271   271  
272   // Potentially-throwing: checks the stop token before 272   // Potentially-throwing: checks the stop token before
273   // the coroutine body executes its first statement. 273   // the coroutine body executes its first statement.
HITCBC 274   43 void await_resume() const 274   43 void await_resume() const
275   { 275   {
HITCBC 276   43 set_current_frame_allocator( 276   43 set_current_frame_allocator(
HITCBC 277   43 p_->environment()->frame_allocator); 277   43 p_->environment()->frame_allocator);
HITCBC 278   43 if(p_->environment()->stop_token.stop_requested()) 278   43 if(p_->environment()->stop_token.stop_requested())
HITCBC 279   3 throw detail::stop_requested_exception{}; 279   2 throw detail::stop_requested_exception{};
HITCBC 280   40 } 280   41 }
281   }; 281   };
HITCBC 282   45 return awaiter{this}; 282   45 return awaiter{this};
283   } 283   }
284   284  
285   /** Return the final-suspend awaiter. 285   /** Return the final-suspend awaiter.
286   286  
287   The coroutine always suspends at the final suspend point. The 287   The coroutine always suspends at the final suspend point. The
288   awaiter's `await_suspend` performs symmetric transfer to the 288   awaiter's `await_suspend` performs symmetric transfer to the
289   stored continuation, resuming the awaiting coroutine. 289   stored continuation, resuming the awaiting coroutine.
290   290  
291   @return An awaiter that suspends and transfers to the 291   @return An awaiter that suspends and transfers to the
292   continuation. 292   continuation.
293   */ 293   */
HITCBC 294   42 auto final_suspend() noexcept 294   42 auto final_suspend() noexcept
295   { 295   {
296   struct awaiter 296   struct awaiter
297   { 297   {
298   promise_type* p_; 298   promise_type* p_;
299   299  
HITCBC 300   42 bool await_ready() const noexcept 300   42 bool await_ready() const noexcept
301   { 301   {
HITCBC 302   42 return false; 302   42 return false;
303   } 303   }
304   304  
HITCBC 305   42 std::coroutine_handle<> await_suspend( 305   42 std::coroutine_handle<> await_suspend(
306   std::coroutine_handle<>) const noexcept 306   std::coroutine_handle<>) const noexcept
307   { 307   {
HITCBC 308   42 return p_->continuation(); 308   42 return p_->continuation();
309   } 309   }
310   310  
311   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 311   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
312   }; 312   };
HITCBC 313   42 return awaiter{this}; 313   42 return awaiter{this};
314   } 314   }
315   315  
316   /** Capture the in-flight exception from the coroutine body. 316   /** Capture the in-flight exception from the coroutine body.
317   317  
318   Called by the compiler when the coroutine body exits via an 318   Called by the compiler when the coroutine body exits via an
319   unhandled exception. The internal stop sentinel is recorded as 319   unhandled exception. The internal stop sentinel is recorded as
320   a stopped completion; any other exception is recorded as an 320   a stopped completion; any other exception is recorded as an
321   exception completion. The stored exception is surfaced (or 321   exception completion. The stored exception is surfaced (or
322   routed to the error handler) when the quitter is awaited or run. 322   routed to the error handler) when the quitter is awaited or run.
323   */ 323   */
HITCBC 324   23 void unhandled_exception() 324   23 void unhandled_exception()
325   { 325   {
326   try 326   try
327   { 327   {
HITCBC 328   23 throw; 328   23 throw;
329   } 329   }
HITCBC 330   23 catch(detail::stop_requested_exception const&) 330   23 catch(detail::stop_requested_exception const&)
331   { 331   {
332   // Store the exception_ptr so that run_async's 332   // Store the exception_ptr so that run_async's
333   // invoke_impl routes to the error handler 333   // invoke_impl routes to the error handler
334   // instead of accessing a non-existent result. 334   // instead of accessing a non-existent result.
HITCBC 335   19 new (&ep_) std::exception_ptr( 335   19 new (&ep_) std::exception_ptr(
336   std::current_exception()); 336   std::current_exception());
HITCBC 337   19 state_ = completion::stopped; 337   19 state_ = completion::stopped;
338   } 338   }
HITCBC 339   4 catch(...) 339   4 catch(...)
340   { 340   {
HITCBC 341   4 new (&ep_) std::exception_ptr( 341   4 new (&ep_) std::exception_ptr(
342   std::current_exception()); 342   std::current_exception());
HITCBC 343   4 state_ = completion::exception; 343   4 state_ = completion::exception;
344   } 344   }
HITCBC 345   23 } 345   23 }
346   346  
347   //------------------------------------------------------ 347   //------------------------------------------------------
348   // transform_awaitable — the key difference from task<T> 348   // transform_awaitable — the key difference from task<T>
349   //------------------------------------------------------ 349   //------------------------------------------------------
350   350  
351   /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable. 351   /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable.
352   352  
353   Forwards the environment to the inner awaitable's 353   Forwards the environment to the inner awaitable's
354   environment-taking `await_suspend` and restores the 354   environment-taking `await_suspend` and restores the
355   thread-local frame allocator before the body resumes. Unlike 355   thread-local frame allocator before the body resumes. Unlike
356   `task`'s, it also checks the stop token. A stop pending before 356   `task`'s, it also checks the stop token. A stop pending before
357   the wrapped awaitable is touched, or on resumption after an 357   the wrapped awaitable is touched, or on resumption after an
358   asynchronous completion, throws the internal sentinel. The 358   asynchronous completion, throws the internal sentinel. The
359   body then unwinds before it observes the operation's result. 359   body then unwinds before it observes the operation's result.
360   A synchronous completion is always handed over through the 360   A synchronous completion is always handed over through the
361   wrapped awaitable's `await_resume`. 361   wrapped awaitable's `await_resume`.
362   362  
363   @tparam Awaitable The awaitable being transformed. 363   @tparam Awaitable The awaitable being transformed.
364   */ 364   */
365   template<class Awaitable> 365   template<class Awaitable>
366   struct transform_awaiter 366   struct transform_awaiter
367   { 367   {
368   /// The wrapped awaitable, decayed and stored by value. 368   /// The wrapped awaitable, decayed and stored by value.
369   std::decay_t<Awaitable> a_; 369   std::decay_t<Awaitable> a_;
370   370  
371   /// The promise of the coroutine performing the `co_await`. 371   /// The promise of the coroutine performing the `co_await`.
372   promise_type* p_; 372   promise_type* p_;
373   373  
374   /// True when a stop was pending at the `co_await`, so the 374   /// True when a stop was pending at the `co_await`, so the
375   /// wrapped awaitable was never touched. 375   /// wrapped awaitable was never touched.
376   bool skipped_ = false; 376   bool skipped_ = false;
377   377  
378   /// True when the wrapped awaitable completed synchronously, so 378   /// True when the wrapped awaitable completed synchronously, so
379   /// its `await_resume` must run to hand over what it acquired. 379   /// its `await_resume` must run to hand over what it acquired.
380   bool completed_ = false; 380   bool completed_ = false;
381   381  
382   /** Report whether the `co_await` can continue without 382   /** Report whether the `co_await` can continue without
383   suspending. 383   suspending.
384   384  
385   The stop token is checked first. If a stop is pending, the 385   The stop token is checked first. If a stop is pending, the
386   wrapped awaitable is never touched: this returns `true`, and 386   wrapped awaitable is never touched: this returns `true`, and
387   @ref await_resume throws. Otherwise the wrapped awaitable's 387   @ref await_resume throws. Otherwise the wrapped awaitable's
388   own `await_ready` decides. A `true` result there means it 388   own `await_ready` decides. A `true` result there means it
389   completed synchronously, so @ref await_resume calls its 389   completed synchronously, so @ref await_resume calls its
390   `await_resume` even if a stop arrives afterwards. That hands 390   `await_resume` even if a stop arrives afterwards. That hands
391   over anything it acquired, such as `async_mutex`'s lock. 391   over anything it acquired, such as `async_mutex`'s lock.
392   392  
393   Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, 393   Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
394   this always returns `false`, and @ref await_suspend does both 394   this always returns `false`, and @ref await_suspend does both
395   steps instead. See `detail::await_ready_then_suspend`. 395   steps instead. See `detail::await_ready_then_suspend`.
396   396  
397   @return `true` if no suspension is needed. Always `false` 397   @return `true` if no suspension is needed. Always `false`
398   where the workaround is on. 398   where the workaround is on.
399   */ 399   */
HITCBC 400   31 bool await_ready() noexcept 400   32 bool await_ready() noexcept
401   { 401   {
402   #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND 402   #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
403   return false; 403   return false;
404   #else 404   #else
HITCBC 405   31 if(p_->environment()->stop_token.stop_requested()) 405   32 if(p_->environment()->stop_token.stop_requested())
406   { 406   {
HITCBC 407   1 skipped_ = true; 407   1 skipped_ = true;
HITCBC 408   1 return true; 408   1 return true;
409   } 409   }
HITCBC 410   30 completed_ = a_.await_ready(); 410   31 completed_ = a_.await_ready();
HITCBC 411   30 return completed_; 411   31 return completed_;
412   #endif 412   #endif
413   } 413   }
414   414  
415   /** Restore the frame allocator, check for stop, then resume the 415   /** Restore the frame allocator, check for stop, then resume the
416   wrapped awaitable. 416   wrapped awaitable.
417   417  
418   Reinstalls the thread-local frame allocator from the stored 418   Reinstalls the thread-local frame allocator from the stored
419   environment. The internal sentinel exception is then thrown 419   environment. The internal sentinel exception is then thrown
420   in three cases: a stop was pending before the wrapped 420   in three cases: a stop was pending before the wrapped
421   awaitable was touched; a stop is pending now and the 421   awaitable was touched; a stop is pending now and the
422   wrapped awaitable completed asynchronously; or a stop is 422   wrapped awaitable completed asynchronously; or a stop is
423   pending now and the wrapped awaitable's result is a 423   pending now and the wrapped awaitable's result is a
424   @ref results holding `cond::canceled`. The body therefore 424   @ref results holding `cond::canceled`. The body therefore
425   never observes that operation's result, and unwinds through 425   never observes that operation's result, and unwinds through
426   its RAII destructors to a stopped completion. A wrapped 426   its RAII destructors to a stopped completion. A wrapped
427   awaitable that completed synchronously always has its 427   awaitable that completed synchronously always has its
428   `await_resume` called, and any other result it gives is 428   `await_resume` called, and any other result it gives is
429   returned, with the stop taking effect at the next 429   returned, with the stop taking effect at the next
430   `co_await`. This is the one place `quitter` differs from 430   `co_await`. This is the one place `quitter` differs from
431   @ref task::promise_type::transform_awaiter. 431   @ref task::promise_type::transform_awaiter.
432   432  
433   After an asynchronous completion with a stop pending, the 433   After an asynchronous completion with a stop pending, the
434   operation's result is dropped. Releasing anything it held is 434   operation's result is dropped. Releasing anything it held is
435   then the wrapped awaiter's destructor's job: `async_mutex` 435   then the wrapped awaiter's destructor's job: `async_mutex`
436   releases a lock it granted, and `async_waker` drops the 436   releases a lock it granted, and `async_waker` drops the
437   wakeup. The body is unwinding, so nothing observes the loss. 437   wakeup. The body is unwinding, so nothing observes the loss.
438   438  
439   @return The wrapped awaitable's await-result, forwarded 439   @return The wrapped awaitable's await-result, forwarded
440   unchanged, when no sentinel is thrown. 440   unchanged, when no sentinel is thrown.
441   441  
442   @par Exception Safety 442   @par Exception Safety
443   Throws the library's internal stop sentinel in the three 443   Throws the library's internal stop sentinel in the three
444   cases above. The wrapped awaitable's `await_resume` is not 444   cases above. The wrapped awaitable's `await_resume` is not
445   called in the first two; in the third, its canceled result 445   called in the first two; in the third, its canceled result
446   is dropped. 446   is dropped.
447   */ 447   */
HITCBC 448   30 decltype(auto) await_resume() 448   31 decltype(auto) await_resume()
449   { 449   {
HITCBC 450   30 set_current_frame_allocator( 450   31 set_current_frame_allocator(
HITCBC 451   30 p_->environment()->frame_allocator); 451   31 p_->environment()->frame_allocator);
HITCBC 452   59 if(skipped_ || 452   61 if(skipped_ ||
HITCBC 453   50 (!completed_ && 453   52 (!completed_ &&
HITCBC 454   21 p_->environment()->stop_token.stop_requested())) 454   22 p_->environment()->stop_token.stop_requested()))
HITCBC 455   16 throw detail::stop_requested_exception{}; 455   17 throw detail::stop_requested_exception{};
456   using R = decltype(a_.await_resume()); 456   using R = decltype(a_.await_resume());
457   if constexpr (detail::is_results_v<R>) 457   if constexpr (detail::is_results_v<R>)
458   { 458   {
459   // A result canceled by our own stop acquired 459   // A result canceled by our own stop acquired
460   // nothing, so the stop can take effect here. 460   // nothing, so the stop can take effect here.
HITCBC 461   5 R r = a_.await_resume(); 461   5 R r = a_.await_resume();
HITCBC 462   7 if(std::get<0>(r) == cond::canceled && 462   7 if(std::get<0>(r) == cond::canceled &&
HITCBC 463   2 p_->environment()->stop_token.stop_requested()) 463   2 p_->environment()->stop_token.stop_requested())
HITCBC 464   1 throw detail::stop_requested_exception{}; 464   1 throw detail::stop_requested_exception{};
HITCBC 465   8 return r; 465   8 return r;
HITCBC 466   3 } 466   3 }
467   else 467   else
468   { 468   {
HITCBC 469   9 return a_.await_resume(); 469   9 return a_.await_resume();
470   } 470   }
471   } 471   }
472   472  
473   /** Suspend by calling the wrapped awaitable with the 473   /** Suspend by calling the wrapped awaitable with the
474   environment. 474   environment.
475   475  
476   This is the plain `await_suspend` the compiler calls for the 476   This is the plain `await_suspend` the compiler calls for the
477   nested `co_await`. It forwards to the wrapped awaitable's 477   nested `co_await`. It forwards to the wrapped awaitable's
478   @ref ContextAwaitable overload, supplying the promise's stored 478   @ref ContextAwaitable overload, supplying the promise's stored
479   environment as the second argument. It keeps the wrapped 479   environment as the second argument. It keeps the wrapped
480   awaitable's suspension decision, whatever form it takes. 480   awaitable's suspension decision, whatever form it takes.
481   When that decision is to continue without suspending, the 481   When that decision is to continue without suspending, the
482   wrapped awaitable completed synchronously, and 482   wrapped awaitable completed synchronously, and
483   @ref await_resume will call its `await_resume`. Returning 483   @ref await_resume will call its `await_resume`. Returning
484   `h` itself counts as continuing without suspending. 484   `h` itself counts as continuing without suspending.
485   485  
486   @param h The coroutine performing the `co_await`. 486   @param h The coroutine performing the `co_await`.
487   487  
488   @return Whatever the wrapped awaitable's `await_suspend` 488   @return Whatever the wrapped awaitable's `await_suspend`
489   returns. When that is a `std::coroutine_handle<>`, the 489   returns. When that is a `std::coroutine_handle<>`, the
490   handle is routed through `detail::symmetric_transfer`. 490   handle is routed through `detail::symmetric_transfer`.
491   Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, 491   Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
492   this function instead returns `bool`. It first checks the 492   this function instead returns `bool`. It first checks the
493   stop token, as @ref await_ready does where the workaround is 493   stop token, as @ref await_ready does where the workaround is
494   off, and returns `false` without touching the wrapped 494   off, and returns `false` without touching the wrapped
495   awaitable if a stop is pending. Otherwise it returns the 495   awaitable if a stop is pending. Otherwise it returns the
496   result of `detail::await_ready_then_suspend`. Where the 496   result of `detail::await_ready_then_suspend`. Where the
497   workaround is off, a returned handle is passed back 497   workaround is off, a returned handle is passed back
498   unchanged for symmetric transfer. 498   unchanged for symmetric transfer.
499   */ 499   */
500   template<class Promise> 500   template<class Promise>
HITCBC 501   25 auto await_suspend( 501   26 auto await_suspend(
502   std::coroutine_handle<Promise> h) noexcept 502   std::coroutine_handle<Promise> h) noexcept
503   { 503   {
504   // completed_ is written only where nothing else can have 504   // completed_ is written only where nothing else can have
505   // resumed this frame: a frame that really suspended may 505   // resumed this frame: a frame that really suspended may
506   // already be running, or gone, on another thread. 506   // already be running, or gone, on another thread.
507   #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND 507   #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
508   if(p_->environment()->stop_token.stop_requested()) 508   if(p_->environment()->stop_token.stop_requested())
509   { 509   {
510   skipped_ = true; 510   skipped_ = true;
511   return false; 511   return false;
512   } 512   }
513   return detail::await_ready_then_suspend( 513   return detail::await_ready_then_suspend(
514   a_, h, p_->environment(), &completed_); 514   a_, h, p_->environment(), &completed_);
515   #else 515   #else
516   using R = decltype( 516   using R = decltype(
517   a_.await_suspend(h, p_->environment())); 517   a_.await_suspend(h, p_->environment()));
518   if constexpr (std::is_same_v< 518   if constexpr (std::is_same_v<
519   R, std::coroutine_handle<>>) 519   R, std::coroutine_handle<>>)
520   { 520   {
521   std::coroutine_handle<> next = 521   std::coroutine_handle<> next =
HITCBC 522   22 a_.await_suspend(h, p_->environment()); 522   23 a_.await_suspend(h, p_->environment());
523   // Returning h means no one else can resume this 523   // Returning h means no one else can resume this
524   // frame: a synchronous completion. 524   // frame: a synchronous completion.
525   // GCC falsely warns that writing completed_ overruns the noop 525   // GCC falsely warns that writing completed_ overruns the noop
526   // coroutine's frame: when the wrapped await_suspend returns 526   // coroutine's frame: when the wrapped await_suspend returns
527   // std::noop_coroutine(), it assumes the branch below can be taken 527   // std::noop_coroutine(), it assumes the branch below can be taken
528   // and treats this frame as the noop frame. A noop handle never 528   // and treats this frame as the noop frame. A noop handle never
529   // equals h, so the branch is dead on that path. 529   // equals h, so the branch is dead on that path.
530   #if defined(__GNUC__) && !defined(__clang__) 530   #if defined(__GNUC__) && !defined(__clang__)
531   #pragma GCC diagnostic push 531   #pragma GCC diagnostic push
532   #pragma GCC diagnostic ignored "-Warray-bounds" 532   #pragma GCC diagnostic ignored "-Warray-bounds"
533   #endif 533   #endif
HITCBC 534   22 if(next == h) 534   23 if(next == h)
HITCBC 535   1 completed_ = true; 535   1 completed_ = true;
536   #if defined(__GNUC__) && !defined(__clang__) 536   #if defined(__GNUC__) && !defined(__clang__)
537   #pragma GCC diagnostic pop 537   #pragma GCC diagnostic pop
538   #endif 538   #endif
HITCBC 539   22 return detail::symmetric_transfer(next); 539   23 return detail::symmetric_transfer(next);
540   } 540   }
541   else if constexpr (std::is_same_v<R, bool>) 541   else if constexpr (std::is_same_v<R, bool>)
542   { 542   {
HITCBC 543   3 if(!a_.await_suspend(h, p_->environment())) 543   3 if(!a_.await_suspend(h, p_->environment()))
544   { 544   {
HITCBC 545   2 completed_ = true; 545   2 completed_ = true;
HITCBC 546   2 return false; 546   2 return false;
547   } 547   }
HITCBC 548   1 return true; 548   1 return true;
549   } 549   }
550   else 550   else
551   { 551   {
552   return a_.await_suspend( 552   return a_.await_suspend(
553   h, p_->environment()); 553   h, p_->environment());
554   } 554   }
555   #endif 555   #endif
556   } 556   }
557   }; 557   };
558   558  
559   /** Transform a nested awaitable before `co_await`. 559   /** Transform a nested awaitable before `co_await`.
560   560  
561   Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the 561   Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the
562   coroutine's environment is propagated into it and the stop 562   coroutine's environment is propagated into it and the stop
563   token is checked on resumption. A diagnostic is emitted if the 563   token is checked on resumption. A diagnostic is emitted if the
564   awaitable does not satisfy @ref ContextAwaitable. 564   awaitable does not satisfy @ref ContextAwaitable.
565   565  
566   @param a The awaitable expression from `co_await a`. 566   @param a The awaitable expression from `co_await a`.
567   567  
568   @return A @ref transform_awaiter wrapping `a`. 568   @return A @ref transform_awaiter wrapping `a`.
569   */ 569   */
570   template<class Awaitable> 570   template<class Awaitable>
HITCBC 571   31 auto transform_awaitable(Awaitable&& a) 571   32 auto transform_awaitable(Awaitable&& a)
572   { 572   {
573   using A = std::decay_t<Awaitable>; 573   using A = std::decay_t<Awaitable>;
574   if constexpr (ContextAwaitable<A>) 574   if constexpr (ContextAwaitable<A>)
575   { 575   {
576   return transform_awaiter<Awaitable>{ 576   return transform_awaiter<Awaitable>{
HITCBC 577   51 std::forward<Awaitable>(a), this}; 577   53 std::forward<Awaitable>(a), this};
578   } 578   }
579   else 579   else
580   { 580   {
581   static_assert(sizeof(A) == 0, 581   static_assert(sizeof(A) == 0,
582   "requires ContextAwaitable"); 582   "requires ContextAwaitable");
583   } 583   }
HITCBC 584   20 } 584   21 }
585   }; 585   };
586   586  
587   /** Handle to the owned coroutine frame. 587   /** Handle to the owned coroutine frame.
588   588  
589   Null when the quitter is empty (for example after a move or after 589   Null when the quitter is empty (for example after a move or after
590   @ref release). Prefer @ref handle to read this; the member is 590   @ref release). Prefer @ref handle to read this; the member is
591   public for use by the coroutine machinery. 591   public for use by the coroutine machinery.
592   */ 592   */
593   std::coroutine_handle<promise_type> h_; 593   std::coroutine_handle<promise_type> h_;
594   594  
595   /** Destroy the quitter and its coroutine frame if owned. 595   /** Destroy the quitter and its coroutine frame if owned.
596   596  
597   @par Preconditions 597   @par Preconditions
598   The frame is not lent: no parent is suspended awaiting it, 598   The frame is not lent: no parent is suspended awaiting it,
599   unless its chain is being unwound after an @ref abandon. 599   unless its chain is being unwound after an @ref abandon.
600   Destroying a lent frame otherwise calls `std::terminate`; end 600   Destroying a lent frame otherwise calls `std::terminate`; end
601   a running chain by requesting stop, or by destroying its 601   a running chain by requesting stop, or by destroying its
602   execution context. 602   execution context.
603   */ 603   */
HITCBC 604   83 ~quitter() 604   83 ~quitter()
605   { 605   {
HITCBC 606   83 destroy_frame(); 606   83 destroy_frame();
HITCBC 607   83 } 607   83 }
608   608  
609   /** Return false; quitters are never immediately ready. 609   /** Return false; quitters are never immediately ready.
610   610  
611   A quitter is lazy and has not started when it is awaited, so the 611   A quitter is lazy and has not started when it is awaited, so the
612   awaiting coroutine always suspends. 612   awaiting coroutine always suspends.
613   613  
614   @return `false`. 614   @return `false`.
615   */ 615   */
HITCBC 616   16 bool await_ready() const noexcept 616   16 bool await_ready() const noexcept
617   { 617   {
HITCBC 618   16 return false; 618   16 return false;
619   } 619   }
620   620  
621   /** Return the result, rethrow exception, or propagate stop. 621   /** Return the result, rethrow exception, or propagate stop.
622   622  
623   When stopped, throws stop_requested_exception so that a 623   When stopped, throws stop_requested_exception so that a
624   parent quitter also stops. A parent task<T> sees this 624   parent quitter also stops. A parent task<T> sees this
625   as an unhandled exception — by design. 625   as an unhandled exception — by design.
626   626  
627   @return The result value for non-void `T`, moved out of the 627   @return The result value for non-void `T`, moved out of the
628   quitter; otherwise `void`. 628   quitter; otherwise `void`.
629   629  
630   @par Exception Safety 630   @par Exception Safety
631   If the coroutine was stopped, the library's internal stop sentinel 631   If the coroutine was stopped, the library's internal stop sentinel
632   is thrown. If the body exited via any other exception, that 632   is thrown. If the body exited via any other exception, that
633   exception is rethrown. 633   exception is rethrown.
634   */ 634   */
HITCBC 635   12 auto await_resume() 635   12 auto await_resume()
636   { 636   {
HITCBC 637   12 lent_ = false; 637   12 lent_ = false;
HITCBC 638   12 if(h_.promise().stopped()) 638   12 if(h_.promise().stopped())
HITCBC 639   6 throw detail::stop_requested_exception{}; 639   6 throw detail::stop_requested_exception{};
HITCBC 640   6 if(h_.promise().state_ == promise_type::completion::exception) 640   6 if(h_.promise().state_ == promise_type::completion::exception)
HITCBC 641   1 std::rethrow_exception(h_.promise().ep_); 641   1 std::rethrow_exception(h_.promise().ep_);
642   if constexpr (! std::is_void_v<T>) 642   if constexpr (! std::is_void_v<T>)
HITCBC 643   4 return std::move(*h_.promise().result_); 643   4 return std::move(*h_.promise().result_);
644   else 644   else
HITCBC 645   1 return; 645   1 return;
646   } 646   }
647   647  
648   /** Start execution with the caller's context. 648   /** Start execution with the caller's context.
649   649  
650   Stores `cont` as the continuation to resume on completion. 650   Stores `cont` as the continuation to resume on completion.
651   Stores `env` as the execution environment propagated to nested 651   Stores `env` as the execution environment propagated to nested
652   `co_await` expressions. Then transfers control into the quitter's 652   `co_await` expressions. Then transfers control into the quitter's
653   coroutine body via the returned handle. 653   coroutine body via the returned handle.
654   654  
655   @param cont The awaiting coroutine to resume when the quitter 655   @param cont The awaiting coroutine to resume when the quitter
656   completes. 656   completes.
657   657  
658   @param env The execution environment (executor, stop token, and 658   @param env The execution environment (executor, stop token, and
659   frame allocator). It must outlive the quitter. 659   frame allocator). It must outlive the quitter.
660   660  
661   @return The quitter's coroutine handle, for symmetric transfer. 661   @return The quitter's coroutine handle, for symmetric transfer.
662   */ 662   */
HITCBC 663   16 std::coroutine_handle<> await_suspend( 663   16 std::coroutine_handle<> await_suspend(
664   std::coroutine_handle<> cont, 664   std::coroutine_handle<> cont,
665   await_context const* env) 665   await_context const* env)
666   { 666   {
HITCBC 667   16 h_.promise().set_continuation(cont); 667   16 h_.promise().set_continuation(cont);
HITCBC 668   16 h_.promise().set_environment(env); 668   16 h_.promise().set_environment(env);
HITCBC 669   16 lent_ = true; 669   16 lent_ = true;
HITCBC 670   16 return h_; 670   16 return h_;
671   } 671   }
672   672  
673   /** Return the coroutine handle. 673   /** Return the coroutine handle.
674   674  
675   @note Do not call `destroy()` on the returned handle while the 675   @note Do not call `destroy()` on the returned handle while the
676   quitter is being awaited: the awaiting frame still holds it and 676   quitter is being awaited: the awaiting frame still holds it and
677   destroys it again, which has undefined behavior. A holder that 677   destroys it again, which has undefined behavior. A holder that
678   gives the coroutine up passes its continuation to @ref abandon 678   gives the coroutine up passes its continuation to @ref abandon
679   instead. (Destroying or 679   instead. (Destroying or
680   move-assigning over the quitter object itself while its frame is 680   move-assigning over the quitter object itself while its frame is
681   lent is a different operation, and calls `std::terminate`.) 681   lent is a different operation, and calls `std::terminate`.)
682   The quitter's lifetime is normally managed by `run_async`, `run`, 682   The quitter's lifetime is normally managed by `run_async`, `run`,
683   or the awaiting parent. For cooperative cancellation, use 683   or the awaiting parent. For cooperative cancellation, use
684   `std::stop_token`. 684   `std::stop_token`.
685   685  
686   @note The operation, queue or primitive holding a suspended 686   @note The operation, queue or primitive holding a suspended
687   coroutine gives it up by passing the continuation it keeps to 687   coroutine gives it up by passing the continuation it keeps to
688   @ref abandon. `handle()` belongs to the owning quitter object. 688   @ref abandon. `handle()` belongs to the owning quitter object.
689   689  
690   @return The coroutine handle. 690   @return The coroutine handle.
691   */ 691   */
HITCBC 692   29 std::coroutine_handle<promise_type> handle() const noexcept 692   29 std::coroutine_handle<promise_type> handle() const noexcept
693   { 693   {
HITCBC 694   29 return h_; 694   29 return h_;
695   } 695   }
696   696  
697   /** Release ownership of the coroutine frame. 697   /** Release ownership of the coroutine frame.
698   698  
699   @note The caller may call `destroy()` on the released handle 699   @note The caller may call `destroy()` on the released handle
700   only when the quitter has not started or has fully completed. 700   only when the quitter has not started or has fully completed.
701   Destroying the released handle of a quitter that is being 701   Destroying the released handle of a quitter that is being
702   awaited is not supported and has undefined behavior. 702   awaited is not supported and has undefined behavior.
703   703  
704   @par Postconditions 704   @par Postconditions
705   `handle()` returns a null handle. Callers needing the 705   `handle()` returns a null handle. Callers needing the
706   original handle must save it, via @ref handle, before 706   original handle must save it, via @ref handle, before
707   calling this. 707   calling this.
708   */ 708   */
HITCBC 709   27 void release() noexcept 709   27 void release() noexcept
710   { 710   {
HITCBC 711   27 h_ = nullptr; 711   27 h_ = nullptr;
HITCBC 712   27 } 712   27 }
713   713  
714   /** Copy construction is disabled; a quitter uniquely owns its frame. 714   /** Copy construction is disabled; a quitter uniquely owns its frame.
715   715  
716   @param other The quitter that would be copied. 716   @param other The quitter that would be copied.
717   */ 717   */
718   quitter(quitter const& other) = delete; 718   quitter(quitter const& other) = delete;
719   719  
720   /** Copy assignment is disabled; a quitter uniquely owns its frame. 720   /** Copy assignment is disabled; a quitter uniquely owns its frame.
721   721  
722   @param other The quitter that would be assigned from. 722   @param other The quitter that would be assigned from.
723   723  
724   @return A reference to `*this`. 724   @return A reference to `*this`.
725   */ 725   */
726   quitter& operator=(quitter const& other) = delete; 726   quitter& operator=(quitter const& other) = delete;
727   727  
728   /** Construct by moving, transferring ownership. 728   /** Construct by moving, transferring ownership.
729   729  
730   @par Postconditions 730   @par Postconditions
731   `other` is empty and must not be awaited. 731   `other` is empty and must not be awaited.
732   732  
733   @param other The quitter to move from. 733   @param other The quitter to move from.
734   */ 734   */
HITCBC 735   38 quitter(quitter&& other) noexcept 735   38 quitter(quitter&& other) noexcept
HITCBC 736   38 : h_(std::exchange(other.h_, nullptr)) 736   38 : h_(std::exchange(other.h_, nullptr))
737   { 737   {
HITCBC 738   38 } 738   38 }
739   739  
740   /** Assign by moving, transferring ownership. 740   /** Assign by moving, transferring ownership.
741   741  
742   If this quitter already owns a coroutine frame, that frame is 742   If this quitter already owns a coroutine frame, that frame is
743   destroyed first. Self-assignment is a no-op. 743   destroyed first. Self-assignment is a no-op.
744   744  
745   @par Preconditions 745   @par Preconditions
746   The frame being replaced is not lent: no parent is suspended 746   The frame being replaced is not lent: no parent is suspended
747   awaiting it. Replacing a lent frame calls `std::terminate`. 747   awaiting it. Replacing a lent frame calls `std::terminate`.
748   748  
749   @par Postconditions 749   @par Postconditions
750   `other` is empty and must not be awaited. 750   `other` is empty and must not be awaited.
751   751  
752   @param other The quitter to move from. 752   @param other The quitter to move from.
753   753  
754   @return A reference to `*this`. 754   @return A reference to `*this`.
755   */ 755   */
HITCBC 756   1 quitter& operator=(quitter&& other) noexcept 756   1 quitter& operator=(quitter&& other) noexcept
757   { 757   {
HITCBC 758   1 if(this != &other) 758   1 if(this != &other)
759   { 759   {
HITCBC 760   1 destroy_frame(); 760   1 destroy_frame();
HITCBC 761   1 h_ = std::exchange(other.h_, nullptr); 761   1 h_ = std::exchange(other.h_, nullptr);
762   } 762   }
HITCBC 763   1 return *this; 763   1 return *this;
764   } 764   }
765   765  
766   private: 766   private:
767   // Set while a parent is suspended awaiting this quitter. 767   // Set while a parent is suspended awaiting this quitter.
768   bool lent_ = false; 768   bool lent_ = false;
769   769  
HITCBC 770   45 explicit quitter(std::coroutine_handle<promise_type> h) 770   45 explicit quitter(std::coroutine_handle<promise_type> h)
HITCBC 771   45 : h_(h) 771   45 : h_(h)
772   { 772   {
HITCBC 773   45 } 773   45 }
774   774  
HITCBC 775   84 void destroy_frame() noexcept 775   84 void destroy_frame() noexcept
776   { 776   {
HITCBC 777   84 if(!h_) 777   84 if(!h_)
HITCBC 778   66 return; 778   66 return;
HITCBC 779   18 if(lent_ && !h_.done()) 779   18 if(lent_ && !h_.done())
780   { 780   {
781   // Only the unwind of an abandoned chain destroys a frame 781   // Only the unwind of an abandoned chain destroys a frame
782   // that is still awaited. 782   // that is still awaited.
HITCBC 783   1 auto* owner = h_.promise().environment()->owner; 783   1 auto* owner = h_.promise().environment()->owner;
HITCBC 784   1 if(!owner || !owner->abandoned()) 784   1 if(!owner || !owner->abandoned())
785   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process 785   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
786   } 786   }
HITCBC 787   18 std::exchange(h_, nullptr).destroy(); 787   18 std::exchange(h_, nullptr).destroy();
HITCBC 788   18 lent_ = false; 788   18 lent_ = false;
789   } 789   }
790   }; 790   };
791   791  
792   } // namespace capy 792   } // namespace capy
793   } // namespace boost 793   } // namespace boost
794   794  
795   #endif 795   #endif