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/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 : #include <boost/capy/results.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 904 : void return_value(T value)
41 : {
42 904 : result_ = std::move(value);
43 904 : }
44 :
45 283 : T&& result() noexcept
46 : {
47 283 : return std::move(*result_);
48 : }
49 : };
50 :
51 : template<>
52 : struct task_return_base<void>
53 : {
54 4097 : void return_void()
55 : {
56 4097 : }
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 @ref ContextRunnable, @ref ContextAwaitable, @ref run, @ref 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 `context_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 context_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 @ref context_awaitable_promise_base, @ref ContextRunnable
132 : */
133 : struct promise_type
134 : : context_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 109232 : promise_type() noexcept
145 109232 : : has_ep_(false)
146 : {
147 109232 : }
148 :
149 : /// Destroy the promise, releasing any stored exception.
150 109232 : ~promise_type()
151 : {
152 109232 : if(has_ep_)
153 490 : ep_.~exception_ptr();
154 109232 : }
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 4977 : std::exception_ptr exception() const noexcept
162 : {
163 4977 : if(has_ep_)
164 732 : return ep_;
165 4245 : 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 109232 : task get_return_object()
176 : {
177 109232 : 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 109232 : auto initial_suspend() noexcept
190 : {
191 : struct awaiter
192 : {
193 : promise_type* p_;
194 :
195 109232 : bool await_ready() const noexcept
196 : {
197 109232 : return false;
198 : }
199 :
200 109232 : void await_suspend(std::coroutine_handle<>) const noexcept
201 : {
202 109232 : }
203 :
204 108149 : void await_resume() const noexcept
205 : {
206 : // Restore TLS when body starts executing
207 108149 : set_current_frame_allocator(p_->environment()->frame_allocator);
208 108149 : }
209 : };
210 109232 : 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 5491 : auto final_suspend() noexcept
224 : {
225 : struct awaiter
226 : {
227 : promise_type* p_;
228 :
229 5491 : bool await_ready() const noexcept
230 : {
231 5491 : return false;
232 : }
233 :
234 5491 : std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
235 : {
236 5491 : return p_->continuation();
237 : }
238 :
239 : void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
240 : };
241 5491 : 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 490 : void unhandled_exception() noexcept
251 : {
252 490 : new (&ep_) std::exception_ptr(std::current_exception());
253 490 : has_ep_ = true;
254 490 : }
255 :
256 : /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable.
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 : Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
276 : this always returns `false`, and @ref await_suspend asks
277 : the wrapped awaitable instead. See
278 : `detail::await_ready_then_suspend`.
279 :
280 : @return The wrapped awaitable's own `await_ready` result:
281 : `true` if no suspension is needed. Always `false` where
282 : the workaround is on.
283 : */
284 108222 : bool await_ready() noexcept
285 : {
286 : #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
287 : return false;
288 : #else
289 108222 : return a_.await_ready();
290 : #endif
291 : }
292 :
293 : /** Restore the frame allocator, then resume the wrapped
294 : awaitable.
295 :
296 : Reinstalls the thread-local frame allocator from the stored
297 : environment before the body continues. This is needed
298 : because the resumption may arrive on a different thread
299 : than the one that suspended.
300 :
301 : @return The wrapped awaitable's await-result, forwarded
302 : unchanged.
303 : */
304 5564 : decltype(auto) await_resume()
305 : {
306 : // Restore TLS before body resumes
307 5564 : set_current_frame_allocator(p_->environment()->frame_allocator);
308 5564 : return a_.await_resume();
309 : }
310 :
311 : /** Suspend by calling the wrapped awaitable with the
312 : environment.
313 :
314 : This is the plain `await_suspend` the compiler calls for the
315 : nested `co_await`. It forwards to the wrapped awaitable's
316 : @ref ContextAwaitable overload, supplying the promise's stored
317 : environment as the second argument. It then hands back
318 : that call's result unchanged, so the wrapped awaitable's
319 : suspension decision, whatever form it takes, is preserved.
320 :
321 : @param h The coroutine performing the `co_await`.
322 :
323 : @return Whatever the wrapped awaitable's `await_suspend`
324 : returns. When that is a `std::coroutine_handle<>`, the
325 : handle is routed through `detail::symmetric_transfer`.
326 : Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
327 : this function instead returns `bool` from
328 : `detail::await_ready_then_suspend`. It first asks the
329 : wrapped awaitable's `await_ready`. When that reports
330 : ready, it returns `false` to continue without
331 : suspending. Otherwise it resumes any returned handle on
332 : the current stack and returns `true`. Where the
333 : workaround is off, a returned handle is passed back
334 : unchanged for symmetric transfer.
335 : */
336 : template<class Promise>
337 107666 : auto await_suspend(std::coroutine_handle<Promise> h) noexcept
338 : {
339 : #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
340 : return detail::await_ready_then_suspend(a_, h, p_->environment());
341 : #else
342 : using R = decltype(a_.await_suspend(h, p_->environment()));
343 : if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
344 106624 : return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
345 : else
346 1042 : return a_.await_suspend(h, p_->environment());
347 : #endif
348 : }
349 : };
350 :
351 : /** Transform a nested awaitable before `co_await`.
352 :
353 : Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the
354 : coroutine's environment is propagated into it. A diagnostic
355 : is emitted if the awaitable does not satisfy @ref ContextAwaitable.
356 :
357 : @param a The awaitable expression from `co_await a`.
358 :
359 : @return A @ref transform_awaiter wrapping `a`.
360 : */
361 : template<class Awaitable>
362 108222 : auto transform_awaitable(Awaitable&& a)
363 : {
364 : using A = std::decay_t<Awaitable>;
365 : if constexpr (ContextAwaitable<A>)
366 : {
367 : return transform_awaiter<Awaitable>{
368 213919 : std::forward<Awaitable>(a), this};
369 : }
370 : else
371 : {
372 : static_assert(ContextAwaitable<A>, "requires ContextAwaitable");
373 : }
374 105697 : }
375 : };
376 :
377 : /** Handle to the owned coroutine frame.
378 :
379 : Null when the task is empty (for example after a move or after
380 : @ref release). Prefer @ref handle to read this; the member is
381 : public for use by the coroutine machinery.
382 : */
383 : std::coroutine_handle<promise_type> h_;
384 :
385 : /** Destroy the task and its coroutine frame if owned.
386 :
387 : @par Preconditions
388 : The frame is not lent: no parent is suspended awaiting it,
389 : unless its chain is being unwound after an @ref abandon.
390 : Destroying a lent frame otherwise calls `std::terminate`; end
391 : a running chain by requesting stop, or by destroying its
392 : execution context.
393 : */
394 113320 : ~task()
395 : {
396 113320 : destroy_frame();
397 113320 : }
398 :
399 : /** Report whether the awaited task is already complete.
400 :
401 : Always returns `false`; a task is lazy and has not started when
402 : it is awaited, so the awaiting coroutine always suspends.
403 :
404 : @return `false`.
405 : */
406 2117 : bool await_ready() const noexcept
407 : {
408 2117 : return false;
409 : }
410 :
411 : /** Return the task's result, rethrowing any captured exception.
412 :
413 : If the coroutine body exited via an unhandled exception, that
414 : exception is rethrown here. Otherwise the result is returned by
415 : move (for `task<T>`) or nothing is returned (for `task<void>`).
416 :
417 : @return The result value for non-void `T`; otherwise `void`.
418 :
419 : @throws The exception captured by the coroutine body, if any.
420 :
421 : @note Discarding a `results` silently drops the error
422 : code, so that overload is marked `[[nodiscard]]`.
423 : */
424 570 : [[nodiscard]] auto await_resume()
425 : requires detail::is_results_v<T>
426 : {
427 570 : lent_ = false;
428 570 : if(h_.promise().has_ep_)
429 105 : std::rethrow_exception(h_.promise().ep_);
430 465 : return std::move(*h_.promise().result_);
431 : }
432 :
433 247 : auto await_resume()
434 : requires (! detail::is_results_v<T>)
435 : {
436 247 : lent_ = false;
437 247 : if(h_.promise().has_ep_)
438 18 : std::rethrow_exception(h_.promise().ep_);
439 : if constexpr (! std::is_void_v<T>)
440 154 : return std::move(*h_.promise().result_);
441 : else
442 75 : return;
443 : }
444 :
445 : /** Start the task with the awaiting coroutine's context.
446 :
447 : Stores `cont` as the continuation to resume on completion.
448 : Stores `env` as the execution environment propagated to nested
449 : `co_await` expressions. Then transfers control into the task's
450 : coroutine body via the returned handle.
451 :
452 : @param cont The awaiting coroutine to resume when the task
453 : completes.
454 :
455 : @param env The execution environment (executor, stop token, and
456 : frame allocator). It must outlive the task.
457 :
458 : @return The task's coroutine handle, for symmetric transfer.
459 : */
460 2014 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, await_context const* env)
461 : {
462 2014 : h_.promise().set_continuation(cont);
463 2014 : h_.promise().set_environment(env);
464 2014 : lent_ = true;
465 2014 : return h_;
466 : }
467 :
468 : /** Return the coroutine handle.
469 :
470 : @note Do not call `destroy()` on the returned handle while the
471 : task is being awaited: the awaiting frame still holds it and
472 : destroys it again, which has undefined behavior. A holder that
473 : gives the coroutine up passes its continuation to @ref abandon
474 : instead. (Destroying or
475 : move-assigning over the task object itself while its frame is
476 : lent is a different operation, and calls `std::terminate`.)
477 : The task's lifetime is normally managed by `run_async`, `run`,
478 : or the awaiting parent. For cooperative cancellation, use
479 : `std::stop_token`.
480 :
481 : @note The operation, queue or primitive holding a suspended
482 : coroutine gives it up with @ref abandon, through the
483 : continuation it keeps, never by destroying a handle.
484 : `handle()` belongs to the owning task object.
485 :
486 : @return The coroutine handle.
487 : */
488 107247 : std::coroutine_handle<promise_type> handle() const noexcept
489 : {
490 107247 : return h_;
491 : }
492 :
493 : /** Release ownership of the coroutine frame.
494 :
495 : After calling this, destroying the task does not destroy the
496 : coroutine frame. The caller becomes responsible for the frame's
497 : lifetime.
498 :
499 : @note The caller may call `destroy()` on the released handle
500 : only when the task has not started or has fully completed.
501 : Destroying the released handle of a task that is being
502 : awaited is not supported and has undefined behavior.
503 :
504 : @par Postconditions
505 : `handle()` returns a null handle. Callers needing the
506 : original handle must save it, via @ref handle, before
507 : calling this.
508 : */
509 107104 : void release() noexcept
510 : {
511 107104 : h_ = nullptr;
512 107104 : }
513 :
514 : /** Copy construction is disabled; a task uniquely owns its frame.
515 :
516 : @param other The task that would be copied.
517 : */
518 : task(task const& other) = delete;
519 :
520 : /** Copy assignment is disabled; a task uniquely owns its frame.
521 :
522 : @param other The task that would be assigned from.
523 :
524 : @return A reference to `*this`.
525 : */
526 : task& operator=(task const& other) = delete;
527 :
528 : /** Construct by moving, transferring ownership of the frame.
529 :
530 : @par Postconditions
531 : `other` is empty and must not be awaited.
532 :
533 : @param other The task to move from.
534 : */
535 4088 : task(task&& other) noexcept
536 4088 : : h_(std::exchange(other.h_, nullptr))
537 : {
538 4088 : }
539 :
540 : /** Assign by moving, transferring ownership of the frame.
541 :
542 : If this task already owns a coroutine frame, that frame is
543 : destroyed first. Self-assignment is a no-op.
544 :
545 : @par Preconditions
546 : The frame being replaced is not lent: no parent is suspended
547 : awaiting it. Replacing a lent frame calls `std::terminate`.
548 :
549 : @par Postconditions
550 : `other` is empty and must not be awaited.
551 :
552 : @param other The task to move from.
553 :
554 : @return A reference to `*this`.
555 : */
556 1 : task& operator=(task&& other) noexcept
557 : {
558 1 : if(this != &other)
559 : {
560 1 : destroy_frame();
561 1 : h_ = std::exchange(other.h_, nullptr);
562 : }
563 1 : return *this;
564 : }
565 :
566 : private:
567 : // Set while a parent is suspended awaiting this task.
568 : bool lent_ = false;
569 :
570 109232 : explicit task(std::coroutine_handle<promise_type> h)
571 109232 : : h_(h)
572 : {
573 109232 : }
574 :
575 113321 : void destroy_frame() noexcept
576 : {
577 113321 : if(!h_)
578 111193 : return;
579 2128 : if(lent_ && !h_.done())
580 : {
581 : // Only the unwind of an abandoned chain destroys a frame
582 : // that is still awaited.
583 1278 : auto* owner = h_.promise().environment()->owner;
584 1278 : if(!owner || !owner->abandoned())
585 : std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
586 : }
587 2128 : std::exchange(h_, nullptr).destroy();
588 2128 : lent_ = false;
589 : }
590 : };
591 :
592 : } // namespace capy
593 : } // namespace boost
594 :
595 : #endif
|