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