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 889 : void return_value(T value)
41 : {
42 889 : result_ = std::move(value);
43 889 : }
44 :
45 279 : T&& result() noexcept
46 : {
47 279 : return std::move(*result_);
48 : }
49 : };
50 :
51 : template<>
52 : struct task_return_base<void>
53 : {
54 3634 : void return_void()
55 : {
56 3634 : }
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 ContextRunnable, ContextAwaitable, 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 `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 context_awaitable_promise_base, 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 112700 : promise_type() noexcept
145 112700 : : has_ep_(false)
146 : {
147 112700 : }
148 :
149 : /// Destroy the promise, releasing any stored exception.
150 112700 : ~promise_type()
151 : {
152 112700 : if(has_ep_)
153 490 : ep_.~exception_ptr();
154 112700 : }
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 4512 : std::exception_ptr exception() const noexcept
162 : {
163 4512 : if(has_ep_)
164 732 : return ep_;
165 3780 : 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 112700 : task get_return_object()
176 : {
177 112700 : 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 112700 : auto initial_suspend() noexcept
190 : {
191 : struct awaiter
192 : {
193 : promise_type* p_;
194 :
195 112700 : bool await_ready() const noexcept
196 : {
197 112700 : return false;
198 : }
199 :
200 112700 : void await_suspend(std::coroutine_handle<>) const noexcept
201 : {
202 112700 : }
203 :
204 107651 : void await_resume() const noexcept
205 : {
206 : // Restore TLS when body starts executing
207 107651 : set_current_frame_allocator(p_->environment()->frame_allocator);
208 107651 : }
209 : };
210 112700 : 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 5013 : auto final_suspend() noexcept
224 : {
225 : struct awaiter
226 : {
227 : promise_type* p_;
228 :
229 5013 : bool await_ready() const noexcept
230 : {
231 5013 : return false;
232 : }
233 :
234 5013 : std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
235 : {
236 5013 : return p_->continuation();
237 : }
238 :
239 : void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
240 : };
241 5013 : 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 : @return The wrapped awaitable's own `await_ready` result:
276 : `true` if no suspension is needed.
277 : */
278 107722 : bool await_ready() noexcept
279 : {
280 107722 : 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 5084 : decltype(auto) await_resume()
295 : {
296 : // Restore TLS before body resumes
297 5084 : set_current_frame_allocator(p_->environment()->frame_allocator);
298 5084 : 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 ContextAwaitable 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 107102 : 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 106071 : return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
328 : else
329 1031 : return a_.await_suspend(h, p_->environment());
330 : }
331 : };
332 :
333 : /** Transform a nested awaitable before `co_await`.
334 :
335 : Wraps an @ref ContextAwaitable 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 ContextAwaitable.
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 107722 : auto transform_awaitable(Awaitable&& a)
345 : {
346 : using A = std::decay_t<Awaitable>;
347 : if constexpr (ContextAwaitable<A>)
348 : {
349 : return transform_awaiter<Awaitable>{
350 212933 : std::forward<Awaitable>(a), this};
351 : }
352 : else
353 : {
354 : static_assert(ContextAwaitable<A>, "requires ContextAwaitable");
355 : }
356 105211 : }
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 :
369 : @par Preconditions
370 : The frame is not lent: no parent is suspended awaiting it,
371 : unless its chain is being unwound after an @ref abandon.
372 : Destroying a lent frame otherwise calls `std::terminate`; end
373 : a running chain by requesting stop, or by destroying its
374 : execution context.
375 : */
376 116770 : ~task()
377 : {
378 116770 : destroy_frame();
379 116770 : }
380 :
381 : /** Report whether the awaited task is already complete.
382 :
383 : Always returns `false`; a task is lazy and has not started when
384 : it is awaited, so the awaiting coroutine always suspends.
385 :
386 : @return `false`.
387 : */
388 2104 : bool await_ready() const noexcept
389 : {
390 2104 : return false;
391 : }
392 :
393 : /** Return the task's result, rethrowing any captured exception.
394 :
395 : If the coroutine body exited via an unhandled exception, that
396 : exception is rethrown here. Otherwise the result is returned by
397 : move (for `task<T>`) or nothing is returned (for `task<void>`).
398 :
399 : @return The result value for non-void `T`; otherwise `void`.
400 :
401 : @throws The exception captured by the coroutine body, if any.
402 :
403 : @note Discarding a `results` silently drops the error
404 : code, so that overload is marked `[[nodiscard]]`.
405 : */
406 562 : [[nodiscard]] auto await_resume()
407 : requires detail::is_results_v<T>
408 : {
409 562 : lent_ = false;
410 562 : if(h_.promise().has_ep_)
411 105 : std::rethrow_exception(h_.promise().ep_);
412 457 : return std::move(*h_.promise().result_);
413 : }
414 :
415 242 : auto await_resume()
416 : requires (! detail::is_results_v<T>)
417 : {
418 242 : lent_ = false;
419 242 : if(h_.promise().has_ep_)
420 18 : std::rethrow_exception(h_.promise().ep_);
421 : if constexpr (! std::is_void_v<T>)
422 151 : return std::move(*h_.promise().result_);
423 : else
424 73 : return;
425 : }
426 :
427 : /** Start the task with the awaiting coroutine's context.
428 :
429 : Stores `cont` as the continuation to resume on completion.
430 : Stores `env` as the execution environment propagated to nested
431 : `co_await` expressions. Then transfers control into the task's
432 : coroutine body via the returned handle.
433 :
434 : @param cont The awaiting coroutine to resume when the task
435 : completes.
436 :
437 : @param env The execution environment (executor, stop token, and
438 : frame allocator). It must outlive the task.
439 :
440 : @return The task's coroutine handle, for symmetric transfer.
441 : */
442 2001 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, await_context const* env)
443 : {
444 2001 : h_.promise().set_continuation(cont);
445 2001 : h_.promise().set_environment(env);
446 2001 : lent_ = true;
447 2001 : return h_;
448 : }
449 :
450 : /** Return the coroutine handle.
451 :
452 : @note Do not call `destroy()` on the returned handle while the
453 : task is being awaited: the awaiting frame still holds it and
454 : destroys it again, which has undefined behavior. A holder that
455 : gives the coroutine up passes its continuation to @ref abandon
456 : instead. (Destroying or
457 : move-assigning over the task object itself while its frame is
458 : lent is a different operation, and calls `std::terminate`.)
459 : The task's lifetime is normally managed by `run_async`, `run`,
460 : or the awaiting parent. For cooperative cancellation, use
461 : `std::stop_token`.
462 :
463 : @note The operation, queue or primitive holding a suspended
464 : coroutine gives it up with @ref abandon, through the
465 : continuation it keeps, never by destroying a handle.
466 : `handle()` belongs to the owning task object.
467 :
468 : @return The coroutine handle.
469 : */
470 110728 : std::coroutine_handle<promise_type> handle() const noexcept
471 : {
472 110728 : return h_;
473 : }
474 :
475 : /** Release ownership of the coroutine frame.
476 :
477 : After calling this, destroying the task does not destroy the
478 : coroutine frame. The caller becomes responsible for the frame's
479 : lifetime.
480 :
481 : @note The caller may call `destroy()` on the released handle
482 : only when the task has not started or has fully completed.
483 : Destroying the released handle of a task that is being
484 : awaited is not supported and has undefined behavior.
485 :
486 : @par Postconditions
487 : `handle()` returns a null handle. Callers needing the
488 : original handle must save it, via @ref handle, before
489 : calling this.
490 : */
491 110585 : void release() noexcept
492 : {
493 110585 : h_ = nullptr;
494 110585 : }
495 :
496 : /** Copy construction is disabled; a task uniquely owns its frame.
497 :
498 : @param other The task that would be copied.
499 : */
500 : task(task const& other) = delete;
501 :
502 : /** Copy assignment is disabled; a task uniquely owns its frame.
503 :
504 : @param other The task that would be assigned from.
505 :
506 : @return A reference to `*this`.
507 : */
508 : task& operator=(task const& other) = delete;
509 :
510 : /** Construct by moving, transferring ownership of the frame.
511 :
512 : @par Postconditions
513 : `other` is empty and must not be awaited.
514 :
515 : @param other The task to move from.
516 : */
517 4070 : task(task&& other) noexcept
518 4070 : : h_(std::exchange(other.h_, nullptr))
519 : {
520 4070 : }
521 :
522 : /** Assign by moving, transferring ownership of the frame.
523 :
524 : If this task already owns a coroutine frame, that frame is
525 : destroyed first. Self-assignment is a no-op.
526 :
527 : @par Preconditions
528 : The frame being replaced is not lent: no parent is suspended
529 : awaiting it. Replacing a lent frame calls `std::terminate`.
530 :
531 : @par Postconditions
532 : `other` is empty and must not be awaited.
533 :
534 : @param other The task to move from.
535 :
536 : @return A reference to `*this`.
537 : */
538 1 : task& operator=(task&& other) noexcept
539 : {
540 1 : if(this != &other)
541 : {
542 1 : destroy_frame();
543 1 : h_ = std::exchange(other.h_, nullptr);
544 : }
545 1 : return *this;
546 : }
547 :
548 : private:
549 : // Set while a parent is suspended awaiting this task.
550 : bool lent_ = false;
551 :
552 112700 : explicit task(std::coroutine_handle<promise_type> h)
553 112700 : : h_(h)
554 : {
555 112700 : }
556 :
557 116771 : void destroy_frame() noexcept
558 : {
559 116771 : if(!h_)
560 114656 : return;
561 2115 : if(lent_ && !h_.done())
562 : {
563 : // Only the unwind of an abandoned chain destroys a frame
564 : // that is still awaited.
565 1278 : auto* owner = h_.promise().environment()->owner;
566 1278 : if(!owner || !owner->abandoned())
567 : std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
568 : }
569 2115 : std::exchange(h_, nullptr).destroy();
570 2115 : lent_ = false;
571 : }
572 : };
573 :
574 : } // namespace capy
575 : } // namespace boost
576 :
577 : #endif
|