TLA Line data Source 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 :
61 HIT 11 : void return_value(T value)
62 : {
63 11 : result_ = std::move(value);
64 11 : }
65 :
66 5 : T&& result() noexcept
67 : {
68 5 : return std::move(*result_);
69 : }
70 : };
71 :
72 : template<>
73 : struct quitter_return_base<void>
74 : {
75 8 : void return_void()
76 : {
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.
189 45 : promise_type() noexcept
190 45 : : state_(completion::running)
191 : {
192 45 : }
193 :
194 : /// Destroy the promise, releasing any stored exception.
195 45 : ~promise_type()
196 : {
197 45 : if(state_ == completion::exception ||
198 41 : state_ == completion::stopped)
199 23 : ep_.~exception_ptr();
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 : */
214 38 : std::exception_ptr exception() const noexcept
215 : {
216 38 : if(state_ == completion::exception ||
217 32 : state_ == completion::stopped)
218 26 : return ep_;
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 : */
228 12 : bool stopped() const noexcept
229 : {
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 : */
240 45 : quitter get_return_object()
241 : {
242 : return quitter{
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 : */
257 45 : auto initial_suspend() noexcept
258 : {
259 : struct awaiter
260 : {
261 : promise_type* p_;
262 :
263 45 : bool await_ready() const noexcept
264 : {
265 45 : return false;
266 : }
267 :
268 45 : void await_suspend(std::coroutine_handle<>) const noexcept
269 : {
270 45 : }
271 :
272 : // Potentially-throwing: checks the stop token before
273 : // the coroutine body executes its first statement.
274 43 : void await_resume() const
275 : {
276 43 : set_current_frame_allocator(
277 43 : p_->environment()->frame_allocator);
278 43 : if(p_->environment()->stop_token.stop_requested())
279 2 : throw detail::stop_requested_exception{};
280 41 : }
281 : };
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 : */
294 42 : auto final_suspend() noexcept
295 : {
296 : struct awaiter
297 : {
298 : promise_type* p_;
299 :
300 42 : bool await_ready() const noexcept
301 : {
302 42 : return false;
303 : }
304 :
305 42 : std::coroutine_handle<> await_suspend(
306 : std::coroutine_handle<>) const noexcept
307 : {
308 42 : return p_->continuation();
309 : }
310 :
311 : void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
312 : };
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 : */
324 23 : void unhandled_exception()
325 : {
326 : try
327 : {
328 23 : throw;
329 : }
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.
335 19 : new (&ep_) std::exception_ptr(
336 : std::current_exception());
337 19 : state_ = completion::stopped;
338 : }
339 4 : catch(...)
340 : {
341 4 : new (&ep_) std::exception_ptr(
342 : std::current_exception());
343 4 : state_ = completion::exception;
344 : }
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 : */
400 32 : bool await_ready() noexcept
401 : {
402 : #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
403 : return false;
404 : #else
405 32 : if(p_->environment()->stop_token.stop_requested())
406 : {
407 1 : skipped_ = true;
408 1 : return true;
409 : }
410 31 : completed_ = a_.await_ready();
411 31 : 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 : */
448 31 : decltype(auto) await_resume()
449 : {
450 31 : set_current_frame_allocator(
451 31 : p_->environment()->frame_allocator);
452 61 : if(skipped_ ||
453 52 : (!completed_ &&
454 22 : p_->environment()->stop_token.stop_requested()))
455 17 : 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.
461 5 : R r = a_.await_resume();
462 7 : if(std::get<0>(r) == cond::canceled &&
463 2 : p_->environment()->stop_token.stop_requested())
464 1 : throw detail::stop_requested_exception{};
465 8 : return r;
466 3 : }
467 : else
468 : {
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>
501 26 : 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 =
522 23 : 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
534 23 : if(next == h)
535 1 : completed_ = true;
536 : #if defined(__GNUC__) && !defined(__clang__)
537 : #pragma GCC diagnostic pop
538 : #endif
539 23 : return detail::symmetric_transfer(next);
540 : }
541 : else if constexpr (std::is_same_v<R, bool>)
542 : {
543 3 : if(!a_.await_suspend(h, p_->environment()))
544 : {
545 2 : completed_ = true;
546 2 : return false;
547 : }
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>
571 32 : auto transform_awaitable(Awaitable&& a)
572 : {
573 : using A = std::decay_t<Awaitable>;
574 : if constexpr (ContextAwaitable<A>)
575 : {
576 : return transform_awaiter<Awaitable>{
577 53 : std::forward<Awaitable>(a), this};
578 : }
579 : else
580 : {
581 : static_assert(sizeof(A) == 0,
582 : "requires ContextAwaitable");
583 : }
584 21 : }
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 : */
604 83 : ~quitter()
605 : {
606 83 : destroy_frame();
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 : */
616 16 : bool await_ready() const noexcept
617 : {
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 : */
635 12 : auto await_resume()
636 : {
637 12 : lent_ = false;
638 12 : if(h_.promise().stopped())
639 6 : throw detail::stop_requested_exception{};
640 6 : if(h_.promise().state_ == promise_type::completion::exception)
641 1 : std::rethrow_exception(h_.promise().ep_);
642 : if constexpr (! std::is_void_v<T>)
643 4 : return std::move(*h_.promise().result_);
644 : else
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 : */
663 16 : std::coroutine_handle<> await_suspend(
664 : std::coroutine_handle<> cont,
665 : await_context const* env)
666 : {
667 16 : h_.promise().set_continuation(cont);
668 16 : h_.promise().set_environment(env);
669 16 : lent_ = true;
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 : */
692 29 : std::coroutine_handle<promise_type> handle() const noexcept
693 : {
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 : */
709 27 : void release() noexcept
710 : {
711 27 : h_ = nullptr;
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 : */
735 38 : quitter(quitter&& other) noexcept
736 38 : : h_(std::exchange(other.h_, nullptr))
737 : {
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 : */
756 1 : quitter& operator=(quitter&& other) noexcept
757 : {
758 1 : if(this != &other)
759 : {
760 1 : destroy_frame();
761 1 : h_ = std::exchange(other.h_, nullptr);
762 : }
763 1 : return *this;
764 : }
765 :
766 : private:
767 : // Set while a parent is suspended awaiting this quitter.
768 : bool lent_ = false;
769 :
770 45 : explicit quitter(std::coroutine_handle<promise_type> h)
771 45 : : h_(h)
772 : {
773 45 : }
774 :
775 84 : void destroy_frame() noexcept
776 : {
777 84 : if(!h_)
778 66 : return;
779 18 : if(lent_ && !h_.done())
780 : {
781 : // Only the unwind of an abandoned chain destroys a frame
782 : // that is still awaited.
783 1 : auto* owner = h_.promise().environment()->owner;
784 1 : if(!owner || !owner->abandoned())
785 : std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
786 : }
787 18 : std::exchange(h_, nullptr).destroy();
788 18 : lent_ = false;
789 : }
790 : };
791 :
792 : } // namespace capy
793 : } // namespace boost
794 :
795 : #endif
|