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 <concepts>
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 : template<typename ... Ts>
32 : struct io_result;
33 :
34 : namespace detail {
35 :
36 : // Helper base for result storage and return_void/return_value
37 : template<typename T>
38 : struct task_return_base
39 : {
40 : std::optional<T> result_;
41 :
42 HIT 1289 : void return_value(T value)
43 : {
44 1289 : result_ = std::move(value);
45 1289 : }
46 :
47 154 : T&& result() noexcept
48 : {
49 154 : return std::move(*result_);
50 : }
51 : };
52 :
53 : template<>
54 : struct task_return_base<void>
55 : {
56 1996 : void return_void()
57 : {
58 1996 : }
59 : };
60 :
61 : template<typename ... Us>
62 : void handle_yield_result(io_result<Us...> & res, std::error_code ec)
63 : {
64 : static_assert((std::constructible_from<Us> && ...), "co_yield requires all result value to be default constructible");
65 : }
66 :
67 : } // namespace detail
68 :
69 : /** Lazy coroutine task satisfying @ref IoRunnable.
70 :
71 : Use `task<T>` as the return type for coroutines that perform I/O
72 : and return a value of type `T`. The coroutine body does not start
73 : executing until the task is awaited, enabling efficient composition
74 : without unnecessary eager execution.
75 :
76 : The task participates in the I/O awaitable protocol: when awaited,
77 : it receives the caller's executor and stop token, propagating them
78 : to nested `co_await` expressions. This enables cancellation and
79 : proper completion dispatch across executor boundaries.
80 :
81 : @par Thread Safety
82 : Distinct objects: Safe.
83 : Shared objects: Unsafe.
84 :
85 : @par Example
86 :
87 : @code
88 : task<int> compute_value()
89 : {
90 : auto [ec, n] = co_await stream.read_some( buf );
91 : if( ec )
92 : co_return 0;
93 : co_return process( buf, n );
94 : }
95 :
96 : task<> run_session( tcp_socket sock )
97 : {
98 : int result = co_await compute_value();
99 : // ...
100 : }
101 : @endcode
102 :
103 : @tparam T The result type. Use `task<>` for `task<void>`.
104 :
105 : @see IoRunnable, IoAwaitable, run, run_async
106 : */
107 : template<typename T = void>
108 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
109 : task
110 : {
111 : struct promise_type
112 : : io_awaitable_promise_base<promise_type>
113 : , detail::task_return_base<T>
114 : {
115 : protected:
116 : friend task;
117 : union { std::exception_ptr ep_; };
118 : bool has_ep_;
119 :
120 : public:
121 5047 : promise_type() noexcept
122 5047 : : has_ep_(false)
123 : {
124 5047 : }
125 :
126 5047 : ~promise_type()
127 : {
128 5047 : if(has_ep_)
129 1599 : ep_.~exception_ptr();
130 5047 : }
131 :
132 4174 : std::exception_ptr exception() const noexcept
133 : {
134 4174 : if(has_ep_)
135 2094 : return ep_;
136 2080 : return {};
137 : }
138 :
139 3433 : task get_return_object()
140 : {
141 3433 : return task{std::coroutine_handle<promise_type>::from_promise(*this)};
142 : }
143 :
144 5047 : auto initial_suspend() noexcept
145 : {
146 : struct awaiter
147 : {
148 : promise_type* p_;
149 :
150 5047 : bool await_ready() const noexcept
151 : {
152 5047 : return false;
153 : }
154 :
155 5047 : void await_suspend(std::coroutine_handle<>) const noexcept
156 : {
157 5047 : }
158 :
159 5044 : void await_resume() const noexcept
160 : {
161 : // Restore TLS when body starts executing
162 5044 : set_current_frame_allocator(p_->environment()->frame_allocator);
163 5044 : }
164 : };
165 5047 : return awaiter{this};
166 : }
167 :
168 4882 : auto final_suspend() noexcept
169 : {
170 : struct awaiter
171 : {
172 : promise_type* p_;
173 :
174 4882 : bool await_ready() const noexcept
175 : {
176 4882 : return false;
177 : }
178 :
179 4882 : std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
180 : {
181 4882 : return p_->continuation();
182 : }
183 :
184 MIS 0 : void await_resume() const noexcept
185 : {
186 0 : }
187 : };
188 HIT 4882 : return awaiter{this};
189 : }
190 :
191 1599 : void unhandled_exception() noexcept
192 : {
193 1599 : new (&ep_) std::exception_ptr(std::current_exception());
194 1599 : has_ep_ = true;
195 1599 : }
196 :
197 : template<class Awaitable>
198 : struct transform_awaiter
199 : {
200 : std::decay_t<Awaitable> a_;
201 : promise_type* p_;
202 :
203 9185 : bool await_ready() noexcept
204 : {
205 9185 : return a_.await_ready();
206 : }
207 :
208 9025 : decltype(auto) await_resume()
209 : {
210 : // Restore TLS before body resumes
211 9025 : set_current_frame_allocator(p_->environment()->frame_allocator);
212 9025 : return a_.await_resume();
213 : }
214 :
215 : template<class Promise>
216 2497 : auto await_suspend(std::coroutine_handle<Promise> h) noexcept
217 : {
218 : using R = decltype(a_.await_suspend(h, p_->environment()));
219 : if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
220 2497 : return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
221 : else
222 MIS 0 : return a_.await_suspend(h, p_->environment());
223 : }
224 : };
225 :
226 : template<class Awaitable>
227 HIT 9185 : auto transform_awaitable(Awaitable&& a)
228 : {
229 : using A = std::decay_t<Awaitable>;
230 : if constexpr (IoAwaitable<A>)
231 : {
232 : return transform_awaiter<Awaitable>{
233 11354 : std::forward<Awaitable>(a), this};
234 : }
235 : else
236 : {
237 : static_assert(sizeof(A) == 0, "requires IoAwaitable");
238 : }
239 2169 : }
240 :
241 :
242 :
243 : template<class ... Ts>
244 : auto yield_value(io_result<Ts...> res)
245 : {
246 : struct awaitable
247 : {
248 : io_result<Ts...> res;
249 :
250 : bool await_ready() const {return !res.ec;}
251 : void await_suspend(std::coroutine_handle<promise_type> h)
252 : {
253 : auto & p = h.promise();
254 : try
255 : {
256 : detail::handle_yield_result(h.promise(), res.ec);
257 : }
258 : catch (...)
259 : {
260 : p.uncaught_exception();
261 : }
262 :
263 : }
264 : std::tuple<Ts...> await_resume()
265 : {
266 : return std::move(res.values);
267 : }
268 : };
269 : }
270 : };
271 :
272 : std::coroutine_handle<promise_type> h_;
273 :
274 : /// Destroy the task and its coroutine frame if owned.
275 6375 : ~task()
276 : {
277 6375 : if(h_)
278 96 : h_.destroy();
279 6375 : }
280 :
281 : /// Return false; tasks are never immediately ready.
282 94 : bool await_ready() const noexcept
283 : {
284 94 : return false;
285 : }
286 :
287 : /// Return the result or rethrow any stored exception.
288 93 : auto await_resume()
289 : {
290 93 : if(h_.promise().has_ep_)
291 18 : std::rethrow_exception(h_.promise().ep_);
292 : if constexpr (! std::is_void_v<T>)
293 59 : return std::move(*h_.promise().result_);
294 : else
295 16 : return;
296 : }
297 :
298 : /// Start execution with the caller's context.
299 71 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
300 : {
301 71 : h_.promise().set_continuation(cont);
302 71 : h_.promise().set_environment(env);
303 71 : return h_;
304 : }
305 :
306 : /** Return the coroutine handle.
307 :
308 : @note Do not call `destroy()` on the returned handle while the
309 : task is being awaited. The task's lifetime is normally managed
310 : by `run_async`, `run`, or the awaiting parent; manually
311 : destroying a suspended task that another coroutine is awaiting
312 : produces undefined behavior. For cooperative cancellation, use
313 : `std::stop_token`.
314 :
315 : @return The coroutine handle.
316 : */
317 3362 : std::coroutine_handle<promise_type> handle() const noexcept
318 : {
319 3362 : return h_;
320 : }
321 :
322 : /** Release ownership of the coroutine frame.
323 :
324 : After calling this, destroying the task does not destroy the
325 : coroutine frame. The caller becomes responsible for the frame's
326 : lifetime.
327 :
328 : @note If the caller intends to call `destroy()` on the
329 : released handle, it must do so only when the task has not
330 : started or has fully completed. Destroying a suspended task
331 : that is being awaited produces undefined behavior.
332 :
333 : @par Postconditions
334 : `handle()` returns the original handle, but the task no longer
335 : owns it.
336 : */
337 3337 : void release() noexcept
338 : {
339 3337 : h_ = nullptr;
340 3337 : }
341 :
342 : task(task const&) = delete;
343 : task& operator=(task const&) = delete;
344 :
345 : /// Construct by moving, transferring ownership.
346 2942 : task(task&& other) noexcept
347 2942 : : h_(std::exchange(other.h_, nullptr))
348 : {
349 2942 : }
350 :
351 : /// Assign by moving, transferring ownership.
352 : task& operator=(task&& other) noexcept
353 : {
354 : if(this != &other)
355 : {
356 : if(h_)
357 : h_.destroy();
358 : h_ = std::exchange(other.h_, nullptr);
359 : }
360 : return *this;
361 : }
362 :
363 : private:
364 3433 : explicit task(std::coroutine_handle<promise_type> h)
365 3433 : : h_(h)
366 : {
367 3433 : }
368 : };
369 :
370 : } // namespace capy
371 : } // namespace boost
372 :
373 : #endif
|