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