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