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/concept/executor.hpp>
15 : #include <boost/capy/concept/context_awaitable.hpp>
16 : #include <boost/capy/cond.hpp>
17 : #include <boost/capy/continuation.hpp>
18 : #include <boost/capy/error.hpp>
19 : #include <boost/capy/ex/abandon.hpp>
20 : #include <boost/capy/ex/context_awaitable_promise_base.hpp>
21 : #include <boost/capy/ex/await_context.hpp>
22 : #include <boost/capy/ex/frame_alloc_mixin.hpp>
23 : #include <boost/capy/ex/frame_allocator.hpp>
24 : #include <boost/capy/detail/await_suspend_helper.hpp>
25 : #include <boost/capy/detail/quitter_stopped.hpp>
26 : #include <boost/capy/results.hpp>
27 :
28 : #include <coroutine>
29 : #include <exception>
30 : #include <optional>
31 : #include <system_error>
32 : #include <type_traits>
33 : #include <utility>
34 :
35 : /* Stop-aware coroutine task.
36 :
37 : quitter<T> is task<T>, except that a stop request ends its frames
38 : by abandonment instead of resuming them. Nothing in the body runs
39 : after the stop, so nothing can intercept it, and no exception is
40 : thrown inside the quitter.
41 :
42 : Segment. A quitter awaited by anything other than a quitter opens a
43 : segment in its await_suspend. The segment is one coroutine frame,
44 : quitter_segment, that is both the segment's owner (its chain_owner is
45 : the await_context::owner every frame in the segment sees) and the
46 : gate. A quitter awaited by a quitter joins the parent's segment. A
47 : quitter started directly by a launcher (run_async, run(ex)) runs in
48 : the launcher's segment; it still gets a gate, and the launcher's
49 : chain_owner is the one the gate abandons. The launcher creates the
50 : gate with launch_handle() and resumes the gate, not the quitter, so
51 : the gate checks the stop before the body starts. A launcher that
52 : resumes the quitter's own handle instead gets a gate when the body
53 : starts, with no such check.
54 :
55 : Gate. A quitter frame never hands its own handle to an operation. Its
56 : transform_awaiter records the frame as the gate's target and passes
57 : the gate's handle instead, so an operation completes into the gate.
58 : The gate then resumes the target, or, if a stop was requested,
59 : abandons the segment with chain_owner::abandon_for_stop. The
60 : operation has finished with the frame by then (the corosio rule: no
61 : frame is destroyed while an operation holds its continuation).
62 :
63 : Synchronous completions. The stop is checked before the
64 : inner await_ready, so a pending stop starts no work. An inner
65 : awaitable that completes without going through the gate (ready,
66 : await_suspend returning false or the gate's own handle) always has
67 : its await_resume called, so anything it acquired is handed over; the
68 : stop takes effect at the next suspension. The exception is a
69 : results<> holding cond::canceled while our stop is pending: the
70 : inner awaitable saw the stop first and acquired nothing (async_mutex,
71 : async_event and async_waker all answer a stop that lands between our
72 : check and theirs this way), so the segment is abandoned there. That
73 : result is taken early and kept in the awaiter, because await_resume
74 : runs in the resumed body, too late to abandon.
75 :
76 : Unwind. When the segment's owner is resumed after an abandon, it
77 : releases the opening quitter's handle and destroys the top frame,
78 : which destroys the frames below it. After a stop it then resumes the
79 : parent, whose co_await yields the stopped value (stopped_value<T>).
80 : After a teardown it abandons the parent, as run's boundary_trampoline
81 : does. A segment frame destroyed instead of resumed (posted to a
82 : closed executor) unwinds as teardown from its destructor.
83 :
84 : Why the gate is never resumed while it runs. Its body is
85 : `for(;;) co_await hop{}`, and every decision is made in
86 : hop::await_suspend, after the frame has suspended. Its handle is
87 : reachable only from the one operation a quitter frame of the segment
88 : is waiting on, and from the owner's post, which happens only when no
89 : operation holds it. Once await_suspend has handed the handle on, by
90 : returning it, resuming a target inline or posting, it touches nothing
91 : of the frame again, because another thread may already be running or
92 : destroying it. */
93 :
94 : namespace boost {
95 : namespace capy {
96 :
97 : template<typename T>
98 : struct quitter;
99 :
100 : namespace detail {
101 :
102 : template<typename T>
103 : struct quitter_return_base
104 : {
105 : std::optional<T> result_;
106 :
107 HIT 5245 : void return_value(T value)
108 : {
109 5245 : result_ = std::move(value);
110 5245 : }
111 :
112 5 : T&& result() noexcept
113 : {
114 5 : return std::move(*result_);
115 : }
116 : };
117 :
118 : template<>
119 : struct quitter_return_base<void>
120 : {
121 8 : void return_void()
122 : {
123 8 : }
124 : };
125 :
126 : /* What a stopped quitter<T> delivers to its parent.
127 :
128 : results<Ts...>: canceled, with each payload value-initialized.
129 : std::error_code: canceled.
130 : Any other T, void included: quitter_stopped, a std::system_error
131 : with canceled, thrown at the boundary. A void return would read as
132 : success. */
133 : template<class T>
134 : struct stopped_value
135 : {
136 29 : static T get()
137 : {
138 : #if defined(__cpp_exceptions)
139 29 : throw quitter_stopped();
140 : #else
141 : // sizeof(T*): T may be void.
142 : static_assert(sizeof(T*) == 0,
143 : "a stopped quitter<T> for a T other than results or "
144 : "std::error_code reports the stop by throwing, "
145 : "which needs exceptions");
146 : #endif
147 : }
148 : };
149 :
150 : template<class... Ts>
151 : struct stopped_value<results<Ts...>>
152 : {
153 : static_assert(
154 : (std::is_default_constructible_v<Ts> && ...),
155 : "a stopped quitter<results<Ts...>> value-initializes every Ts");
156 :
157 4806 : static results<Ts...> get()
158 : {
159 4806 : return results<Ts...>(make_error_code(error::canceled), Ts{}...);
160 : }
161 : };
162 :
163 : template<>
164 : struct stopped_value<std::error_code>
165 : {
166 1 : static std::error_code get() noexcept
167 : {
168 1 : return make_error_code(error::canceled);
169 : }
170 : };
171 :
172 : template<class T>
173 : struct is_quitter : std::false_type {};
174 :
175 : template<class T>
176 : struct is_quitter<quitter<T>> : std::true_type {};
177 :
178 : // A stop that can be carried out: requested, with an owner to abandon.
179 : // With no owner a quitter cannot stop, and runs on as a task would; a
180 : // launcher that runs quitters must bind a chain_owner.
181 : inline bool
182 20303 : quitter_stop_pending(await_context const* env) noexcept
183 : {
184 20303 : if(!env->stop_token.stop_requested())
185 15467 : return false;
186 4836 : BOOST_CAPY_ASSERT(env->owner &&
187 : "a quitter's environment needs an owner to stop");
188 4836 : return env->owner != nullptr;
189 : }
190 :
191 : struct quitter_opener;
192 :
193 : // One per segment: the owner and the gate in one coroutine frame.
194 : struct quitter_segment
195 : {
196 : struct promise_type;
197 :
198 : std::coroutine_handle<promise_type> h_;
199 : };
200 :
201 : // The part of a quitter that opened a segment, without its T.
202 : struct quitter_opener
203 : {
204 : // Takes the quitter's frame handle, leaving the quitter empty.
205 : std::coroutine_handle<> (*release_top_)(quitter_opener&) noexcept =
206 : nullptr;
207 : quitter_segment seg_{};
208 : bool stopped_ = false;
209 : // Set while a parent is suspended awaiting the quitter.
210 : bool lent_ = false;
211 : };
212 :
213 : // Not the chain's frame allocator: after a teardown this frame is freed
214 : // once the parent has been abandoned, when that allocator may be gone.
215 : // run's boundary_trampoline does the same.
216 : struct quitter_segment::promise_type
217 : : process_frame_mixin
218 : {
219 : // The segment's owner, when a quitter opened the segment.
220 : chain_owner owner_;
221 : // The environment of the segment's frames, when a quitter opened it.
222 : await_context env_;
223 : // The environment the frames run in: env_, or the launcher's.
224 : await_context const* frame_env_ = nullptr;
225 : // The awaiting coroutine, and its own owner for a teardown.
226 : continuation parent_;
227 : // The quitter frame waiting in the gate.
228 : std::coroutine_handle<> target_;
229 : // The quitter that opened the segment; null for a gate only.
230 : quitter_opener* opener_ = nullptr;
231 : // An unwind is still owed. Cleared when the top frame completes,
232 : // when the unwind runs, and by the quitter when it destroys this.
233 : bool active_ = false;
234 :
235 10090 : ~promise_type()
236 : {
237 : // Destroyed instead of resumed: a closed executor discarded the
238 : // owner's post. That is teardown, whatever the cause.
239 10090 : if(active_)
240 : {
241 1 : active_ = false;
242 : // This frame is going away; the quitter must not destroy it
243 : // again when the parent's unwind reaches it.
244 1 : opener_->seg_.h_ = nullptr;
245 1 : opener_->release_top_(*opener_).destroy();
246 1 : auto parent = parent_;
247 1 : capy::abandon(parent);
248 : }
249 10090 : }
250 :
251 10090 : quitter_segment get_return_object() noexcept
252 : {
253 : return quitter_segment{
254 10090 : std::coroutine_handle<promise_type>::from_promise(*this)};
255 : }
256 :
257 10090 : std::suspend_always initial_suspend() noexcept { return {}; }
258 : std::suspend_always final_suspend() noexcept { return {}; } // LCOV_EXCL_LINE the body never ends
259 : void return_void() noexcept {} // LCOV_EXCL_LINE the body never ends
260 : void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE the body never throws
261 :
262 10118 : std::coroutine_handle<> handle() noexcept
263 : {
264 10118 : return std::coroutine_handle<promise_type>::from_promise(*this);
265 : }
266 :
267 : // Abandon the segment for a stop. The caller holds the waiting
268 : // frame and touches nothing of it afterwards.
269 4834 : void stop() noexcept
270 : {
271 4834 : frame_env_->owner->abandon_for_stop();
272 4834 : }
273 :
274 14909 : std::coroutine_handle<> decide() noexcept
275 : {
276 14909 : if(opener_ && owner_.abandoned())
277 : {
278 4803 : active_ = false;
279 4803 : auto* op = opener_;
280 4803 : op->release_top_(*op).destroy();
281 4803 : if(owner_.stopped())
282 : {
283 4801 : op->stopped_ = true;
284 4801 : return parent_.h;
285 : }
286 2 : auto parent = parent_;
287 2 : capy::abandon(parent);
288 2 : return std::noop_coroutine();
289 : }
290 10106 : if(quitter_stop_pending(frame_env_))
291 : {
292 4825 : stop();
293 4825 : return std::noop_coroutine();
294 : }
295 5281 : return target_;
296 : }
297 : };
298 :
299 : struct quitter_hop
300 : {
301 14909 : bool await_ready() const noexcept { return false; }
302 :
303 14909 : auto await_suspend(
304 : std::coroutine_handle<quitter_segment::promise_type> h) noexcept
305 : {
306 14909 : return detail::symmetric_transfer(h.promise().decide());
307 : }
308 :
309 4824 : void await_resume() const noexcept {}
310 : };
311 :
312 : inline quitter_segment
313 10090 : make_quitter_segment()
314 : {
315 : for(;;)
316 : co_await quitter_hop{};
317 20180 : }
318 :
319 : } // namespace detail
320 :
321 : /** Defers a coroutine body until awaited, and ends it on a stop request without resuming it.
322 :
323 : A `quitter` is a @ref task whose author has chosen that a stop
324 : request ends it. When the stop token fires, the quitter's frames are
325 : not resumed: they are destroyed, from the top down, as an abandoned
326 : chain is (see @ref abandon). Only destructors run. Nothing in the
327 : body can observe or intercept the stop, not even `catch(...)`, and no
328 : exception is thrown inside the quitter. The stop is reported to the
329 : first coroutine above that is not a quitter, as a value: see
330 : Await-returns.
331 :
332 : A quitter stops only quitter frames. A @ref task below a quitter
333 : receives the stop request as usual, and returns through its own code
334 : before the quitter is destroyed. A task above a quitter receives the
335 : stopped value from `co_await`. A quitter awaited by a quitter is
336 : stopped together with it.
337 :
338 : Everything else (frame allocation, environment propagation,
339 : symmetric transfer, move semantics) is as for @ref task.
340 :
341 : @par Await-effects
342 :
343 : Let `q` be a `quitter<T>`. If the stop token in the awaiting
344 : environment has already been triggered, `co_await q` does not start
345 : the body, and await-returns the stopped value. Otherwise it suspends
346 : the awaiting coroutine and transfers control directly into the
347 : quitter's body on the current thread; no executor operation is
348 : posted. The quitter propagates the caller's executor, stop token and
349 : frame allocator to every `co_await` inside the body.
350 :
351 : At every `co_await` in the body, the stop token is checked before
352 : the awaited operation is started. An operation that completes
353 : asynchronously does not resume the body directly: the stop token is
354 : checked again first. If either check finds a stop, the body is not
355 : resumed. The quitter's frames are destroyed on the executor, and
356 : the coroutine that awaited the quitter is resumed with the stopped
357 : value. When `run_async` started the quitter, it reports the stop to
358 : its handlers instead, as Await-returns describes.
359 :
360 : An operation that completes without suspending always has
361 : its `await_resume` called, because its `await_ready` or
362 : `await_suspend` may already have acquired a resource, such as
363 : `async_mutex`'s lock, that only its `await_resume` hands over. If
364 : that result is a @ref results holding `cond::canceled` while a stop
365 : is pending, the operation saw the stop first and acquired nothing,
366 : so the quitter is stopped at that `co_await`. Any other result is
367 : handed to the body, and the stop takes effect at the next
368 : suspension. An operation whose result is not a @ref results can
369 : therefore hand the body one result it produced after seeing the
370 : stop.
371 :
372 : When the body returns or exits via an exception, control transfers
373 : directly back to the awaiting coroutine.
374 :
375 : @par Await-returns
376 :
377 : The value the body passed to `co_return`, moved out of the quitter,
378 : or nothing when `T` is `void`. If the body exits via an unhandled
379 : exception, that exception is rethrown instead.
380 :
381 : If the quitter was stopped, the stopped value instead:
382 :
383 : @li for `results<Ts...>`: `make_error_code(error::canceled)` with
384 : each `Ts` value-initialized; it compares equal to
385 : `cond::canceled`. Every `Ts` must be default-constructible.
386 : @li for `std::error_code`: `make_error_code(error::canceled)`.
387 : @li for any other `T`, `void` included: no value; an exception of
388 : an unspecified type derived from `std::system_error`, whose
389 : `code()` compares equal to `cond::canceled`, is thrown. A `void`
390 : return would read as success. Such a `quitter<T>` cannot be used
391 : where exceptions are disabled: use `quitter<results<>>` there.
392 :
393 : `co_await run(ex)(q)`, and the other @ref run overloads, yield the
394 : same stopped value. A quitter started by `run_async` reports a stop
395 : the same way. For `results<Ts...>` and `std::error_code`, the value
396 : handler receives the stopped value. For any other `T`, the error
397 : handler receives the exception. With no error handler, no handler
398 : is called and the stop is discarded. This also holds when the
399 : exception escapes a @ref task that awaited the quitter: the task is
400 : unwound, and with no error handler `run_async` discards the stop.
401 : Any other exception, a `std::system_error` with `cond::canceled`
402 : included, still calls `std::terminate`. That includes a
403 : `std::system_error` copy of the stop that a task throws.
404 :
405 : @par Await-postcondition
406 : The quitter's coroutine has completed, has been destroyed by a
407 : stop, or, when the stop was requested before it was awaited, was
408 : never started. A quitter must not be awaited twice.
409 :
410 : @par Remarks
411 : Supports _ContextAwaitable cancellation_. A quitter stops through the
412 : owner of its segment (@ref await_context::owner). A launcher that
413 : starts a quitter in an environment with no owner cannot stop it: a
414 : debug build asserts, and otherwise the quitter runs on as a
415 : @ref task would. A stop that arrives while
416 : the execution context is being shut down may end the quitter as a
417 : teardown instead, with nothing delivered. Stop the work, wait for it
418 : to finish, and only then destroy the context.
419 :
420 : A stop destroys quitter frames that are suspended at a `co_await`.
421 : GCC 15.2, and GCC 16 before 2026-07-02, at `-O0`, miscompile such a
422 : destruction when the `co_await` initializes a structured binding
423 : with an element that is not trivially destructible: the destructor
424 : of the never-constructed binding runs (GCC PR c++/124584). Bind the
425 : result to a named variable, and destructure it after the
426 : `co_await`:
427 :
428 : @code
429 : auto r = co_await mtx.scoped_lock();
430 : auto& [ec, guard] = r;
431 : @endcode
432 :
433 : @tparam T The result type. Use `quitter<>` for `quitter<void>`.
434 :
435 : @see @ref task, @ref ContextRunnable, @ref ContextAwaitable,
436 : @ref chain_owner
437 : */
438 : template<typename T = void>
439 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
440 : quitter
441 : : private detail::quitter_opener
442 : {
443 : /** Stores `quitter<T>`'s result and routes its awaits through the segment's gate.
444 :
445 : This is the promise object the compiler associates with a
446 : `quitter<T>` coroutine. It satisfies the coroutine promise
447 : requirements and participates in the I/O awaitable protocol via
448 : @ref context_awaitable_promise_base. Unlike @ref task::promise_type,
449 : its `transform_awaitable` checks the stop token before each
450 : awaited operation starts. It hands the operation the segment's
451 : gate as the continuation, not this coroutine. It is part of
452 : the coroutine machinery and is not intended to be used directly
453 : by callers.
454 :
455 : Result storage and `return_value`/`return_void` are provided by
456 : `detail::quitter_return_base<T>`.
457 :
458 : @see @ref context_awaitable_promise_base, @ref ContextRunnable
459 : */
460 : struct promise_type
461 : : context_awaitable_promise_base<promise_type>
462 : , detail::quitter_return_base<T>
463 : {
464 : private:
465 : friend quitter;
466 :
467 : template<class>
468 : friend struct quitter;
469 :
470 : std::exception_ptr ep_;
471 :
472 : // The gate of the segment this frame runs in.
473 : detail::quitter_segment::promise_type* seg_ = nullptr;
474 :
475 : // The gate this frame owns, when a launcher started it.
476 : detail::quitter_segment own_gate_{};
477 :
478 : // This frame opened its segment.
479 : bool top_ = false;
480 :
481 50 : void open_gate()
482 : {
483 50 : own_gate_ = detail::make_quitter_segment();
484 50 : seg_ = &own_gate_.h_.promise();
485 50 : seg_->frame_env_ = this->environment();
486 50 : }
487 :
488 : public:
489 : /// Construct the promise.
490 10204 : promise_type() noexcept = default;
491 :
492 : /// Destroy the promise, and the gate it owns, if any.
493 10204 : ~promise_type()
494 : {
495 10204 : if(own_gate_.h_)
496 50 : own_gate_.h_.destroy();
497 10204 : }
498 :
499 : /** Return the exception the body exited with, if any.
500 :
501 : @return The stored exception if the coroutine exited via an
502 : exception, otherwise a null `std::exception_ptr`.
503 : */
504 18 : std::exception_ptr exception() const noexcept
505 : {
506 18 : return ep_;
507 : }
508 :
509 : /** Return what a stopped `quitter<T>` delivers.
510 :
511 : Launchers that start a quitter directly call this when its
512 : segment was abandoned for a stop: see
513 : @ref chain_owner::stopped. Its result, or the exception it
514 : throws, is reported in place of the coroutine's own result.
515 :
516 : @return `make_error_code(error::canceled)` with value-initialized
517 : payloads for `results<Ts...>`, or
518 : `make_error_code(error::canceled)` for `std::error_code`.
519 :
520 : @throws std::system_error For any other `T`, `void` included,
521 : with a `code()` that compares equal to `cond::canceled`.
522 : */
523 4836 : static T stopped_result()
524 : {
525 4836 : return detail::stopped_value<T>::get();
526 : }
527 :
528 : /** Return the handle a launcher resumes to start the quitter.
529 :
530 : Launchers that start a quitter directly resume this handle,
531 : not the coroutine's own, so the stop is checked when the body
532 : would start rather than when the launcher was called. The
533 : handle is the quitter's gate, which either starts the body
534 : or, if a stop was requested by then, abandons the segment for
535 : the stop: see @ref chain_owner::abandon_for_stop.
536 :
537 : @par Preconditions
538 : The coroutine has not started, and its environment is set.
539 :
540 : @return The handle of the quitter's gate.
541 :
542 : @throws std::bad_alloc If the gate cannot be allocated.
543 : */
544 48 : std::coroutine_handle<> launch_handle()
545 : {
546 48 : open_gate();
547 48 : seg_->target_ =
548 48 : std::coroutine_handle<promise_type>::from_promise(*this);
549 48 : return seg_->handle();
550 : }
551 :
552 : /** Return the owning `quitter` for this coroutine.
553 :
554 : Called by the compiler to produce the object returned to the
555 : caller when the coroutine is created.
556 :
557 : @return A `quitter` owning the coroutine frame.
558 : */
559 10204 : quitter get_return_object()
560 : {
561 : return quitter{
562 10204 : std::coroutine_handle<promise_type>::from_promise(*this)};
563 : }
564 :
565 : /** Return the initial-suspend awaiter.
566 :
567 : The coroutine always suspends at the initial suspend point,
568 : so the body does not start until the quitter is awaited or
569 : started. When the body is resumed, the awaiter restores the
570 : thread-local frame allocator. A quitter started directly by a
571 : launcher that did not call @ref launch_handle creates its
572 : gate at that point.
573 :
574 : @return An awaiter that suspends unconditionally.
575 : */
576 10204 : auto initial_suspend() noexcept
577 : {
578 : struct awaiter
579 : {
580 : promise_type* p_;
581 :
582 10204 : bool await_ready() const noexcept
583 : {
584 10204 : return false;
585 : }
586 :
587 10204 : void await_suspend(std::coroutine_handle<>) const noexcept
588 : {
589 10204 : }
590 :
591 : // Allocates the gate of a quitter that a launcher started
592 : // without launch_handle(); a failure is reported as the
593 : // body's exception.
594 10193 : void await_resume() const
595 : {
596 10193 : set_current_frame_allocator(
597 10193 : p_->environment()->frame_allocator);
598 10193 : if(!p_->seg_)
599 2 : p_->open_gate();
600 10193 : }
601 : };
602 10204 : return awaiter{this};
603 : }
604 :
605 : /** Return the final-suspend awaiter.
606 :
607 : The coroutine always suspends at the final suspend point. The
608 : awaiter's `await_suspend` performs symmetric transfer to the
609 : stored continuation, resuming the awaiting coroutine.
610 :
611 : @return An awaiter that suspends and transfers to the
612 : continuation.
613 : */
614 5257 : auto final_suspend() noexcept
615 : {
616 : struct awaiter
617 : {
618 : promise_type* p_;
619 :
620 5257 : bool await_ready() const noexcept
621 : {
622 5257 : return false;
623 : }
624 :
625 5257 : std::coroutine_handle<> await_suspend(
626 : std::coroutine_handle<>) const noexcept
627 : {
628 : // The top frame completed: its segment owes no
629 : // unwind.
630 5257 : if(p_->top_)
631 5236 : p_->seg_->active_ = false;
632 5257 : return p_->continuation();
633 : }
634 :
635 : void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
636 : };
637 5257 : return awaiter{this};
638 : }
639 :
640 : /** Capture the in-flight exception from the coroutine body.
641 :
642 : Called by the compiler when the coroutine body exits via an
643 : unhandled exception. The exception is rethrown when the
644 : quitter is awaited, or routed to the error handler when it is
645 : run.
646 : */
647 4 : void unhandled_exception() noexcept
648 : {
649 4 : ep_ = std::current_exception();
650 4 : }
651 :
652 : /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable.
653 :
654 : Forwards the environment to the inner awaitable's
655 : environment-taking `await_suspend`, and restores the
656 : thread-local frame allocator before the body resumes. Unlike
657 : `task`'s, it checks the stop token before the inner awaitable
658 : is touched. It passes the inner awaitable the segment's gate
659 : as the continuation, not this coroutine.
660 :
661 : @tparam Awaitable The awaitable being transformed.
662 : */
663 : template<class Awaitable>
664 : struct transform_awaiter
665 : {
666 : /// The wrapped awaitable, decayed and stored by value.
667 : std::decay_t<Awaitable> a_;
668 :
669 : /// The promise of the coroutine performing the `co_await`.
670 : promise_type* p_;
671 :
672 : /// The wrapped awaitable's await-result type.
673 : using result_type = decltype(
674 : std::declval<std::decay_t<Awaitable>&>().await_resume());
675 :
676 : /// True when the await-result is a @ref results, which is
677 : /// checked for a canceled stop after a synchronous completion.
678 : static constexpr bool checks_canceled =
679 : detail::is_results_v<result_type>;
680 :
681 : /// True when the checks that may call the wrapped awaitable's
682 : /// `await_resume` early cannot throw.
683 : static constexpr bool checks_noexcept =
684 : !checks_canceled || noexcept(
685 : std::declval<std::decay_t<Awaitable>&>().await_resume());
686 :
687 : /// Empty stand-in for @ref result_ when nothing is checked.
688 : struct no_result {};
689 :
690 : /// The result of a synchronous completion, taken early by
691 : /// @ref stops_on_sync_completion and handed over by
692 : /// @ref await_resume.
693 : std::conditional_t<checks_canceled,
694 : std::optional<result_type>, no_result> result_{};
695 :
696 : /// True when the stop takes effect at this `co_await` without
697 : /// suspending: a stop was pending before the wrapped awaitable
698 : /// was touched, or it completed synchronously with a result
699 : /// canceled by that stop.
700 : bool stop_here_ = false;
701 :
702 : /** Take the result of a synchronous completion, and report
703 : whether the stop takes effect here instead.
704 :
705 : The wrapped awaitable's `await_resume` is always called,
706 : because its `await_ready` or `await_suspend` may already
707 : have acquired something that only `await_resume` hands
708 : over. A @ref results holding `cond::canceled` while our
709 : stop is pending saw the stop first and acquired nothing,
710 : so the quitter can be abandoned. Any other result is kept
711 : for @ref await_resume. A result that is not a
712 : @ref results is not taken early.
713 :
714 : @return `true` if the quitter is abandoned at this
715 : `co_await`.
716 : */
717 14 : bool stops_on_sync_completion() noexcept(checks_noexcept)
718 : {
719 : if constexpr (checks_canceled)
720 : {
721 9 : result_.emplace(a_.await_resume());
722 : // The stop check first: comparing with
723 : // cond::canceled goes through the error category's
724 : // virtual calls, and a stop is rarely pending.
725 14 : return detail::quitter_stop_pending(p_->environment()) &&
726 14 : std::get<0>(*result_) == cond::canceled;
727 : }
728 : else
729 : {
730 5 : return false;
731 : }
732 : }
733 :
734 : /** Report whether the `co_await` can continue without
735 : suspending.
736 :
737 : The stop token is checked first. If a stop is pending, the
738 : wrapped awaitable is never touched: this returns `false`,
739 : and @ref await_suspend ends the quitter. Otherwise the
740 : wrapped awaitable's own `await_ready` decides. A `true`
741 : result means it completed synchronously. Its
742 : `await_resume` then hands over its result, even if a stop
743 : arrives afterwards, unless that result is a @ref results
744 : canceled by the stop (see @ref stops_on_sync_completion).
745 :
746 : Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
747 : this always returns `false`, and @ref await_suspend does both
748 : steps instead. See `detail::await_ready_then_suspend`.
749 :
750 : @return `true` if no suspension is needed.
751 : */
752 10080 : bool await_ready() noexcept(checks_noexcept)
753 : {
754 : #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
755 : return false;
756 : #else
757 10080 : if(detail::quitter_stop_pending(p_->environment()))
758 : {
759 5 : stop_here_ = true;
760 5 : return false;
761 : }
762 10075 : if(!a_.await_ready())
763 10070 : return false;
764 5 : stop_here_ = stops_on_sync_completion();
765 5 : return !stop_here_;
766 : #endif
767 : }
768 :
769 : /** Restore the frame allocator, then return the wrapped
770 : awaitable's result.
771 :
772 : The body is resumed only when the wrapped awaitable
773 : completed synchronously, or completed through the gate
774 : with no stop requested.
775 :
776 : @return The wrapped awaitable's await-result, forwarded
777 : unchanged, or the result already taken from it.
778 : */
779 5248 : decltype(auto) await_resume()
780 : {
781 5248 : set_current_frame_allocator(
782 5248 : p_->environment()->frame_allocator);
783 : if constexpr (checks_canceled)
784 : {
785 5237 : if(result_)
786 6 : return result_type(std::move(*result_));
787 : }
788 5242 : return a_.await_resume();
789 : }
790 :
791 : /** Start the wrapped awaitable with the gate as its
792 : continuation, or end the quitter on a pending stop.
793 :
794 : If a stop is pending, the segment is abandoned for the
795 : stop and this coroutine stays suspended until it is
796 : destroyed. Otherwise this coroutine becomes the gate's
797 : target, and the wrapped awaitable's @ref ContextAwaitable
798 : overload is called with the gate's handle and the
799 : promise's environment. If that call completes without
800 : suspending (returning `false`, or the gate's own handle),
801 : this coroutine continues to @ref await_resume without
802 : passing through the gate, unless its result is a
803 : @ref results canceled by the stop (see
804 : @ref stops_on_sync_completion).
805 :
806 : @param h The coroutine performing the `co_await`.
807 :
808 : @return Where the workaround is off, the handle to resume:
809 : `h` to continue, a handle the wrapped awaitable returned,
810 : or `std::noop_coroutine()`. Where
811 : `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, `false`
812 : to continue and `true` to stay suspended; the stop check
813 : and the wrapped awaitable's `await_ready` both run here,
814 : through `detail::await_ready_then_suspend`.
815 : */
816 : template<class Promise>
817 10076 : auto await_suspend(
818 : std::coroutine_handle<Promise> h) noexcept(checks_noexcept)
819 : {
820 : // After the wrapped awaitable's await_suspend, the gate
821 : // may already have run and destroyed this frame on
822 : // another thread: only locals are used from then on,
823 : // except after a synchronous completion, which leaves
824 : // the frame to us.
825 10076 : auto* seg = p_->seg_;
826 10076 : auto const* env = p_->environment();
827 : #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
828 : if(detail::quitter_stop_pending(env))
829 : {
830 : seg->stop();
831 : return true;
832 : }
833 : seg->target_ = h;
834 : if(detail::await_ready_then_suspend(
835 : a_, seg->handle(), env))
836 : return true;
837 : if(stops_on_sync_completion())
838 : {
839 : seg->stop();
840 : return true;
841 : }
842 : return false;
843 : #else
844 10076 : if(stop_here_)
845 : {
846 6 : seg->stop();
847 6 : return std::coroutine_handle<>(std::noop_coroutine());
848 : }
849 10070 : seg->target_ = h;
850 10070 : std::coroutine_handle<> gate = seg->handle();
851 : using R = decltype(a_.await_suspend(gate, env));
852 : if constexpr (std::is_same_v<R, bool>)
853 : {
854 5 : if(!a_.await_suspend(gate, env))
855 4 : return continue_or_stop(h);
856 1 : return std::coroutine_handle<>(std::noop_coroutine());
857 : }
858 : else if constexpr (std::is_void_v<R>)
859 : {
860 : a_.await_suspend(gate, env);
861 : return std::coroutine_handle<>(std::noop_coroutine());
862 : }
863 : else
864 : {
865 10065 : std::coroutine_handle<> r = a_.await_suspend(gate, env);
866 10065 : if(r == gate)
867 5 : return continue_or_stop(h);
868 10060 : return r;
869 : }
870 : #endif
871 : }
872 :
873 : #if !BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
874 : /** After a synchronous completion in @ref await_suspend,
875 : continue to @ref await_resume, or end the quitter.
876 :
877 : @param h The coroutine performing the `co_await`.
878 :
879 : @return `h`, or `std::noop_coroutine()` when the quitter is
880 : abandoned here (see @ref stops_on_sync_completion).
881 : */
882 : std::coroutine_handle<>
883 9 : continue_or_stop(std::coroutine_handle<> h)
884 : noexcept(checks_noexcept)
885 : {
886 9 : if(!stops_on_sync_completion())
887 7 : return h;
888 2 : p_->seg_->stop();
889 2 : return std::noop_coroutine();
890 : }
891 : #endif
892 : };
893 :
894 : /** Awaiter for a nested `co_await` of a quitter.
895 :
896 : The awaited quitter joins this coroutine's segment instead of
897 : opening one: it runs in the same environment and gate, and a
898 : stop ends both together. Its completion transfers straight
899 : back to this coroutine.
900 :
901 : @tparam Q The awaited quitter type.
902 : */
903 : template<class Q>
904 : struct join_awaiter
905 : {
906 : /// The awaited quitter, stored by value.
907 : Q q_;
908 :
909 : /// The promise of the coroutine performing the `co_await`.
910 : promise_type* p_;
911 :
912 : /// Return `false`; the joined quitter has not started.
913 108 : bool await_ready() const noexcept
914 : {
915 108 : return false;
916 : }
917 :
918 : /** Start the joined quitter, or end the segment on a pending
919 : stop.
920 :
921 : @param h The coroutine performing the `co_await`.
922 :
923 : @return The joined quitter's handle, for symmetric
924 : transfer, or `std::noop_coroutine()` after a stop. Where
925 : the workaround is on, `void`.
926 : */
927 : template<class Promise>
928 108 : auto await_suspend(std::coroutine_handle<Promise> h) noexcept
929 : {
930 108 : auto const* env = p_->environment();
931 108 : if(detail::quitter_stop_pending(env))
932 : {
933 1 : p_->seg_->stop();
934 1 : return detail::symmetric_transfer(
935 2 : std::noop_coroutine());
936 : }
937 107 : return detail::symmetric_transfer(
938 214 : q_.join(h, p_->seg_, env));
939 : }
940 :
941 : /** Restore the frame allocator, then return the joined
942 : quitter's result.
943 :
944 : @return The joined quitter's await-result.
945 : */
946 4 : decltype(auto) await_resume()
947 : {
948 4 : set_current_frame_allocator(
949 4 : p_->environment()->frame_allocator);
950 4 : return q_.await_resume();
951 : }
952 : };
953 :
954 : /** Transform a nested awaitable before `co_await`.
955 :
956 : A quitter is wrapped in a @ref join_awaiter. Any other
957 : @ref ContextAwaitable is wrapped in a @ref transform_awaiter.
958 : A diagnostic is emitted for anything else.
959 :
960 : @param a The awaitable expression from `co_await a`.
961 :
962 : @return The awaiter wrapping `a`.
963 : */
964 : template<class Awaitable>
965 10188 : auto transform_awaitable(Awaitable&& a)
966 : {
967 : using A = std::decay_t<Awaitable>;
968 : if constexpr (detail::is_quitter<A>::value)
969 : {
970 216 : return join_awaiter<A>{std::forward<Awaitable>(a), this};
971 : }
972 : else if constexpr (ContextAwaitable<A>)
973 : {
974 : return transform_awaiter<Awaitable>{
975 20147 : std::forward<Awaitable>(a), this};
976 : }
977 : else
978 : {
979 : static_assert(sizeof(A) == 0,
980 : "requires ContextAwaitable");
981 : }
982 10169 : }
983 : };
984 :
985 : /** Handle to the owned coroutine frame.
986 :
987 : Null when the quitter is empty (for example after a move, after
988 : @ref release, or once a stop has destroyed the frame). Prefer
989 : @ref handle to read this; the member is public for use by the
990 : coroutine machinery.
991 : */
992 : std::coroutine_handle<promise_type> h_;
993 :
994 : /** Destroy the quitter and its coroutine frame if owned.
995 :
996 : @par Preconditions
997 : The frame is not lent: no parent is suspended awaiting it,
998 : unless its chain is being unwound after an @ref abandon.
999 : Destroying a lent frame otherwise calls `std::terminate`; end
1000 : a running chain by requesting stop, or by destroying its
1001 : execution context.
1002 : */
1003 30437 : ~quitter()
1004 : {
1005 30437 : destroy_frame();
1006 30437 : }
1007 :
1008 : /** Return false; quitters are never immediately ready.
1009 :
1010 : A quitter is lazy and has not started when it is awaited, so the
1011 : awaiting coroutine always suspends.
1012 :
1013 : @return `false`.
1014 : */
1015 10051 : bool await_ready() const noexcept
1016 : {
1017 10051 : return false;
1018 : }
1019 :
1020 : /** Return the result, rethrow the body's exception, or deliver the stopped value.
1021 :
1022 : @return The result value for non-void `T`, moved out of the
1023 : quitter, or the stopped value if the quitter was stopped.
1024 :
1025 : @par Exception Safety
1026 : If the body exited via an exception, that exception is rethrown.
1027 : If the quitter was stopped and `T` is not `results<Ts...>` or
1028 : `std::error_code`, a `std::system_error` whose `code()`
1029 : compares equal to `cond::canceled` is thrown.
1030 : */
1031 10045 : T await_resume()
1032 : {
1033 10045 : lent_ = false;
1034 10045 : if(stopped_)
1035 4805 : return promise_type::stopped_result();
1036 5240 : auto& p = h_.promise();
1037 5240 : if(p.ep_)
1038 1 : std::rethrow_exception(p.ep_);
1039 : if constexpr (! std::is_void_v<T>)
1040 5238 : return std::move(*p.result_);
1041 : }
1042 :
1043 : /** Open a segment and start the quitter with the caller's context.
1044 :
1045 : If the stop token in `env` has already been triggered, the body
1046 : is not started. `cont` is returned, so the caller resumes at once
1047 : with the stopped value.
1048 :
1049 : Otherwise the quitter opens a segment of its own. The segment's
1050 : owner destroys the quitter's frames if the segment is abandoned.
1051 : Its gate is what the quitter's awaits complete through. The body
1052 : runs with `env`'s executor, stop token and frame allocator, and
1053 : the segment's owner. Control then transfers into the body.
1054 :
1055 : @param cont The awaiting coroutine to resume when the quitter
1056 : completes or is stopped.
1057 :
1058 : @param env The execution environment. It must outlive the
1059 : quitter.
1060 :
1061 : @return The quitter's coroutine handle for symmetric transfer, or
1062 : `cont`.
1063 :
1064 : @throws std::bad_alloc If the segment's frame cannot be
1065 : allocated. When the awaiting coroutine is a @ref task, its
1066 : awaiter's `await_suspend` is `noexcept`, so `std::terminate` is
1067 : called instead.
1068 : */
1069 10044 : std::coroutine_handle<> await_suspend(
1070 : std::coroutine_handle<> cont,
1071 : await_context const* env)
1072 : {
1073 10044 : if(env->stop_token.stop_requested())
1074 : {
1075 4 : stopped_ = true;
1076 4 : return cont;
1077 : }
1078 10040 : seg_ = detail::make_quitter_segment();
1079 10040 : auto& sp = seg_.h_.promise();
1080 10040 : sp.owner_.bind(seg_.h_, env->executor);
1081 10040 : sp.env_ = *env;
1082 10040 : sp.env_.owner = &sp.owner_;
1083 10040 : sp.frame_env_ = &sp.env_;
1084 10040 : sp.parent_.h = cont;
1085 10040 : sp.parent_.owner = env->owner;
1086 10040 : sp.opener_ = this;
1087 10040 : sp.active_ = true;
1088 10040 : auto& p = h_.promise();
1089 10040 : p.seg_ = &sp;
1090 10040 : p.top_ = true;
1091 10040 : p.set_environment(&sp.env_);
1092 10040 : p.set_continuation(cont);
1093 10040 : lent_ = true;
1094 10040 : return h_;
1095 : }
1096 :
1097 : /** Return the coroutine handle.
1098 :
1099 : @note Do not call `destroy()` on the returned handle while the
1100 : quitter is being awaited: the awaiting frame still holds it and
1101 : destroys it again, which has undefined behavior. A holder that
1102 : gives the coroutine up passes its continuation to @ref abandon
1103 : instead. (Destroying or
1104 : move-assigning over the quitter object itself while its frame is
1105 : lent is a different operation, and calls `std::terminate`.)
1106 : The quitter's lifetime is normally managed by `run_async`, `run`,
1107 : or the awaiting parent.
1108 :
1109 : @note The operation, queue or primitive holding a suspended
1110 : coroutine gives it up by passing the continuation it keeps to
1111 : @ref abandon. `handle()` belongs to the owning quitter object.
1112 :
1113 : @note To run the quitter for an awaiting coroutine, call its
1114 : `await_suspend`, which opens a segment for it. Do not resume
1115 : this handle with the awaiting coroutine's environment: a stop
1116 : then abandons the awaiting coroutine's segment, and a @ref task
1117 : chain in it is torn down with no handler called. When
1118 : `run_async` or `run` owns that segment, a debug build asserts.
1119 : A launcher that resumes the handle itself must own the segment:
1120 : see @ref await_context::owner.
1121 :
1122 : @return The coroutine handle.
1123 : */
1124 65 : std::coroutine_handle<promise_type> handle() const noexcept
1125 : {
1126 65 : return h_;
1127 : }
1128 :
1129 : /** Release ownership of the coroutine frame.
1130 :
1131 : @note The caller may call `destroy()` on the released handle
1132 : only when the quitter has not started or has fully completed.
1133 : Destroying the released handle of a quitter that is being
1134 : awaited is not supported and has undefined behavior.
1135 :
1136 : @par Postconditions
1137 : `handle()` returns a null handle. Callers needing the
1138 : original handle must save it, via @ref handle, before
1139 : calling this.
1140 : */
1141 50 : void release() noexcept
1142 : {
1143 50 : h_ = nullptr;
1144 50 : }
1145 :
1146 : /** Copy construction is disabled; a quitter uniquely owns its frame.
1147 :
1148 : @param other The quitter that would be copied.
1149 : */
1150 : quitter(quitter const& other) = delete;
1151 :
1152 : /** Copy assignment is disabled; a quitter uniquely owns its frame.
1153 :
1154 : @param other The quitter that would be assigned from.
1155 :
1156 : @return A reference to `*this`.
1157 : */
1158 : quitter& operator=(quitter const& other) = delete;
1159 :
1160 : /** Construct by moving, transferring ownership.
1161 :
1162 : @par Preconditions
1163 : `other` is not being awaited.
1164 :
1165 : @par Postconditions
1166 : `other` is empty and must not be awaited.
1167 :
1168 : @param other The quitter to move from.
1169 : */
1170 20233 : quitter(quitter&& other) noexcept
1171 20233 : : h_(std::exchange(other.h_, nullptr))
1172 : {
1173 20233 : release_top_ = other.release_top_;
1174 20233 : }
1175 :
1176 : /** Assign by moving, transferring ownership.
1177 :
1178 : If this quitter already owns a coroutine frame, that frame is
1179 : destroyed first. Self-assignment is a no-op.
1180 :
1181 : @par Preconditions
1182 : The frame being replaced is not lent: no parent is suspended
1183 : awaiting it. Replacing a lent frame calls `std::terminate`.
1184 : `other` is not being awaited.
1185 :
1186 : @par Postconditions
1187 : `other` is empty and must not be awaited.
1188 :
1189 : @param other The quitter to move from.
1190 :
1191 : @return A reference to `*this`.
1192 : */
1193 1 : quitter& operator=(quitter&& other) noexcept
1194 : {
1195 1 : if(this != &other)
1196 : {
1197 1 : destroy_frame();
1198 1 : h_ = std::exchange(other.h_, nullptr);
1199 1 : stopped_ = false;
1200 : }
1201 1 : return *this;
1202 : }
1203 :
1204 : private:
1205 : template<class>
1206 : friend struct quitter;
1207 :
1208 10204 : explicit quitter(std::coroutine_handle<promise_type> h)
1209 10204 : : h_(h)
1210 : {
1211 10204 : release_top_ = &release_top;
1212 10204 : }
1213 :
1214 : static std::coroutine_handle<>
1215 4804 : release_top(detail::quitter_opener& o) noexcept
1216 : {
1217 4804 : return std::exchange(static_cast<quitter&>(o).h_, nullptr);
1218 : }
1219 :
1220 : // Start as a quitter joining the segment of the quitter `parent`.
1221 107 : std::coroutine_handle<> join(
1222 : std::coroutine_handle<> parent,
1223 : detail::quitter_segment::promise_type* seg,
1224 : await_context const* env) noexcept
1225 : {
1226 107 : auto& p = h_.promise();
1227 107 : p.seg_ = seg;
1228 107 : p.set_environment(env);
1229 107 : p.set_continuation(parent);
1230 107 : lent_ = true;
1231 107 : return h_;
1232 : }
1233 :
1234 30438 : void destroy_frame() noexcept
1235 : {
1236 30438 : if(h_)
1237 : {
1238 5350 : if(lent_ && !h_.done())
1239 : {
1240 : // Only the unwind of an abandoned chain destroys a frame
1241 : // that is still awaited.
1242 103 : auto* owner = h_.promise().environment()->owner;
1243 103 : if(!owner || !owner->abandoned())
1244 : std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
1245 : }
1246 5350 : std::exchange(h_, nullptr).destroy();
1247 : }
1248 30438 : if(seg_.h_)
1249 : {
1250 10039 : seg_.h_.promise().active_ = false;
1251 10039 : std::exchange(seg_.h_, nullptr).destroy();
1252 : }
1253 30438 : lent_ = false;
1254 30438 : }
1255 : };
1256 :
1257 : } // namespace capy
1258 : } // namespace boost
1259 :
1260 : #endif
|