TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/capy
9 : //
10 :
11 : #ifndef BOOST_CAPY_TASK_HPP
12 : #define BOOST_CAPY_TASK_HPP
13 :
14 : #include <boost/capy/detail/config.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 : #include <boost/capy/io_result.hpp>
22 :
23 : #include <exception>
24 : #include <optional>
25 : #include <type_traits>
26 : #include <utility>
27 : #include <variant>
28 :
29 : namespace boost {
30 : namespace capy {
31 :
32 : namespace detail {
33 :
34 : // Helper base for result storage and return_void/return_value
35 : template<typename T>
36 : struct task_return_base
37 : {
38 : std::optional<T> result_;
39 :
40 HIT 870 : void return_value(T value)
41 : {
42 870 : result_ = std::move(value);
43 870 : }
44 :
45 273 : T&& result() noexcept
46 : {
47 273 : return std::move(*result_);
48 : }
49 : };
50 :
51 : template<>
52 : struct task_return_base<void>
53 : {
54 1271 : void return_void()
55 : {
56 1271 : }
57 : };
58 :
59 : } // namespace detail
60 :
61 : /** Defers a coroutine body until awaited, then runs it inline on the caller's thread.
62 :
63 : Use `task<T>` as the return type for coroutines that perform I/O
64 : and return a value of type `T`. The coroutine body does not start
65 : executing until the task is awaited, enabling efficient composition
66 : without unnecessary eager execution.
67 :
68 : The task participates in the I/O awaitable protocol: when awaited,
69 : it receives the caller's executor and stop token, propagating them
70 : to nested `co_await` expressions. This enables cancellation and
71 : proper completion dispatch across executor boundaries.
72 :
73 : @par Await-effects
74 :
75 : Let `t` be a `task<T>`. `co_await t` always suspends the awaiting
76 : coroutine, then transfers control directly into the task's coroutine
77 : body on the current thread; no executor operation is posted. The task
78 : records the caller's environment (executor, stop token, and frame
79 : allocator) by pointer rather than copying it. It propagates that
80 : environment to every `co_await` inside the body.
81 :
82 : The body runs until it returns or exits via an exception. Control
83 : then transfers directly back to the awaiting coroutine, again
84 : without an executor operation.
85 :
86 : `task` never inspects the stop token; it only propagates it. A task
87 : body observes a stop request through the results of the operations it
88 : awaits, or by reading the token itself. See @ref quitter for a task
89 : that stops its own body.
90 :
91 : @par Await-returns
92 : The value the body passed to `co_return`, moved out of the task, or
93 : nothing when `T` is `void`.
94 :
95 : If the body exits via an unhandled exception, that exception is
96 : rethrown instead.
97 :
98 : @par Await-postcondition
99 : The task's coroutine has run to completion and is suspended at its
100 : final suspend point. The task still owns the frame, but not the
101 : result: the await moves it out, so a task must not be awaited twice.
102 :
103 : @par Thread Safety
104 : Distinct objects: Safe.
105 : Shared objects: Unsafe.
106 :
107 : @par Example
108 :
109 : @par !example example
110 :
111 :
112 : @tparam T The result type. Use `task<>` for `task<void>`.
113 :
114 : @see IoRunnable, IoAwaitable, run, run_async
115 : */
116 : template<typename T = void>
117 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
118 : task
119 : {
120 : /** Stores `task<T>`'s result and joins the I/O awaitable protocol via `io_awaitable_promise_base`.
121 :
122 : This is the promise object the compiler associates with a
123 : `task<T>` coroutine. It satisfies the coroutine promise
124 : requirements and participates in the I/O awaitable protocol via
125 : @ref io_awaitable_promise_base. It is part of the coroutine
126 : machinery and is not intended to be used directly by callers.
127 :
128 : Result storage and `return_value`/`return_void` are provided by
129 : `detail::task_return_base<T>`.
130 :
131 : @see io_awaitable_promise_base, IoRunnable
132 : */
133 : struct promise_type
134 : : io_awaitable_promise_base<promise_type>
135 : , detail::task_return_base<T>
136 : {
137 : private:
138 : friend task;
139 : union { std::exception_ptr ep_; };
140 : bool has_ep_;
141 :
142 : public:
143 : /// Construct the promise with no stored exception.
144 2766 : promise_type() noexcept
145 2766 : : has_ep_(false)
146 : {
147 2766 : }
148 :
149 : /// Destroy the promise, releasing any stored exception.
150 2766 : ~promise_type()
151 : {
152 2766 : if(has_ep_)
153 489 : ep_.~exception_ptr();
154 2766 : }
155 :
156 : /** Return the exception captured by the coroutine body, if any.
157 :
158 : @return The stored exception, or a null `std::exception_ptr`
159 : if the coroutine did not exit via an unhandled exception.
160 : */
161 2171 : std::exception_ptr exception() const noexcept
162 : {
163 2171 : if(has_ep_)
164 730 : return ep_;
165 1441 : return {};
166 : }
167 :
168 : /** Return the owning `task` for this coroutine.
169 :
170 : Called by the compiler to produce the object returned to the
171 : caller when the coroutine is created.
172 :
173 : @return A `task` owning the coroutine frame.
174 : */
175 2766 : task get_return_object()
176 : {
177 2766 : return task{std::coroutine_handle<promise_type>::from_promise(*this)};
178 : }
179 :
180 : /** Return the initial-suspend awaiter.
181 :
182 : The coroutine always suspends at the initial suspend point,
183 : so the body does not start until the task is awaited. When the
184 : body is resumed, the awaiter restores the thread-local frame
185 : allocator from the stored environment.
186 :
187 : @return An awaiter that suspends unconditionally.
188 : */
189 2766 : auto initial_suspend() noexcept
190 : {
191 : struct awaiter
192 : {
193 : promise_type* p_;
194 :
195 2766 : bool await_ready() const noexcept
196 : {
197 2766 : return false;
198 : }
199 :
200 2766 : void await_suspend(std::coroutine_handle<>) const noexcept
201 : {
202 2766 : }
203 :
204 2762 : void await_resume() const noexcept
205 : {
206 : // Restore TLS when body starts executing
207 2762 : set_current_frame_allocator(p_->environment()->frame_allocator);
208 2762 : }
209 : };
210 2766 : return awaiter{this};
211 : }
212 :
213 : /** Return the final-suspend awaiter.
214 :
215 : The coroutine always suspends at the final suspend point. The
216 : awaiter's `await_suspend` performs symmetric transfer to the
217 : stored continuation (consuming it), resuming the awaiting
218 : coroutine.
219 :
220 : @return An awaiter that suspends and transfers to the
221 : continuation.
222 : */
223 2630 : auto final_suspend() noexcept
224 : {
225 : struct awaiter
226 : {
227 : promise_type* p_;
228 :
229 2630 : bool await_ready() const noexcept
230 : {
231 2630 : return false;
232 : }
233 :
234 2630 : std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
235 : {
236 2630 : return p_->continuation();
237 : }
238 :
239 : void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
240 : };
241 2630 : return awaiter{this};
242 : }
243 :
244 : /** Capture the in-flight exception from the coroutine body.
245 :
246 : Called by the compiler when the coroutine body exits via an
247 : unhandled exception. The captured exception is rethrown when
248 : the task is awaited.
249 : */
250 489 : void unhandled_exception() noexcept
251 : {
252 489 : new (&ep_) std::exception_ptr(std::current_exception());
253 489 : has_ep_ = true;
254 489 : }
255 :
256 : /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
257 :
258 : Forwards the environment to the inner awaitable's
259 : environment-taking `await_suspend` and restores the
260 : thread-local frame allocator before the body resumes.
261 :
262 : @tparam Awaitable The awaitable being transformed.
263 : */
264 : template<class Awaitable>
265 : struct transform_awaiter
266 : {
267 : /// The wrapped awaitable, decayed and stored by value.
268 : std::decay_t<Awaitable> a_;
269 :
270 : /// The promise of the coroutine performing the `co_await`.
271 : promise_type* p_;
272 :
273 : /** Report whether the wrapped awaitable is already complete.
274 :
275 : @return The wrapped awaitable's own `await_ready` result:
276 : `true` if no suspension is needed.
277 : */
278 2877 : bool await_ready() noexcept
279 : {
280 2877 : return a_.await_ready();
281 : }
282 :
283 : /** Restore the frame allocator, then resume the wrapped
284 : awaitable.
285 :
286 : Reinstalls the thread-local frame allocator from the stored
287 : environment before the body continues. This is needed
288 : because the resumption may arrive on a different thread
289 : than the one that suspended.
290 :
291 : @return The wrapped awaitable's await-result, forwarded
292 : unchanged.
293 : */
294 2745 : decltype(auto) await_resume()
295 : {
296 : // Restore TLS before body resumes
297 2745 : set_current_frame_allocator(p_->environment()->frame_allocator);
298 2745 : return a_.await_resume();
299 : }
300 :
301 : /** Suspend by calling the wrapped awaitable with the
302 : environment.
303 :
304 : This is the plain `await_suspend` the compiler calls for the
305 : nested `co_await`. It forwards to the wrapped awaitable's
306 : @ref IoAwaitable overload, supplying the promise's stored
307 : environment as the second argument. It then hands back
308 : that call's result unchanged, so the wrapped awaitable's
309 : suspension decision, whatever form it takes, is preserved.
310 :
311 : @param h The coroutine performing the `co_await`.
312 :
313 : @return Whatever the wrapped awaitable's `await_suspend`
314 : returns. When that is a `std::coroutine_handle<>`, the
315 : handle is routed through `detail::symmetric_transfer`.
316 : On MSVC that helper resumes the handle on the current
317 : stack, and this function returns `void`, so the awaiting
318 : coroutine suspends unconditionally. On every other
319 : compiler the handle is returned unchanged for symmetric
320 : transfer.
321 : */
322 : template<class Promise>
323 2253 : auto await_suspend(std::coroutine_handle<Promise> h) noexcept
324 : {
325 : using R = decltype(a_.await_suspend(h, p_->environment()));
326 : if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
327 1253 : return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
328 : else
329 1000 : return a_.await_suspend(h, p_->environment());
330 : }
331 : };
332 :
333 : /** Transform a nested awaitable before `co_await`.
334 :
335 : Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
336 : coroutine's environment is propagated into it. A diagnostic
337 : is emitted if the awaitable does not satisfy @ref IoAwaitable.
338 :
339 : @param a The awaitable expression from `co_await a`.
340 :
341 : @return A @ref transform_awaiter wrapping `a`.
342 : */
343 : template<class Awaitable>
344 2877 : auto transform_awaitable(Awaitable&& a)
345 : {
346 : using A = std::decay_t<Awaitable>;
347 : if constexpr (IoAwaitable<A>)
348 : {
349 : return transform_awaiter<Awaitable>{
350 4408 : std::forward<Awaitable>(a), this};
351 : }
352 : else
353 : {
354 : static_assert(sizeof(A) == 0, "requires IoAwaitable");
355 : }
356 1531 : }
357 : };
358 :
359 : /** Handle to the owned coroutine frame.
360 :
361 : Null when the task is empty (for example after a move or after
362 : @ref release). Prefer @ref handle to read this; the member is
363 : public for use by the coroutine machinery.
364 : */
365 : std::coroutine_handle<promise_type> h_;
366 :
367 : /// Destroy the task and its coroutine frame if owned.
368 5856 : ~task()
369 : {
370 5856 : if(h_)
371 767 : h_.destroy();
372 5856 : }
373 :
374 : /** Report whether the awaited task is already complete.
375 :
376 : Always returns `false`; a task is lazy and has not started when
377 : it is awaited, so the awaiting coroutine always suspends.
378 :
379 : @return `false`.
380 : */
381 764 : bool await_ready() const noexcept
382 : {
383 764 : return false;
384 : }
385 :
386 : /** Return the task's result, rethrowing any captured exception.
387 :
388 : If the coroutine body exited via an unhandled exception, that
389 : exception is rethrown here. Otherwise the result is returned by
390 : move (for `task<T>`) or nothing is returned (for `task<void>`).
391 :
392 : @return The result value for non-void `T`; otherwise `void`.
393 :
394 : @throws The exception captured by the coroutine body, if any.
395 :
396 : @note Discarding an `io_result` silently drops the error
397 : code, so that overload is marked `[[nodiscard]]`.
398 : */
399 552 : [[nodiscard]] auto await_resume()
400 : requires detail::is_io_result_v<T>
401 : {
402 552 : if(h_.promise().has_ep_)
403 105 : std::rethrow_exception(h_.promise().ep_);
404 447 : return std::move(*h_.promise().result_);
405 : }
406 :
407 211 : auto await_resume()
408 : requires (! detail::is_io_result_v<T>)
409 : {
410 211 : if(h_.promise().has_ep_)
411 18 : std::rethrow_exception(h_.promise().ep_);
412 : if constexpr (! std::is_void_v<T>)
413 148 : return std::move(*h_.promise().result_);
414 : else
415 45 : return;
416 : }
417 :
418 : /** Start the task with the awaiting coroutine's context.
419 :
420 : Stores `cont` as the continuation to resume on completion.
421 : Stores `env` as the execution environment propagated to nested
422 : `co_await` expressions. Then transfers control into the task's
423 : coroutine body via the returned handle.
424 :
425 : @param cont The awaiting coroutine to resume when the task
426 : completes.
427 :
428 : @param env The execution environment (executor, stop token, and
429 : frame allocator). It must outlive the task.
430 :
431 : @return The task's coroutine handle, for symmetric transfer.
432 : */
433 683 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
434 : {
435 683 : h_.promise().set_continuation(cont);
436 683 : h_.promise().set_environment(env);
437 683 : return h_;
438 : }
439 :
440 : /** Return the coroutine handle.
441 :
442 : @note Do not call `destroy()` on the returned handle while the
443 : task is being awaited. The task's lifetime is normally managed
444 : by `run_async`, `run`, or the awaiting parent. Manually
445 : destroying a suspended task that another coroutine is awaiting
446 : produces undefined behavior. For cooperative cancellation, use
447 : `std::stop_token`.
448 :
449 : @return The coroutine handle.
450 : */
451 2082 : std::coroutine_handle<promise_type> handle() const noexcept
452 : {
453 2082 : return h_;
454 : }
455 :
456 : /** Release ownership of the coroutine frame.
457 :
458 : After calling this, destroying the task does not destroy the
459 : coroutine frame. The caller becomes responsible for the frame's
460 : lifetime.
461 :
462 : @note The caller may call `destroy()` on the released handle
463 : only when the task has not started or has fully completed.
464 : Destroying a suspended task that is being awaited produces
465 : undefined behavior.
466 :
467 : @par Postconditions
468 : `handle()` returns a null handle. Callers needing the
469 : original handle must save it, via @ref handle, before
470 : calling this.
471 : */
472 1999 : void release() noexcept
473 : {
474 1999 : h_ = nullptr;
475 1999 : }
476 :
477 : /** Copy construction is disabled; a task uniquely owns its frame.
478 :
479 : @param other The task that would be copied.
480 : */
481 : task(task const& other) = delete;
482 :
483 : /** Copy assignment is disabled; a task uniquely owns its frame.
484 :
485 : @param other The task that would be assigned from.
486 :
487 : @return A reference to `*this`.
488 : */
489 : task& operator=(task const& other) = delete;
490 :
491 : /** Construct by moving, transferring ownership of the frame.
492 :
493 : @par Postconditions
494 : `other` is empty and must not be awaited.
495 :
496 : @param other The task to move from.
497 : */
498 3090 : task(task&& other) noexcept
499 3090 : : h_(std::exchange(other.h_, nullptr))
500 : {
501 3090 : }
502 :
503 : /** Assign by moving, transferring ownership of the frame.
504 :
505 : If this task already owns a coroutine frame, that frame is
506 : destroyed first. Self-assignment is a no-op.
507 :
508 : @par Postconditions
509 : `other` is empty and must not be awaited.
510 :
511 : @param other The task to move from.
512 :
513 : @return A reference to `*this`.
514 : */
515 : task& operator=(task&& other) noexcept
516 : {
517 : if(this != &other)
518 : {
519 : if(h_)
520 : h_.destroy();
521 : h_ = std::exchange(other.h_, nullptr);
522 : }
523 : return *this;
524 : }
525 :
526 : private:
527 2766 : explicit task(std::coroutine_handle<promise_type> h)
528 2766 : : h_(h)
529 : {
530 2766 : }
531 : };
532 :
533 : } // namespace capy
534 : } // namespace boost
535 :
536 : #endif
|