include/boost/capy/quitter.hpp

100.0% Lines (81/81) 97.2% List of functions (104/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() :120 6x 100.0% 100.0% boost::capy::quitter<int>::promise_type::promise_type() :120 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::promise_type() :120 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::promise_type() :120 12x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::~promise_type() :126 6x 100.0% 100.0% boost::capy::quitter<int>::promise_type::~promise_type() :126 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::~promise_type() :126 1x 80.0% 83.0% boost::capy::quitter<void>::promise_type::~promise_type() :126 12x 100.0% 100.0% boost::capy::quitter<int>::promise_type::exception() const :138 10x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::exception() const :138 1x 80.0% 80.0% boost::capy::quitter<void>::promise_type::exception() const :138 15x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::stopped() const :147 6x 100.0% 100.0% boost::capy::quitter<int>::promise_type::stopped() const :147 5x 100.0% 100.0% boost::capy::quitter<void>::promise_type::stopped() const :147 1x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::get_return_object() :159 6x 100.0% 100.0% boost::capy::quitter<int>::promise_type::get_return_object() :159 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() :159 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::get_return_object() :159 12x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::initial_suspend() :176 6x 100.0% 100.0% boost::capy::quitter<int>::promise_type::initial_suspend() :176 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::initial_suspend() :176 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::initial_suspend() :176 12x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::final_suspend() :213 6x 100.0% 100.0% boost::capy::quitter<int>::promise_type::final_suspend() :213 14x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::final_suspend() :213 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::final_suspend() :213 12x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::promise_type::unhandled_exception() :243 5x 66.7% 73.0% boost::capy::quitter<int>::promise_type::unhandled_exception() :243 5x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::unhandled_exception() :243 0 0.0% 0.0% boost::capy::quitter<void>::promise_type::unhandled_exception() :243 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() :287 4x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<int> >::await_ready() :287 3x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_ready() :287 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter_test::bool_resume_awaitable>::await_ready() :287 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_ready() :287 1x 100.0% 100.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::yield_awaitable>::await_ready() :287 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::delay_awaitable>::await_ready() :287 1x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_ready() :287 3x 100.0% 100.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_ready() :287 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() :287 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() :287 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() :294 4x 83.3% 89.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<int> >::await_resume() :294 3x 83.3% 80.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_resume() :294 1x 83.3% 78.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::quitter_test::bool_resume_awaitable>::await_resume() :294 1x 83.3% 78.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_resume() :294 1x 83.3% 89.0% boost::capy::quitter<int>::promise_type::transform_awaiter<boost::capy::yield_awaitable>::await_resume() :294 1x 83.3% 78.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::delay_awaitable>::await_resume() :294 1x 83.3% 89.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::quitter<void> >::await_resume() :294 3x 83.3% 89.0% boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::stop_only_awaitable>::await_resume() :294 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() :294 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() :294 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>) :304 4x 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>) :304 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>) :304 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>) :304 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>) :304 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>) :304 1x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaiter<boost::capy::delay_awaitable>::await_suspend<boost::capy::quitter<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :304 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>) :304 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>) :304 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>) :304 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>) :304 0 0.0% 0.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&&) :331 4x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::quitter<int> >(boost::capy::quitter<int>&&) :331 3x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::quitter<void> >(boost::capy::quitter<void>&&) :331 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&&) :331 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::stop_only_awaitable>(boost::capy::stop_only_awaitable&&) :331 1x 100.0% 100.0% auto boost::capy::quitter<int>::promise_type::transform_awaitable<boost::capy::yield_awaitable>(boost::capy::yield_awaitable&&) :331 1x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::delay_awaitable>(boost::capy::delay_awaitable&&) :331 1x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::quitter<void> >(boost::capy::quitter<void>&&) :331 3x 100.0% 100.0% auto boost::capy::quitter<void>::promise_type::transform_awaitable<boost::capy::stop_only_awaitable>(boost::capy::stop_only_awaitable&&) :331 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&&) :331 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&&) :331 1x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::~quitter() :356 36x 100.0% 100.0% boost::capy::quitter<int>::~quitter() :356 26x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::~quitter() :356 2x 75.0% 75.0% boost::capy::quitter<void>::~quitter() :356 18x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::await_ready() const :363 6x 100.0% 100.0% boost::capy::quitter<int>::await_ready() const :363 5x 100.0% 100.0% boost::capy::quitter<void>::await_ready() const :363 4x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::await_resume() :374 6x 83.3% 72.0% boost::capy::quitter<int>::await_resume() :374 5x 100.0% 100.0% boost::capy::quitter<void>::await_resume() :374 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*) :387 6x 100.0% 100.0% boost::capy::quitter<int>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :387 5x 100.0% 100.0% boost::capy::quitter<void>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :387 4x 100.0% 100.0% boost::capy::quitter<int>::handle() const :407 11x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::handle() const :407 1x 100.0% 100.0% boost::capy::quitter<void>::handle() const :407 8x 100.0% 100.0% boost::capy::quitter<int>::release() :419 9x 100.0% 100.0% boost::capy::quitter<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::release() :419 1x 100.0% 100.0% boost::capy::quitter<void>::release() :419 8x 100.0% 100.0% boost::capy::quitter<boost::capy::io_result<unsigned long> >::quitter(boost::capy::quitter<boost::capy::io_result<unsigned long> >&&) :428 30x 100.0% 100.0% boost::capy::quitter<int>::quitter(boost::capy::quitter<int>&&) :428 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> > >&&) :428 1x 100.0% 100.0% boost::capy::quitter<void>::quitter(boost::capy::quitter<void>&&) :428 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>) :446 6x 100.0% 100.0% boost::capy::quitter<int>::quitter(std::__n4861::coroutine_handle<boost::capy::quitter<int>::promise_type>) :446 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>) :446 1x 100.0% 100.0% boost::capy::quitter<void>::quitter(std::__n4861::coroutine_handle<boost::capy::quitter<void>::promise_type>) :446 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 /** Stop-aware lazy coroutine task satisfying @ref IoRunnable.
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 @tparam T The result type. Use `quitter<>` for `quitter<void>`.
82
83 @see task, IoRunnable, IoAwaitable
84 */
85 template<typename T = void>
86 struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
87 quitter
88 {
89 /** The coroutine promise type for `quitter<T>`.
90
91 This is the promise object the compiler associates with a
92 `quitter<T>` coroutine. It satisfies the coroutine promise
93 requirements and participates in the I/O awaitable protocol via
94 @ref io_awaitable_promise_base. Unlike @ref task::promise_type,
95 its `transform_awaitable` checks the stop token before each
96 awaited result reaches the body, throwing an internal sentinel
97 exception that unwinds to a "stopped" completion. It is part of
98 the coroutine machinery and is not intended to be used directly
99 by callers.
100
101 Result storage and `return_value`/`return_void` are provided by
102 `detail::quitter_return_base<T>`.
103
104 @see io_awaitable_promise_base, IoRunnable
105 */
106 struct promise_type
107 : io_awaitable_promise_base<promise_type>
108 , detail::quitter_return_base<T>
109 {
110 private:
111 friend quitter;
112
113 enum class completion { running, value, exception, stopped };
114
115 union { std::exception_ptr ep_; };
116 completion state_;
117
118 public:
119 /// Construct the promise in the running state.
120 33x promise_type() noexcept
121 33x : state_(completion::running)
122 {
123 33x }
124
125 /// Destroy the promise, releasing any stored exception.
126 33x ~promise_type()
127 {
128 33x if(state_ == completion::exception ||
129 29x state_ == completion::stopped)
130 20x ep_.~exception_ptr();
131 33x }
132
133 /// Return a non-null exception_ptr when the coroutine threw
134 /// or was stopped. Stopped quitters report the sentinel
135 /// stop_requested_exception so that run_async routes to
136 /// the error handler instead of accessing a non-existent
137 /// result.
138 26x std::exception_ptr exception() const noexcept
139 {
140 26x if(state_ == completion::exception ||
141 20x state_ == completion::stopped)
142 20x return ep_;
143 6x return {};
144 }
145
146 /// True when the coroutine was stopped via the stop token.
147 12x bool stopped() const noexcept
148 {
149 12x return state_ == completion::stopped;
150 }
151
152 /** Return the owning `quitter` for this coroutine.
153
154 Called by the compiler to produce the object returned to the
155 caller when the coroutine is created.
156
157 @return A `quitter` owning the coroutine frame.
158 */
159 33x quitter get_return_object()
160 {
161 return quitter{
162 33x std::coroutine_handle<promise_type>::from_promise(*this)};
163 }
164
165 /** Return the initial-suspend awaiter.
166
167 The coroutine always suspends at the initial suspend point,
168 so the body does not start until the quitter is awaited. When
169 the body is resumed, the awaiter restores the thread-local
170 frame allocator and, if stop has already been requested,
171 throws the internal sentinel exception so the body never
172 runs and the coroutine completes as stopped.
173
174 @return An awaiter that suspends unconditionally.
175 */
176 33x auto initial_suspend() noexcept
177 {
178 struct awaiter
179 {
180 promise_type* p_;
181
182 bool await_ready() const noexcept
183 {
184 return false;
185 }
186
187 void await_suspend(std::coroutine_handle<>) const noexcept
188 {
189 }
190
191 // Potentially-throwing: checks the stop token before
192 // the coroutine body executes its first statement.
193 void await_resume() const
194 {
195 set_current_frame_allocator(
196 p_->environment()->frame_allocator);
197 if(p_->environment()->stop_token.stop_requested())
198 throw detail::stop_requested_exception{};
199 }
200 };
201 33x return awaiter{this};
202 }
203
204 /** Return the final-suspend awaiter.
205
206 The coroutine always suspends at the final suspend point. The
207 awaiter's `await_suspend` performs symmetric transfer to the
208 stored continuation, resuming the awaiting coroutine.
209
210 @return An awaiter that suspends and transfers to the
211 continuation.
212 */
213 33x auto final_suspend() noexcept
214 {
215 struct awaiter
216 {
217 promise_type* p_;
218
219 bool await_ready() const noexcept
220 {
221 return false;
222 }
223
224 std::coroutine_handle<> await_suspend(
225 std::coroutine_handle<>) const noexcept
226 {
227 return p_->continuation();
228 }
229
230 void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
231 };
232 33x return awaiter{this};
233 }
234
235 /** Capture the in-flight exception from the coroutine body.
236
237 Called by the compiler when the coroutine body exits via an
238 unhandled exception. The internal stop sentinel is recorded as
239 a stopped completion; any other exception is recorded as an
240 exception completion. The stored exception is surfaced (or
241 routed to the error handler) when the quitter is awaited or run.
242 */
243 20x void unhandled_exception()
244 {
245 try
246 {
247 20x throw;
248 }
249 20x catch(detail::stop_requested_exception const&)
250 {
251 // Store the exception_ptr so that run_async's
252 // invoke_impl routes to the error handler
253 // instead of accessing a non-existent result.
254 16x new (&ep_) std::exception_ptr(
255 std::current_exception());
256 16x state_ = completion::stopped;
257 }
258 4x catch(...)
259 {
260 4x new (&ep_) std::exception_ptr(
261 std::current_exception());
262 4x state_ = completion::exception;
263 }
264 20x }
265
266 //------------------------------------------------------
267 // transform_awaitable — the key difference from task<T>
268 //------------------------------------------------------
269
270 /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
271
272 Forwards the environment to the inner awaitable's
273 environment-taking `await_suspend` and restores the
274 thread-local frame allocator before the body resumes. Unlike
275 `task`'s, it also checks the stop token on resumption, throwing
276 the internal sentinel so a stop request unwinds the body before
277 it observes the I/O result.
278
279 @tparam Awaitable The awaitable being transformed.
280 */
281 template<class Awaitable>
282 struct transform_awaiter
283 {
284 std::decay_t<Awaitable> a_;
285 promise_type* p_;
286
287 21x bool await_ready() noexcept
288 {
289 21x return a_.await_ready();
290 }
291
292 // Check the stop token BEFORE the coroutine body
293 // sees the result of the I/O operation.
294 21x decltype(auto) await_resume()
295 {
296 21x set_current_frame_allocator(
297 21x p_->environment()->frame_allocator);
298 21x if(p_->environment()->stop_token.stop_requested())
299 13x throw detail::stop_requested_exception{};
300 8x return a_.await_resume();
301 }
302
303 template<class Promise>
304 19x auto await_suspend(
305 std::coroutine_handle<Promise> h) noexcept
306 {
307 using R = decltype(
308 a_.await_suspend(h, p_->environment()));
309 if constexpr (std::is_same_v<
310 R, std::coroutine_handle<>>)
311 18x return detail::symmetric_transfer(
312 36x a_.await_suspend(h, p_->environment()));
313 else
314 1x return a_.await_suspend(
315 2x h, p_->environment());
316 }
317 };
318
319 /** Transform a nested awaitable before `co_await`.
320
321 Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
322 coroutine's environment is propagated into it and the stop
323 token is checked on resumption. A diagnostic is emitted if the
324 awaitable does not satisfy @ref IoAwaitable.
325
326 @param a The awaitable expression from `co_await a`.
327
328 @return A @ref transform_awaiter wrapping `a`.
329 */
330 template<class Awaitable>
331 21x auto transform_awaitable(Awaitable&& a)
332 {
333 using A = std::decay_t<Awaitable>;
334 if constexpr (IoAwaitable<A>)
335 {
336 return transform_awaiter<Awaitable>{
337 38x std::forward<Awaitable>(a), this};
338 }
339 else
340 {
341 static_assert(sizeof(A) == 0,
342 "requires IoAwaitable");
343 }
344 17x }
345 };
346
347 /** Handle to the owned coroutine frame.
348
349 Null when the quitter is empty (for example after a move or after
350 @ref release). Prefer @ref handle to read this; the member is
351 public for use by the coroutine machinery.
352 */
353 std::coroutine_handle<promise_type> h_;
354
355 /// Destroy the quitter and its coroutine frame if owned.
356 82x ~quitter()
357 {
358 82x if(h_)
359 15x h_.destroy();
360 82x }
361
362 /// Return false; quitters are never immediately ready.
363 15x bool await_ready() const noexcept
364 {
365 15x return false;
366 }
367
368 /** Return the result, rethrow exception, or propagate stop.
369
370 When stopped, throws stop_requested_exception so that a
371 parent quitter also stops. A parent task<T> will see this
372 as an unhandled exception — by design.
373 */
374 12x auto await_resume()
375 {
376 12x if(h_.promise().stopped())
377 6x throw detail::stop_requested_exception{};
378 6x if(h_.promise().state_ == promise_type::completion::exception)
379 1x std::rethrow_exception(h_.promise().ep_);
380 if constexpr (! std::is_void_v<T>)
381 4x return std::move(*h_.promise().result_);
382 else
383 1x return;
384 }
385
386 /// Start execution with the caller's context.
387 15x std::coroutine_handle<> await_suspend(
388 std::coroutine_handle<> cont,
389 io_env const* env)
390 {
391 15x h_.promise().set_continuation(cont);
392 15x h_.promise().set_environment(env);
393 15x return h_;
394 }
395
396 /** Return the coroutine handle.
397
398 @note Do not call `destroy()` on the returned handle while
399 the quitter is being awaited. The quitter's lifetime is
400 normally managed by `run_async`, `run`, or the awaiting
401 parent; manually destroying a suspended quitter that another
402 coroutine is awaiting produces undefined behavior. For
403 cooperative cancellation, use `std::stop_token`.
404
405 @return The coroutine handle.
406 */
407 20x std::coroutine_handle<promise_type> handle() const noexcept
408 {
409 20x return h_;
410 }
411
412 /** Release ownership of the coroutine frame.
413
414 @note If the caller intends to call `destroy()` on the
415 released handle, it must do so only when the quitter has not
416 started or has fully completed. Destroying a suspended
417 quitter that is being awaited produces undefined behavior.
418 */
419 18x void release() noexcept
420 {
421 18x h_ = nullptr;
422 18x }
423
424 quitter(quitter const&) = delete;
425 quitter& operator=(quitter const&) = delete;
426
427 /// Construct by moving, transferring ownership.
428 49x quitter(quitter&& other) noexcept
429 49x : h_(std::exchange(other.h_, nullptr))
430 {
431 49x }
432
433 /// Assign by moving, transferring ownership.
434 quitter& operator=(quitter&& other) noexcept
435 {
436 if(this != &other)
437 {
438 if(h_)
439 h_.destroy();
440 h_ = std::exchange(other.h_, nullptr);
441 }
442 return *this;
443 }
444
445 private:
446 33x explicit quitter(std::coroutine_handle<promise_type> h)
447 33x : h_(h)
448 {
449 33x }
450 };
451
452 } // namespace capy
453 } // namespace boost
454
455 #endif
456