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 1317 : void return_value(T value)
39 : {
40 1317 : result_ = std::move(value);
41 1317 : }
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 1981 : void return_void()
53 : {
54 1981 : }
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 : struct promise_type
102 : : io_awaitable_promise_base<promise_type>
103 : , detail::task_return_base<T>
104 : {
105 : private:
106 : friend task;
107 : union { std::exception_ptr ep_; };
108 : bool has_ep_;
109 :
110 : public:
111 5097 : promise_type() noexcept
112 5097 : : has_ep_(false)
113 : {
114 5097 : }
115 :
116 5097 : ~promise_type()
117 : {
118 5097 : if(has_ep_)
119 1612 : ep_.~exception_ptr();
120 5097 : }
121 :
122 4176 : std::exception_ptr exception() const noexcept
123 : {
124 4176 : if(has_ep_)
125 2108 : return ep_;
126 2068 : return {};
127 : }
128 :
129 5097 : task get_return_object()
130 : {
131 5097 : return task{std::coroutine_handle<promise_type>::from_promise(*this)};
132 : }
133 :
134 5097 : auto initial_suspend() noexcept
135 : {
136 : struct awaiter
137 : {
138 : promise_type* p_;
139 :
140 5097 : bool await_ready() const noexcept
141 : {
142 5097 : return false;
143 : }
144 :
145 5097 : void await_suspend(std::coroutine_handle<>) const noexcept
146 : {
147 5097 : }
148 :
149 5094 : void await_resume() const noexcept
150 : {
151 : // Restore TLS when body starts executing
152 5094 : set_current_frame_allocator(p_->environment()->frame_allocator);
153 5094 : }
154 : };
155 5097 : return awaiter{this};
156 : }
157 :
158 4910 : auto final_suspend() noexcept
159 : {
160 : struct awaiter
161 : {
162 : promise_type* p_;
163 :
164 4910 : bool await_ready() const noexcept
165 : {
166 4910 : return false;
167 : }
168 :
169 4910 : std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
170 : {
171 4910 : return p_->continuation();
172 : }
173 :
174 : void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
175 : };
176 4910 : return awaiter{this};
177 : }
178 :
179 1612 : void unhandled_exception() noexcept
180 : {
181 1612 : new (&ep_) std::exception_ptr(std::current_exception());
182 1612 : has_ep_ = true;
183 1612 : }
184 :
185 : template<class Awaitable>
186 : struct transform_awaiter
187 : {
188 : std::decay_t<Awaitable> a_;
189 : promise_type* p_;
190 :
191 9253 : bool await_ready() noexcept
192 : {
193 9253 : return a_.await_ready();
194 : }
195 :
196 9069 : decltype(auto) await_resume()
197 : {
198 : // Restore TLS before body resumes
199 9069 : set_current_frame_allocator(p_->environment()->frame_allocator);
200 9069 : return a_.await_resume();
201 : }
202 :
203 : template<class Promise>
204 2544 : auto await_suspend(std::coroutine_handle<Promise> h) noexcept
205 : {
206 : using R = decltype(a_.await_suspend(h, p_->environment()));
207 : if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
208 2543 : return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
209 : else
210 1 : return a_.await_suspend(h, p_->environment());
211 : }
212 : };
213 :
214 : template<class Awaitable>
215 9253 : auto transform_awaitable(Awaitable&& a)
216 : {
217 : using A = std::decay_t<Awaitable>;
218 : if constexpr (IoAwaitable<A>)
219 : {
220 : return transform_awaiter<Awaitable>{
221 11450 : std::forward<Awaitable>(a), this};
222 : }
223 : else
224 : {
225 : static_assert(sizeof(A) == 0, "requires IoAwaitable");
226 : }
227 2197 : }
228 : };
229 :
230 : std::coroutine_handle<promise_type> h_;
231 :
232 : /// Destroy the task and its coroutine frame if owned.
233 10553 : ~task()
234 : {
235 10553 : if(h_)
236 1731 : h_.destroy();
237 10553 : }
238 :
239 : /// Return false; tasks are never immediately ready.
240 1593 : bool await_ready() const noexcept
241 : {
242 1593 : return false;
243 : }
244 :
245 : /// Return the result or rethrow any stored exception.
246 1728 : auto await_resume()
247 : {
248 1728 : if(h_.promise().has_ep_)
249 557 : std::rethrow_exception(h_.promise().ep_);
250 : if constexpr (! std::is_void_v<T>)
251 1155 : return std::move(*h_.promise().result_);
252 : else
253 16 : return;
254 : }
255 :
256 : /// Start execution with the caller's context.
257 1706 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
258 : {
259 1706 : h_.promise().set_continuation(cont);
260 1706 : h_.promise().set_environment(env);
261 1706 : return h_;
262 : }
263 :
264 : /** Return the coroutine handle.
265 :
266 : @note Do not call `destroy()` on the returned handle while the
267 : task is being awaited. The task's lifetime is normally managed
268 : by `run_async`, `run`, or the awaiting parent; manually
269 : destroying a suspended task that another coroutine is awaiting
270 : produces undefined behavior. For cooperative cancellation, use
271 : `std::stop_token`.
272 :
273 : @return The coroutine handle.
274 : */
275 3391 : std::coroutine_handle<promise_type> handle() const noexcept
276 : {
277 3391 : return h_;
278 : }
279 :
280 : /** Release ownership of the coroutine frame.
281 :
282 : After calling this, destroying the task does not destroy the
283 : coroutine frame. The caller becomes responsible for the frame's
284 : lifetime.
285 :
286 : @note If the caller intends to call `destroy()` on the
287 : released handle, it must do so only when the task has not
288 : started or has fully completed. Destroying a suspended task
289 : that is being awaited produces undefined behavior.
290 :
291 : @par Postconditions
292 : `handle()` returns the original handle, but the task no longer
293 : owns it.
294 : */
295 3366 : void release() noexcept
296 : {
297 3366 : h_ = nullptr;
298 3366 : }
299 :
300 : task(task const&) = delete;
301 : task& operator=(task const&) = delete;
302 :
303 : /// Construct by moving, transferring ownership.
304 5456 : task(task&& other) noexcept
305 5456 : : h_(std::exchange(other.h_, nullptr))
306 : {
307 5456 : }
308 :
309 : /// Assign by moving, transferring ownership.
310 : task& operator=(task&& other) noexcept
311 : {
312 : if(this != &other)
313 : {
314 : if(h_)
315 : h_.destroy();
316 : h_ = std::exchange(other.h_, nullptr);
317 : }
318 : return *this;
319 : }
320 :
321 : private:
322 5097 : explicit task(std::coroutine_handle<promise_type> h)
323 5097 : : h_(h)
324 : {
325 5097 : }
326 : };
327 :
328 : } // namespace capy
329 : } // namespace boost
330 :
331 : #endif
|