include/boost/capy/quitter.hpp

100.0% Lines (81/0/81) 98.1% List of functions (105/0/107)
quitter.hpp
f(x) Functions (107)
Function Calls Lines Blocks
boost::capy::detail::quitter_return_base<boost::capy::io_result<unsigned long> >::return_value(boost::capy::io_result<unsigned long>) :50 1x 100.0% 100.0% 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<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 2x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::promise_type() :166 6x 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<void>::promise_type::promise_type() :166 12x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::~promise_type() :172 6x 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% 83.0% boost::capy::quitter<void>::promise_type::~promise_type() :172 12x 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 15x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::stopped() const :205 6x 100.0% 100.0% boost::capy::quitter<int>::promise_type::stopped() const :205 5x 100.0% 100.0% boost::capy::quitter<void>::promise_type::stopped() const :205 1x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::get_return_object() :217 6x 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<void>::promise_type::get_return_object() :217 12x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::initial_suspend() :234 6x 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<void>::promise_type::initial_suspend() :234 12x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::final_suspend() :271 6x 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<void>::promise_type::final_suspend() :271 12x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::unhandled_exception() :301 5x 66.7% 73.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<void>::promise_type::unhandled_exception() :301 10x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_ready() :357 5x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<int> >::await_ready() :357 3x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_ready() :357 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter_test::bool_resume_awaitable>::await_ready() :357 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_ready() :357 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::yield_awaitable>::await_ready() :357 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::async_waker::wait_awaiter>::await_ready() :357 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_ready() :357 3x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_ready() :357 4x 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() :357 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() :357 1x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_resume() :384 5x 83.3% 89.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<int> >::await_resume() :384 3x 83.3% 80.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_resume() :384 1x 83.3% 78.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter_test::bool_resume_awaitable>::await_resume() :384 1x 83.3% 78.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_resume() :384 1x 83.3% 89.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::yield_awaitable>::await_resume() :384 1x 83.3% 78.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::async_waker::wait_awaiter>::await_resume() :384 1x 83.3% 89.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_resume() :384 3x 83.3% 89.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_resume() :384 4x 83.3% 89.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() :384 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() :384 1x 83.3% 78.0% auto boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_suspend<boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type>) :417 5x 100.0% 100.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>) :417 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>) :417 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>) :417 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>) :417 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>) :417 1x 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>) :417 1x 100.0% 100.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>) :417 3x 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>) :417 4x 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>) :417 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>) :417 1x 100.0% 100.0% auto boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::transform_awaitable<boost::capy::stop_only_awaitable>(boost::capy::stop_only_awaitable&&) :444 5x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::quitter<int> >(boost::capy::quitter<int>&&) :444 3x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::quitter<void> >(boost::capy::quitter<void>&&) :444 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&&) :444 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::stop_only_awaitable>(boost::capy::stop_only_awaitable&&) :444 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::yield_awaitable>(boost::capy::yield_awaitable&&) :444 1x 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&&) :444 1x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::quitter<void> >(boost::capy::quitter<void>&&) :444 3x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::stop_only_awaitable>(boost::capy::stop_only_awaitable&&) :444 4x 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&&) :444 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&&) :444 1x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::~quitter() :469 36x 100.0% 100.0% boost::capy::quitter<int>::~quitter() :469 26x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::~quitter() :469 2x 75.0% 75.0% boost::capy::quitter<void>::~quitter() :469 18x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::await_ready() const :482 6x 100.0% 100.0% boost::capy::quitter<int>::await_ready() const :482 5x 100.0% 100.0% boost::capy::quitter<void>::await_ready() const :482 4x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::await_resume() :501 6x 83.3% 72.0% boost::capy::quitter<int>::await_resume() :501 5x 100.0% 100.0% boost::capy::quitter<void>::await_resume() :501 1x 66.7% 53.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :528 6x 100.0% 100.0% boost::capy::quitter<int>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :528 5x 100.0% 100.0% boost::capy::quitter<void>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :528 4x 100.0% 100.0% boost::capy::quitter<int>::handle() const :548 11x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::handle() const :548 1x 100.0% 100.0% boost::capy::quitter<void>::handle() const :548 8x 100.0% 100.0% boost::capy::quitter<int>::release() :565 9x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::release() :565 1x 100.0% 100.0% boost::capy::quitter<void>::release() :565 8x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::quitter(boost::capy::quitter<boost::capy::io_result<unsigned long> >&&) :591 30x 100.0% 100.0% boost::capy::quitter<int>::quitter(boost::capy::quitter<int>&&) :591 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> > >&&) :591 1x 100.0% 100.0% boost::capy::quitter<void>::quitter(boost::capy::quitter<void>&&) :591 6x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::quitter(std::__n4861::coroutine_handle<boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type>) :620 6x 100.0% 100.0% boost::capy::quitter<int>::quitter(std::__n4861::coroutine_handle<boost::capy::quitter<int>::promise_type>) :620 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>) :620 1x 100.0% 100.0% boost::capy::quitter<void>::quitter(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :620 12x 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/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 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 2x void return_void()
65 {
66 2x }
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 33x promise_type() noexcept
167 33x : state_(completion::running)
168 {
169 33x }
170
171 /// Destroy the promise, releasing any stored exception.
172 33x ~promise_type()
173 {
174 33x if(state_ == completion::exception ||
175 29x state_ == completion::stopped)
176 20x ep_.~exception_ptr();
177 33x }
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 26x std::exception_ptr exception() const noexcept
192 {
193 26x if(state_ == completion::exception ||
194 20x state_ == completion::stopped)
195 20x return ep_;
196 6x 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 33x quitter get_return_object()
218 {
219 return quitter{
220 33x 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 33x 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 33x 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 33x 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 33x 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 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 22x bool await_ready() noexcept
358 {
359 22x 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 22x decltype(auto) await_resume()
385 {
386 22x set_current_frame_allocator(
387 22x p_->environment()->frame_allocator);
388 22x if(p_->environment()->stop_token.stop_requested())
389 14x throw detail::stop_requested_exception{};
390 8x 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 21x 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 19x return detail::symmetric_transfer(
425 38x a_.await_suspend(h, p_->environment()));
426 else
427 2x return a_.await_suspend(
428 4x 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 22x 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 41x std::forward<Awaitable>(a), this};
451 }
452 else
453 {
454 static_assert(sizeof(A) == 0,
455 "requires IoAwaitable");
456 }
457 19x }
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 82x ~quitter()
470 {
471 82x if(h_)
472 15x h_.destroy();
473 82x }
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 15x bool await_ready() const noexcept
483 {
484 15x 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 12x auto await_resume()
502 {
503 12x if(h_.promise().stopped())
504 6x throw detail::stop_requested_exception{};
505 6x if(h_.promise().state_ == promise_type::completion::exception)
506 1x std::rethrow_exception(h_.promise().ep_);
507 if constexpr (! std::is_void_v<T>)
508 4x return std::move(*h_.promise().result_);
509 else
510 1x 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 15x std::coroutine_handle<> await_suspend(
529 std::coroutine_handle<> cont,
530 io_env const* env)
531 {
532 15x h_.promise().set_continuation(cont);
533 15x h_.promise().set_environment(env);
534 15x 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 20x std::coroutine_handle<promise_type> handle() const noexcept
549 {
550 20x 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 18x void release() noexcept
566 {
567 18x h_ = nullptr;
568 18x }
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 49x quitter(quitter&& other) noexcept
592 49x : h_(std::exchange(other.h_, nullptr))
593 {
594 49x }
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 33x explicit quitter(std::coroutine_handle<promise_type> h)
621 33x : h_(h)
622 {
623 33x }
624 };
625
626 } // namespace capy
627 } // namespace boost
628
629 #endif
630