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