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