include/boost/capy/quitter.hpp

100.0% Lines (95 / 95, 1 excl) 94.2% Functions (113 / 120)
quitter.hpp
f(x) Functions (120)
Function Calls Lines Blocks
boost::capy::detail::quitter_return_base<int>::return_value(int) :50 9x 100.0% 100.0% boost::capy::detail::quitter_return_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::return_value(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) :50 1x 100.0% 100.0% boost::capy::detail::quitter_return_base<std::tuple<std::error_code, unsigned long> >::return_value(std::tuple<std::error_code, unsigned long>) :50 1x 100.0% 100.0% boost::capy::detail::quitter_return_base<int>::result() :55 4x 100.0% 100.0% boost::capy::detail::quitter_return_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::result() :55 1x 100.0% 100.0% boost::capy::detail::quitter_return_base<void>::return_void() :64 4x 100.0% 100.0% boost::capy::quitter<int>::promise_type::promise_type() :166 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::promise_type() :166 1x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::promise_type() :166 6x 100.0% 100.0% boost::capy::quitter<void>::promise_type::promise_type() :166 17x 100.0% 100.0% boost::capy::quitter<int>::promise_type::~promise_type() :172 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::~promise_type() :172 1x 80.0% 80.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::~promise_type() :172 6x 100.0% 100.0% boost::capy::quitter<void>::promise_type::~promise_type() :172 17x 100.0% 100.0% boost::capy::quitter<int>::promise_type::exception() const :191 10x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::exception() const :191 1x 80.0% 80.0% boost::capy::quitter<void>::promise_type::exception() const :191 17x 100.0% 100.0% boost::capy::quitter<int>::promise_type::stopped() const :205 5x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::stopped() const :205 6x 100.0% 100.0% boost::capy::quitter<void>::promise_type::stopped() const :205 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::get_return_object() :217 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::get_return_object() :217 1x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::get_return_object() :217 6x 100.0% 100.0% boost::capy::quitter<void>::promise_type::get_return_object() :217 17x 100.0% 100.0% boost::capy::quitter<int>::promise_type::initial_suspend() :234 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::initial_suspend() :234 1x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::initial_suspend() :234 6x 100.0% 100.0% boost::capy::quitter<void>::promise_type::initial_suspend() :234 17x 100.0% 100.0% boost::capy::quitter<int>::promise_type::final_suspend() :271 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::final_suspend() :271 1x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::final_suspend() :271 6x 100.0% 100.0% boost::capy::quitter<void>::promise_type::final_suspend() :271 14x 100.0% 100.0% boost::capy::quitter<int>::promise_type::unhandled_exception() :301 5x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::unhandled_exception() :301 0 0.0% 0.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::unhandled_exception() :301 5x 66.7% 73.0% boost::capy::quitter<void>::promise_type::unhandled_exception() :301 10x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<int> >::await_ready() :363 3x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_ready() :363 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter_test::bool_resume_awaitable>::await_ready() :363 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_ready() :363 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::yield_awaitable>::await_ready() :363 1x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_ready() :363 4x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::async_waker::wait_awaiter>::await_ready() :363 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::park_awaitable>::await_ready() :363 0 0.0% 0.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_ready() :363 3x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter_test::rmw_ready_awaitable>::await_ready() :363 2x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_ready() :363 4x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::task<void> >::await_ready() :363 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::test::stream::read_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable>::await_ready() :363 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::test::stream::write_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable>::await_ready() :363 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<int> >::await_resume() :394 3x 83.3% 80.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_resume() :394 1x 83.3% 78.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter_test::bool_resume_awaitable>::await_resume() :394 1x 83.3% 78.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_resume() :394 1x 83.3% 89.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::yield_awaitable>::await_resume() :394 1x 83.3% 78.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_resume() :394 4x 83.3% 89.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::async_waker::wait_awaiter>::await_resume() :394 1x 83.3% 89.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::park_awaitable>::await_resume() :394 0 0.0% 0.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_resume() :394 3x 83.3% 89.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter_test::rmw_ready_awaitable>::await_resume() :394 2x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_resume() :394 4x 83.3% 89.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::task<void> >::await_resume() :394 0 0.0% 0.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::test::stream::read_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable>::await_resume() :394 1x 83.3% 78.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::test::stream::write_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable>::await_resume() :394 1x 83.3% 78.0% auto boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<int> >::await_suspend<boost::capy::quitter<int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<int>::promise_type>) :431 3x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_suspend<boost::capy::quitter<int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<int>::promise_type>) :431 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter_test::bool_resume_awaitable>::await_suspend<boost::capy::quitter<int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<int>::promise_type>) :431 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_suspend<boost::capy::quitter<int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<int>::promise_type>) :431 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::yield_awaitable>::await_suspend<boost::capy::quitter<int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<int>::promise_type>) :431 1x 100.0% 100.0% auto boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_suspend<boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type>) :431 4x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::async_waker::wait_awaiter>::await_suspend<boost::capy::quitter<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :431 1x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::park_awaitable>::await_suspend<boost::capy::quitter<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :431 0 0.0% 0.0% auto boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_suspend<boost::capy::quitter<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :431 3x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter_test::rmw_ready_awaitable>::await_suspend<boost::capy::quitter<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :431 2x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_suspend<boost::capy::quitter<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :431 4x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::task<void> >::await_suspend<boost::capy::quitter<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :431 1x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::test::stream::read_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable>::await_suspend<boost::capy::quitter<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :431 0 0.0% 0.0% auto boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::test::stream::write_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable>::await_suspend<boost::capy::quitter<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :431 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::quitter<int> >(boost::capy::quitter<int>&&) :463 3x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::quitter<void> >(boost::capy::quitter<void>&&) :463 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::quitter_test::bool_resume_awaitable>(boost::capy::quitter_test::bool_resume_awaitable&&) :463 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::stop_only_awaitable>(boost::capy::stop_only_awaitable&&) :463 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::yield_awaitable>(boost::capy::yield_awaitable&&) :463 1x 100.0% 100.0% auto boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type::transform_awaitable<boost::capy::stop_only_awaitable>(boost::capy::stop_only_awaitable&&) :463 4x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::async_waker::wait_awaiter>(boost::capy::async_waker::wait_awaiter&&) :463 1x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::park_awaitable>(boost::capy::park_awaitable&&) :463 0 0.0% 0.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::quitter<void> >(boost::capy::quitter<void>&&) :463 3x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::quitter_test::rmw_ready_awaitable>(boost::capy::quitter_test::rmw_ready_awaitable&&) :463 2x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::stop_only_awaitable>(boost::capy::stop_only_awaitable&&) :463 4x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::task<void> >(boost::capy::task<void>&&) :463 1x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::test::stream::read_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable>(boost::capy::test::stream::read_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable&&) :463 1x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::test::stream::write_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable>(boost::capy::test::stream::write_some<boost::capy::mutable_buffer>(boost::capy::mutable_buffer)::awaitable&&) :463 1x 100.0% 100.0% boost::capy::quitter<int>::~quitter() :496 26x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::~quitter() :496 2x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::~quitter() :496 24x 100.0% 100.0% boost::capy::quitter<void>::~quitter() :496 24x 100.0% 100.0% boost::capy::quitter<int>::await_ready() const :508 5x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::await_ready() const :508 6x 100.0% 100.0% boost::capy::quitter<void>::await_ready() const :508 5x 100.0% 100.0% boost::capy::quitter<int>::await_resume() :527 5x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::await_resume() :527 6x 85.7% 72.0% boost::capy::quitter<void>::await_resume() :527 1x 71.4% 53.0% boost::capy::quitter<int>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::await_context const*) :555 5x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::await_context const*) :555 6x 100.0% 100.0% boost::capy::quitter<void>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::await_context const*) :555 5x 100.0% 100.0% boost::capy::quitter<int>::handle() const :584 11x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::handle() const :584 1x 100.0% 100.0% boost::capy::quitter<void>::handle() const :584 10x 100.0% 100.0% boost::capy::quitter<int>::release() :601 9x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::release() :601 1x 100.0% 100.0% boost::capy::quitter<void>::release() :601 10x 100.0% 100.0% boost::capy::quitter<int>::quitter(boost::capy::quitter<int>&&) :627 12x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::quitter(boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&&) :627 1x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::quitter(boost::capy::quitter<std::tuple<std::error_code, unsigned long> >&&) :627 18x 100.0% 100.0% boost::capy::quitter<void>::quitter(boost::capy::quitter<void>&&) :627 7x 100.0% 100.0% boost::capy::quitter<void>::operator=(boost::capy::quitter<void>&&) :648 1x 100.0% 100.0% boost::capy::quitter<int>::quitter(std::__n4861::coroutine_handle<boost::capy::quitter<int>::promise_type>) :662 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::quitter(std::__n4861::coroutine_handle<boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type>) :662 1x 100.0% 100.0% boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::quitter(std::__n4861::coroutine_handle<boost::capy::quitter<std::tuple<std::error_code, unsigned long> >::promise_type>) :662 6x 100.0% 100.0% boost::capy::quitter<void>::quitter(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :662 17x 100.0% 100.0%
Line TLA Hits 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/context_awaitable.hpp>
17 #include <boost/capy/ex/context_awaitable_promise_base.hpp>
18 #include <boost/capy/ex/await_context.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 11x void return_value(T value)
51 {
52 11x result_ = std::move(value);
53 11x }
54
55 5x T&& result() noexcept
56 {
57 5x return std::move(*result_);
58 }
59 };
60
61 template<>
62 struct quitter_return_base<void>
63 {
64 4x void return_void()
65 {
66 4x }
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 _ContextAwaitable cancellation_.
126
127 @tparam T The result type. Use `quitter<>` for `quitter<void>`.
128
129 @see @ref task, @ref ContextRunnable, @ref ContextAwaitable
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 context_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 @ref context_awaitable_promise_base, @ref ContextRunnable
151 */
152 struct promise_type
153 : context_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 38x promise_type() noexcept
167 38x : state_(completion::running)
168 {
169 38x }
170
171 /// Destroy the promise, releasing any stored exception.
172 38x ~promise_type()
173 {
174 38x if(state_ == completion::exception ||
175 34x state_ == completion::stopped)
176 20x ep_.~exception_ptr();
177 38x }
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 28x std::exception_ptr exception() const noexcept
192 {
193 28x if(state_ == completion::exception ||
194 22x state_ == completion::stopped)
195 20x return ep_;
196 8x 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 12x bool stopped() const noexcept
206 {
207 12x 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 38x quitter get_return_object()
218 {
219 return quitter{
220 38x 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 38x auto initial_suspend() noexcept
235 {
236 struct awaiter
237 {
238 promise_type* p_;
239
240 bool await_ready() const noexcept
241 {
242 return false;
243 }
244
245 void await_suspend(std::coroutine_handle<>) const noexcept
246 {
247 }
248
249 // Potentially-throwing: checks the stop token before
250 // the coroutine body executes its first statement.
251 void await_resume() const
252 {
253 set_current_frame_allocator(
254 p_->environment()->frame_allocator);
255 if(p_->environment()->stop_token.stop_requested())
256 throw detail::stop_requested_exception{};
257 }
258 };
259 38x 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 35x auto final_suspend() noexcept
272 {
273 struct awaiter
274 {
275 promise_type* p_;
276
277 bool await_ready() const noexcept
278 {
279 return false;
280 }
281
282 std::coroutine_handle<> await_suspend(
283 std::coroutine_handle<>) const noexcept
284 {
285 return p_->continuation();
286 }
287
288 void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
289 };
290 35x 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 20x void unhandled_exception()
302 {
303 try
304 {
305 20x throw;
306 }
307 20x 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 16x new (&ep_) std::exception_ptr(
313 std::current_exception());
314 16x state_ = completion::stopped;
315 }
316 4x catch(...)
317 {
318 4x new (&ep_) std::exception_ptr(
319 std::current_exception());
320 4x state_ = completion::exception;
321 }
322 20x }
323
324 //------------------------------------------------------
325 // transform_awaitable — the key difference from task<T>
326 //------------------------------------------------------
327
328 /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable.
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 Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
355 this always returns `false`, and @ref await_suspend asks
356 the wrapped awaitable instead. See
357 `detail::await_ready_then_suspend`.
358
359 @return The wrapped awaitable's own `await_ready` result:
360 `true` if no suspension is needed. Always `false` where
361 the workaround is on.
362 */
363 24x bool await_ready() noexcept
364 {
365 #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
366 return false;
367 #else
368 24x return a_.await_ready();
369 #endif
370 }
371
372 /** Restore the frame allocator, check for stop, then resume the
373 wrapped awaitable.
374
375 Reinstalls the thread-local frame allocator from the stored
376 environment, then reads the environment's stop token. If a
377 stop request is pending, the internal sentinel exception is
378 thrown from here. The body therefore never observes the
379 operation's result. It unwinds through its RAII destructors
380 to a stopped completion. This is the one place `quitter`
381 differs from @ref task::promise_type::transform_awaiter.
382
383 @return The wrapped awaitable's await-result, forwarded
384 unchanged, when no stop request is pending.
385
386 @par Exception Safety
387 Throws the library's internal stop sentinel if the
388 environment's stop token has a stop request pending. The
389 wrapped awaitable's `await_resume` is not called in that
390 case.
391 */
392 // Check the stop token BEFORE the coroutine body
393 // sees the result of the I/O operation.
394 23x decltype(auto) await_resume()
395 {
396 23x set_current_frame_allocator(
397 23x p_->environment()->frame_allocator);
398 23x if(p_->environment()->stop_token.stop_requested())
399 14x throw detail::stop_requested_exception{};
400 9x return a_.await_resume();
401 }
402
403 /** Suspend by calling the wrapped awaitable with the
404 environment.
405
406 This is the plain `await_suspend` the compiler calls for the
407 nested `co_await`. It forwards to the wrapped awaitable's
408 @ref ContextAwaitable overload, supplying the promise's stored
409 environment as the second argument. It then hands back
410 that call's result unchanged, so the wrapped awaitable's
411 suspension decision, whatever form it takes, is preserved.
412 The stop token is not checked here; @ref await_resume checks
413 it on the way back out.
414
415 @param h The coroutine performing the `co_await`.
416
417 @return Whatever the wrapped awaitable's `await_suspend`
418 returns. When that is a `std::coroutine_handle<>`, the
419 handle is routed through `detail::symmetric_transfer`.
420 Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
421 this function instead returns `bool` from
422 `detail::await_ready_then_suspend`. It first asks the
423 wrapped awaitable's `await_ready`. When that reports
424 ready, it returns `false` to continue without
425 suspending. Otherwise it resumes any returned handle on
426 the current stack and returns `true`. Where the
427 workaround is off, a returned handle is passed back
428 unchanged for symmetric transfer.
429 */
430 template<class Promise>
431 23x auto await_suspend(
432 std::coroutine_handle<Promise> h) noexcept
433 {
434 #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
435 return detail::await_ready_then_suspend(
436 a_, h, p_->environment());
437 #else
438 using R = decltype(
439 a_.await_suspend(h, p_->environment()));
440 if constexpr (std::is_same_v<
441 R, std::coroutine_handle<>>)
442 21x return detail::symmetric_transfer(
443 42x a_.await_suspend(h, p_->environment()));
444 else
445 2x return a_.await_suspend(
446 4x h, p_->environment());
447 #endif
448 }
449 };
450
451 /** Transform a nested awaitable before `co_await`.
452
453 Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the
454 coroutine's environment is propagated into it and the stop
455 token is checked on resumption. A diagnostic is emitted if the
456 awaitable does not satisfy @ref ContextAwaitable.
457
458 @param a The awaitable expression from `co_await a`.
459
460 @return A @ref transform_awaiter wrapping `a`.
461 */
462 template<class Awaitable>
463 24x auto transform_awaitable(Awaitable&& a)
464 {
465 using A = std::decay_t<Awaitable>;
466 if constexpr (ContextAwaitable<A>)
467 {
468 return transform_awaiter<Awaitable>{
469 43x std::forward<Awaitable>(a), this};
470 }
471 else
472 {
473 static_assert(sizeof(A) == 0,
474 "requires ContextAwaitable");
475 }
476 19x }
477 };
478
479 /** Handle to the owned coroutine frame.
480
481 Null when the quitter is empty (for example after a move or after
482 @ref release). Prefer @ref handle to read this; the member is
483 public for use by the coroutine machinery.
484 */
485 std::coroutine_handle<promise_type> h_;
486
487 /** Destroy the quitter and its coroutine frame if owned.
488
489 @par Preconditions
490 The frame is not lent: no parent is suspended awaiting it,
491 unless its chain is being unwound after an @ref abandon.
492 Destroying a lent frame otherwise calls `std::terminate`; end
493 a running chain by requesting stop, or by destroying its
494 execution context.
495 */
496 76x ~quitter()
497 {
498 76x destroy_frame();
499 76x }
500
501 /** Return false; quitters are never immediately ready.
502
503 A quitter is lazy and has not started when it is awaited, so the
504 awaiting coroutine always suspends.
505
506 @return `false`.
507 */
508 16x bool await_ready() const noexcept
509 {
510 16x return false;
511 }
512
513 /** Return the result, rethrow exception, or propagate stop.
514
515 When stopped, throws stop_requested_exception so that a
516 parent quitter also stops. A parent task<T> sees this
517 as an unhandled exception — by design.
518
519 @return The result value for non-void `T`, moved out of the
520 quitter; otherwise `void`.
521
522 @par Exception Safety
523 If the coroutine was stopped, the library's internal stop sentinel
524 is thrown. If the body exited via any other exception, that
525 exception is rethrown.
526 */
527 12x auto await_resume()
528 {
529 12x lent_ = false;
530 12x if(h_.promise().stopped())
531 6x throw detail::stop_requested_exception{};
532 6x if(h_.promise().state_ == promise_type::completion::exception)
533 1x std::rethrow_exception(h_.promise().ep_);
534 if constexpr (! std::is_void_v<T>)
535 4x return std::move(*h_.promise().result_);
536 else
537 1x return;
538 }
539
540 /** Start execution with the caller's context.
541
542 Stores `cont` as the continuation to resume on completion.
543 Stores `env` as the execution environment propagated to nested
544 `co_await` expressions. Then transfers control into the quitter's
545 coroutine body via the returned handle.
546
547 @param cont The awaiting coroutine to resume when the quitter
548 completes.
549
550 @param env The execution environment (executor, stop token, and
551 frame allocator). It must outlive the quitter.
552
553 @return The quitter's coroutine handle, for symmetric transfer.
554 */
555 16x std::coroutine_handle<> await_suspend(
556 std::coroutine_handle<> cont,
557 await_context const* env)
558 {
559 16x h_.promise().set_continuation(cont);
560 16x h_.promise().set_environment(env);
561 16x lent_ = true;
562 16x return h_;
563 }
564
565 /** Return the coroutine handle.
566
567 @note Do not call `destroy()` on the returned handle while the
568 quitter is being awaited: the awaiting frame still holds it and
569 destroys it again, which has undefined behavior. A holder that
570 gives the coroutine up passes its continuation to @ref abandon
571 instead. (Destroying or
572 move-assigning over the quitter object itself while its frame is
573 lent is a different operation, and calls `std::terminate`.)
574 The quitter's lifetime is normally managed by `run_async`, `run`,
575 or the awaiting parent. For cooperative cancellation, use
576 `std::stop_token`.
577
578 @note The operation, queue or primitive holding a suspended
579 coroutine gives it up by passing the continuation it keeps to
580 @ref abandon. `handle()` belongs to the owning quitter object.
581
582 @return The coroutine handle.
583 */
584 22x std::coroutine_handle<promise_type> handle() const noexcept
585 {
586 22x return h_;
587 }
588
589 /** Release ownership of the coroutine frame.
590
591 @note The caller may call `destroy()` on the released handle
592 only when the quitter has not started or has fully completed.
593 Destroying the released handle of a quitter that is being
594 awaited is not supported and has undefined behavior.
595
596 @par Postconditions
597 `handle()` returns a null handle. Callers needing the
598 original handle must save it, via @ref handle, before
599 calling this.
600 */
601 20x void release() noexcept
602 {
603 20x h_ = nullptr;
604 20x }
605
606 /** Copy construction is disabled; a quitter uniquely owns its frame.
607
608 @param other The quitter that would be copied.
609 */
610 quitter(quitter const& other) = delete;
611
612 /** Copy assignment is disabled; a quitter uniquely owns its frame.
613
614 @param other The quitter that would be assigned from.
615
616 @return A reference to `*this`.
617 */
618 quitter& operator=(quitter const& other) = delete;
619
620 /** Construct by moving, transferring ownership.
621
622 @par Postconditions
623 `other` is empty and must not be awaited.
624
625 @param other The quitter to move from.
626 */
627 38x quitter(quitter&& other) noexcept
628 38x : h_(std::exchange(other.h_, nullptr))
629 {
630 38x }
631
632 /** Assign by moving, transferring ownership.
633
634 If this quitter already owns a coroutine frame, that frame is
635 destroyed first. Self-assignment is a no-op.
636
637 @par Preconditions
638 The frame being replaced is not lent: no parent is suspended
639 awaiting it. Replacing a lent frame calls `std::terminate`.
640
641 @par Postconditions
642 `other` is empty and must not be awaited.
643
644 @param other The quitter to move from.
645
646 @return A reference to `*this`.
647 */
648 1x quitter& operator=(quitter&& other) noexcept
649 {
650 1x if(this != &other)
651 {
652 1x destroy_frame();
653 1x h_ = std::exchange(other.h_, nullptr);
654 }
655 1x return *this;
656 }
657
658 private:
659 // Set while a parent is suspended awaiting this quitter.
660 bool lent_ = false;
661
662 38x explicit quitter(std::coroutine_handle<promise_type> h)
663 38x : h_(h)
664 {
665 38x }
666
667 77x void destroy_frame() noexcept
668 {
669 77x if(!h_)
670 59x return;
671 18x if(lent_ && !h_.done())
672 {
673 // Only the unwind of an abandoned chain destroys a frame
674 // that is still awaited.
675 1x auto* owner = h_.promise().environment()->owner;
676 1x if(!owner || !owner->abandoned())
677 − std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
678 }
679 18x std::exchange(h_, nullptr).destroy();
680 18x lent_ = false;
681 }
682 };
683
684 } // namespace capy
685 } // namespace boost
686
687 #endif
688