TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Michael Vandeberg
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_QUITTER_HPP
11 : #define BOOST_CAPY_QUITTER_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/detail/stop_requested_exception.hpp>
15 : #include <boost/capy/concept/executor.hpp>
16 : #include <boost/capy/concept/io_awaitable.hpp>
17 : #include <boost/capy/ex/io_awaitable_promise_base.hpp>
18 : #include <boost/capy/ex/io_env.hpp>
19 : #include <boost/capy/ex/frame_allocator.hpp>
20 : #include <boost/capy/detail/await_suspend_helper.hpp>
21 :
22 : #include <exception>
23 : #include <optional>
24 : #include <type_traits>
25 : #include <utility>
26 :
27 : /* Stop-aware coroutine task.
28 :
29 : quitter<T> is identical to task<T> except that when the stop token
30 : is triggered, the coroutine body never sees the cancellation. The
31 : promise intercepts it on resume (in transform_awaiter::await_resume)
32 : and throws a sentinel exception that unwinds through RAII destructors
33 : to final_suspend. The parent sees a "stopped" completion.
34 :
35 : See doc/quitter.md for the full design rationale. */
36 :
37 : namespace boost {
38 : namespace capy {
39 :
40 : namespace detail {
41 :
42 : // Reuse the same return-value storage as task<T>.
43 : // task_return_base is defined in task.hpp, but quitter needs its own
44 : // copy to avoid a header dependency on task.hpp.
45 : template<typename T>
46 : struct quitter_return_base
47 : {
48 : std::optional<T> result_;
49 :
50 HIT 9 : void return_value(T value)
51 : {
52 9 : result_ = std::move(value);
53 9 : }
54 :
55 3 : T&& result() noexcept
56 : {
57 3 : return std::move(*result_);
58 : }
59 : };
60 :
61 : template<>
62 : struct quitter_return_base<void>
63 : {
64 1 : void return_void()
65 : {
66 1 : }
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 : struct promise_type
90 : : io_awaitable_promise_base<promise_type>
91 : , detail::quitter_return_base<T>
92 : {
93 : private:
94 : friend quitter;
95 :
96 : enum class completion { running, value, exception, stopped };
97 :
98 : union { std::exception_ptr ep_; };
99 : completion state_;
100 :
101 : public:
102 28 : promise_type() noexcept
103 28 : : state_(completion::running)
104 : {
105 28 : }
106 :
107 28 : ~promise_type()
108 : {
109 28 : if(state_ == completion::exception ||
110 26 : state_ == completion::stopped)
111 18 : ep_.~exception_ptr();
112 28 : }
113 :
114 : /// Return a non-null exception_ptr when the coroutine threw
115 : /// or was stopped. Stopped quitters report the sentinel
116 : /// stop_requested_exception so that run_async routes to
117 : /// the error handler instead of accessing a non-existent
118 : /// result.
119 22 : std::exception_ptr exception() const noexcept
120 : {
121 22 : if(state_ == completion::exception ||
122 18 : state_ == completion::stopped)
123 18 : return ep_;
124 4 : return {};
125 : }
126 :
127 : /// True when the coroutine was stopped via the stop token.
128 10 : bool stopped() const noexcept
129 : {
130 10 : return state_ == completion::stopped;
131 : }
132 :
133 28 : quitter get_return_object()
134 : {
135 : return quitter{
136 28 : std::coroutine_handle<promise_type>::from_promise(*this)};
137 : }
138 :
139 28 : auto initial_suspend() noexcept
140 : {
141 : struct awaiter
142 : {
143 : promise_type* p_;
144 :
145 28 : bool await_ready() const noexcept
146 : {
147 28 : return false;
148 : }
149 :
150 28 : void await_suspend(std::coroutine_handle<>) const noexcept
151 : {
152 28 : }
153 :
154 : // Potentially-throwing: checks the stop token before
155 : // the coroutine body executes its first statement.
156 28 : void await_resume() const
157 : {
158 28 : set_current_frame_allocator(
159 28 : p_->environment()->frame_allocator);
160 28 : if(p_->environment()->stop_token.stop_requested())
161 3 : throw detail::stop_requested_exception{};
162 25 : }
163 : };
164 28 : return awaiter{this};
165 : }
166 :
167 28 : auto final_suspend() noexcept
168 : {
169 : struct awaiter
170 : {
171 : promise_type* p_;
172 :
173 28 : bool await_ready() const noexcept
174 : {
175 28 : return false;
176 : }
177 :
178 28 : std::coroutine_handle<> await_suspend(
179 : std::coroutine_handle<>) const noexcept
180 : {
181 28 : return p_->continuation();
182 : }
183 :
184 MIS 0 : void await_resume() const noexcept
185 : {
186 0 : }
187 : };
188 HIT 28 : return awaiter{this};
189 : }
190 :
191 18 : void unhandled_exception()
192 : {
193 : try
194 : {
195 18 : throw;
196 : }
197 18 : catch(detail::stop_requested_exception const&)
198 : {
199 : // Store the exception_ptr so that run_async's
200 : // invoke_impl routes to the error handler
201 : // instead of accessing a non-existent result.
202 16 : new (&ep_) std::exception_ptr(
203 : std::current_exception());
204 16 : state_ = completion::stopped;
205 : }
206 2 : catch(...)
207 : {
208 2 : new (&ep_) std::exception_ptr(
209 : std::current_exception());
210 2 : state_ = completion::exception;
211 : }
212 18 : }
213 :
214 : //------------------------------------------------------
215 : // transform_awaitable — the key difference from task<T>
216 : //------------------------------------------------------
217 :
218 : template<class Awaitable>
219 : struct transform_awaiter
220 : {
221 : std::decay_t<Awaitable> a_;
222 : promise_type* p_;
223 :
224 18 : bool await_ready() noexcept
225 : {
226 18 : return a_.await_ready();
227 : }
228 :
229 : // Check the stop token BEFORE the coroutine body
230 : // sees the result of the I/O operation.
231 18 : decltype(auto) await_resume()
232 : {
233 18 : set_current_frame_allocator(
234 18 : p_->environment()->frame_allocator);
235 18 : if(p_->environment()->stop_token.stop_requested())
236 13 : throw detail::stop_requested_exception{};
237 5 : return a_.await_resume();
238 : }
239 :
240 : template<class Promise>
241 16 : auto await_suspend(
242 : std::coroutine_handle<Promise> h) noexcept
243 : {
244 : using R = decltype(
245 : a_.await_suspend(h, p_->environment()));
246 : if constexpr (std::is_same_v<
247 : R, std::coroutine_handle<>>)
248 16 : return detail::symmetric_transfer(
249 32 : a_.await_suspend(h, p_->environment()));
250 : else
251 MIS 0 : return a_.await_suspend(
252 0 : h, p_->environment());
253 : }
254 : };
255 :
256 : template<class Awaitable>
257 HIT 18 : auto transform_awaitable(Awaitable&& a)
258 : {
259 : using A = std::decay_t<Awaitable>;
260 : if constexpr (IoAwaitable<A>)
261 : {
262 : return transform_awaiter<Awaitable>{
263 33 : std::forward<Awaitable>(a), this};
264 : }
265 : else
266 : {
267 : static_assert(sizeof(A) == 0,
268 : "requires IoAwaitable");
269 : }
270 15 : }
271 : };
272 :
273 : std::coroutine_handle<promise_type> h_;
274 :
275 : /// Destroy the quitter and its coroutine frame if owned.
276 72 : ~quitter()
277 : {
278 72 : if(h_)
279 13 : h_.destroy();
280 72 : }
281 :
282 : /// Return false; quitters are never immediately ready.
283 13 : bool await_ready() const noexcept
284 : {
285 13 : return false;
286 : }
287 :
288 : /** Return the result, rethrow exception, or propagate stop.
289 :
290 : When stopped, throws stop_requested_exception so that a
291 : parent quitter also stops. A parent task<T> will see this
292 : as an unhandled exception — by design.
293 : */
294 10 : auto await_resume()
295 : {
296 10 : if(h_.promise().stopped())
297 6 : throw detail::stop_requested_exception{};
298 4 : if(h_.promise().state_ == promise_type::completion::exception)
299 MIS 0 : std::rethrow_exception(h_.promise().ep_);
300 : if constexpr (! std::is_void_v<T>)
301 HIT 4 : return std::move(*h_.promise().result_);
302 : else
303 MIS 0 : return;
304 : }
305 :
306 : /// Start execution with the caller's context.
307 HIT 13 : std::coroutine_handle<> await_suspend(
308 : std::coroutine_handle<> cont,
309 : io_env const* env)
310 : {
311 13 : h_.promise().set_continuation(cont);
312 13 : h_.promise().set_environment(env);
313 13 : return h_;
314 : }
315 :
316 : /// Return the coroutine handle.
317 17 : std::coroutine_handle<promise_type> handle() const noexcept
318 : {
319 17 : return h_;
320 : }
321 :
322 : /// Release ownership of the coroutine frame.
323 15 : void release() noexcept
324 : {
325 15 : h_ = nullptr;
326 15 : }
327 :
328 : quitter(quitter const&) = delete;
329 : quitter& operator=(quitter const&) = delete;
330 :
331 : /// Construct by moving, transferring ownership.
332 44 : quitter(quitter&& other) noexcept
333 44 : : h_(std::exchange(other.h_, nullptr))
334 : {
335 44 : }
336 :
337 : /// Assign by moving, transferring ownership.
338 : quitter& operator=(quitter&& other) noexcept
339 : {
340 : if(this != &other)
341 : {
342 : if(h_)
343 : h_.destroy();
344 : h_ = std::exchange(other.h_, nullptr);
345 : }
346 : return *this;
347 : }
348 :
349 : private:
350 28 : explicit quitter(std::coroutine_handle<promise_type> h)
351 28 : : h_(h)
352 : {
353 28 : }
354 : };
355 :
356 : } // namespace capy
357 : } // namespace boost
358 :
359 : #endif
|