TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
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_TASK_HPP
11 : #define BOOST_CAPY_TASK_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/concept/executor.hpp>
15 : #include <boost/capy/concept/io_awaitable.hpp>
16 : #include <boost/capy/ex/io_awaitable_promise_base.hpp>
17 : #include <boost/capy/ex/io_env.hpp>
18 : #include <boost/capy/ex/frame_allocator.hpp>
19 : #include <boost/capy/detail/await_suspend_helper.hpp>
20 : #include <boost/capy/io_result.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 868 : void return_value(T value)
40 : {
41 868 : result_ = std::move(value);
42 868 : }
43 :
44 271 : T&& result() noexcept
45 : {
46 271 : return std::move(*result_);
47 : }
48 : };
49 :
50 : template<>
51 : struct task_return_base<void>
52 : {
53 1227 : void return_void()
54 : {
55 1227 : }
56 : };
57 :
58 : } // namespace detail
59 :
60 : /** Lazy coroutine task satisfying @ref IoRunnable.
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 Thread Safety
73 : Distinct objects: Safe.
74 : Shared objects: Unsafe.
75 :
76 : @par Example
77 :
78 : @code
79 : task<int> compute_value()
80 : {
81 : auto [ec, n] = co_await stream.read_some( buf );
82 : if( ec )
83 : co_return 0;
84 : co_return process( buf, n );
85 : }
86 :
87 : task<> run_session( tcp_socket sock )
88 : {
89 : int result = co_await compute_value();
90 : // ...
91 : }
92 : @endcode
93 :
94 : @tparam T The result type. Use `task<>` for `task<void>`.
95 :
96 : @see IoRunnable, IoAwaitable, run, run_async
97 : */
98 : template<typename T = void>
99 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
100 : task
101 : {
102 : /** The coroutine promise type for `task<T>`.
103 :
104 : This is the promise object the compiler associates with a
105 : `task<T>` coroutine. It satisfies the coroutine promise
106 : requirements and participates in the I/O awaitable protocol via
107 : @ref io_awaitable_promise_base. It is part of the coroutine
108 : machinery and is not intended to be used directly by callers.
109 :
110 : Result storage and `return_value`/`return_void` are provided by
111 : `detail::task_return_base<T>`.
112 :
113 : @see io_awaitable_promise_base, IoRunnable
114 : */
115 : struct promise_type
116 : : io_awaitable_promise_base<promise_type>
117 : , detail::task_return_base<T>
118 : {
119 : private:
120 : friend task;
121 : union { std::exception_ptr ep_; };
122 : bool has_ep_;
123 :
124 : public:
125 : /// Construct the promise with no stored exception.
126 2749 : promise_type() noexcept
127 2749 : : has_ep_(false)
128 : {
129 2749 : }
130 :
131 : /// Destroy the promise, releasing any stored exception.
132 2749 : ~promise_type()
133 : {
134 2749 : if(has_ep_)
135 489 : ep_.~exception_ptr();
136 2749 : }
137 :
138 : /** Return the exception captured by the coroutine body, if any.
139 :
140 : @return The stored exception, or a null `std::exception_ptr`
141 : if the coroutine did not exit via an unhandled exception.
142 : */
143 2125 : std::exception_ptr exception() const noexcept
144 : {
145 2125 : if(has_ep_)
146 730 : return ep_;
147 1395 : return {};
148 : }
149 :
150 : /** Return the owning `task` for this coroutine.
151 :
152 : Called by the compiler to produce the object returned to the
153 : caller when the coroutine is created.
154 :
155 : @return A `task` owning the coroutine frame.
156 : */
157 2749 : task get_return_object()
158 : {
159 2749 : return task{std::coroutine_handle<promise_type>::from_promise(*this)};
160 : }
161 :
162 : /** Return the initial-suspend awaiter.
163 :
164 : The coroutine always suspends at the initial suspend point,
165 : so the body does not start until the task is awaited. When the
166 : body is resumed, the awaiter restores the thread-local frame
167 : allocator from the stored environment.
168 :
169 : @return An awaiter that suspends unconditionally.
170 : */
171 2749 : auto initial_suspend() noexcept
172 : {
173 : struct awaiter
174 : {
175 : promise_type* p_;
176 :
177 2749 : bool await_ready() const noexcept
178 : {
179 2749 : return false;
180 : }
181 :
182 2749 : void await_suspend(std::coroutine_handle<>) const noexcept
183 : {
184 2749 : }
185 :
186 2745 : void await_resume() const noexcept
187 : {
188 : // Restore TLS when body starts executing
189 2745 : set_current_frame_allocator(p_->environment()->frame_allocator);
190 2745 : }
191 : };
192 2749 : return awaiter{this};
193 : }
194 :
195 : /** Return the final-suspend awaiter.
196 :
197 : The coroutine always suspends at the final suspend point. The
198 : awaiter's `await_suspend` performs symmetric transfer to the
199 : stored continuation (consuming it), resuming the awaiting
200 : coroutine.
201 :
202 : @return An awaiter that suspends and transfers to the
203 : continuation.
204 : */
205 2584 : auto final_suspend() noexcept
206 : {
207 : struct awaiter
208 : {
209 : promise_type* p_;
210 :
211 2584 : bool await_ready() const noexcept
212 : {
213 2584 : return false;
214 : }
215 :
216 2584 : std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
217 : {
218 2584 : return p_->continuation();
219 : }
220 :
221 : void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
222 : };
223 2584 : return awaiter{this};
224 : }
225 :
226 : /** Capture the in-flight exception from the coroutine body.
227 :
228 : Called by the compiler when the coroutine body exits via an
229 : unhandled exception. The captured exception is rethrown when
230 : the task is awaited.
231 : */
232 489 : void unhandled_exception() noexcept
233 : {
234 489 : new (&ep_) std::exception_ptr(std::current_exception());
235 489 : has_ep_ = true;
236 489 : }
237 :
238 : /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
239 :
240 : Forwards the environment to the inner awaitable's
241 : environment-taking `await_suspend` and restores the
242 : thread-local frame allocator before the body resumes.
243 :
244 : @tparam Awaitable The awaitable being transformed.
245 : */
246 : template<class Awaitable>
247 : struct transform_awaiter
248 : {
249 : std::decay_t<Awaitable> a_;
250 : promise_type* p_;
251 :
252 2869 : bool await_ready() noexcept
253 : {
254 2869 : return a_.await_ready();
255 : }
256 :
257 2708 : decltype(auto) await_resume()
258 : {
259 : // Restore TLS before body resumes
260 2708 : set_current_frame_allocator(p_->environment()->frame_allocator);
261 2708 : return a_.await_resume();
262 : }
263 :
264 : template<class Promise>
265 2253 : auto await_suspend(std::coroutine_handle<Promise> h) noexcept
266 : {
267 : using R = decltype(a_.await_suspend(h, p_->environment()));
268 : if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
269 1253 : return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
270 : else
271 1000 : return a_.await_suspend(h, p_->environment());
272 : }
273 : };
274 :
275 : /** Transform a nested awaitable before `co_await`.
276 :
277 : Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
278 : coroutine's environment is propagated into it. A diagnostic
279 : is emitted if the awaitable does not satisfy @ref IoAwaitable.
280 :
281 : @param a The awaitable expression from `co_await a`.
282 :
283 : @return A @ref transform_awaiter wrapping `a`.
284 : */
285 : template<class Awaitable>
286 2869 : auto transform_awaitable(Awaitable&& a)
287 : {
288 : using A = std::decay_t<Awaitable>;
289 : if constexpr (IoAwaitable<A>)
290 : {
291 : return transform_awaiter<Awaitable>{
292 4393 : std::forward<Awaitable>(a), this};
293 : }
294 : else
295 : {
296 : static_assert(sizeof(A) == 0, "requires IoAwaitable");
297 : }
298 1524 : }
299 : };
300 :
301 : /** Handle to the owned coroutine frame.
302 :
303 : Null when the task is empty (for example after a move or after
304 : @ref release). Prefer @ref handle to read this; the member is
305 : public for use by the coroutine machinery.
306 : */
307 : std::coroutine_handle<promise_type> h_;
308 :
309 : /// Destroy the task and its coroutine frame if owned.
310 5839 : ~task()
311 : {
312 5839 : if(h_)
313 767 : h_.destroy();
314 5839 : }
315 :
316 : /** Report whether the awaited task is already complete.
317 :
318 : Always returns `false`; a task is lazy and has not started when
319 : it is awaited, so the awaiting coroutine always suspends.
320 :
321 : @return `false`.
322 : */
323 764 : bool await_ready() const noexcept
324 : {
325 764 : return false;
326 : }
327 :
328 : /** Return the task's result, rethrowing any captured exception.
329 :
330 : If the coroutine body exited via an unhandled exception, that
331 : exception is rethrown here. Otherwise the result is returned by
332 : move (for `task<T>`) or nothing is returned (for `task<void>`).
333 :
334 : @return The result value for non-void `T`; otherwise `void`.
335 :
336 : @throws The exception captured by the coroutine body, if any.
337 :
338 : @note Discarding an `io_result` silently drops the error
339 : code, so that overload is marked `[[nodiscard]]`.
340 : */
341 552 : [[nodiscard]] auto await_resume()
342 : requires detail::is_io_result_v<T>
343 : {
344 552 : if(h_.promise().has_ep_)
345 105 : std::rethrow_exception(h_.promise().ep_);
346 447 : return std::move(*h_.promise().result_);
347 : }
348 :
349 211 : auto await_resume()
350 : requires (! detail::is_io_result_v<T>)
351 : {
352 211 : if(h_.promise().has_ep_)
353 18 : std::rethrow_exception(h_.promise().ep_);
354 : if constexpr (! std::is_void_v<T>)
355 148 : return std::move(*h_.promise().result_);
356 : else
357 45 : return;
358 : }
359 :
360 : /** Start the task with the awaiting coroutine's context.
361 :
362 : Stores `cont` as the continuation to resume on completion and
363 : `env` as the execution environment propagated to nested
364 : `co_await` expressions, then transfers control into the task's
365 : coroutine body via the returned handle.
366 :
367 : @param cont The awaiting coroutine to resume when the task
368 : completes.
369 :
370 : @param env The execution environment (executor, stop token, and
371 : frame allocator). It must outlive the task.
372 :
373 : @return The task's coroutine handle, for symmetric transfer.
374 : */
375 683 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
376 : {
377 683 : h_.promise().set_continuation(cont);
378 683 : h_.promise().set_environment(env);
379 683 : return h_;
380 : }
381 :
382 : /** Return the coroutine handle.
383 :
384 : @note Do not call `destroy()` on the returned handle while the
385 : task is being awaited. The task's lifetime is normally managed
386 : by `run_async`, `run`, or the awaiting parent; manually
387 : destroying a suspended task that another coroutine is awaiting
388 : produces undefined behavior. For cooperative cancellation, use
389 : `std::stop_token`.
390 :
391 : @return The coroutine handle.
392 : */
393 2065 : std::coroutine_handle<promise_type> handle() const noexcept
394 : {
395 2065 : return h_;
396 : }
397 :
398 : /** Release ownership of the coroutine frame.
399 :
400 : After calling this, destroying the task does not destroy the
401 : coroutine frame. The caller becomes responsible for the frame's
402 : lifetime.
403 :
404 : @note If the caller intends to call `destroy()` on the
405 : released handle, it must do so only when the task has not
406 : started or has fully completed. Destroying a suspended task
407 : that is being awaited produces undefined behavior.
408 :
409 : @par Postconditions
410 : `handle()` returns the original handle, but the task no longer
411 : owns it.
412 : */
413 1982 : void release() noexcept
414 : {
415 1982 : h_ = nullptr;
416 1982 : }
417 :
418 : task(task const&) = delete;
419 : task& operator=(task const&) = delete;
420 :
421 : /** Construct by moving, transferring ownership of the frame.
422 :
423 : @par Postconditions
424 : `other` is empty and must not be awaited.
425 :
426 : @param other The task to move from.
427 : */
428 3090 : task(task&& other) noexcept
429 3090 : : h_(std::exchange(other.h_, nullptr))
430 : {
431 3090 : }
432 :
433 : /** Assign by moving, transferring ownership of the frame.
434 :
435 : If this task already owns a coroutine frame, that frame is
436 : destroyed first. Self-assignment is a no-op.
437 :
438 : @par Postconditions
439 : `other` is empty and must not be awaited.
440 :
441 : @param other The task to move from.
442 :
443 : @return `*this`.
444 : */
445 : task& operator=(task&& other) noexcept
446 : {
447 : if(this != &other)
448 : {
449 : if(h_)
450 : h_.destroy();
451 : h_ = std::exchange(other.h_, nullptr);
452 : }
453 : return *this;
454 : }
455 :
456 : private:
457 2749 : explicit task(std::coroutine_handle<promise_type> h)
458 2749 : : h_(h)
459 : {
460 2749 : }
461 : };
462 :
463 : } // namespace capy
464 : } // namespace boost
465 :
466 : #endif
|