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/corosio
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_support.hpp>
17 : #include <boost/capy/ex/executor_ref.hpp>
18 : #include <boost/capy/ex/frame_allocator.hpp>
19 :
20 : #include <exception>
21 : #include <optional>
22 : #include <type_traits>
23 : #include <utility>
24 : #include <variant>
25 :
26 : namespace boost {
27 : namespace capy {
28 :
29 : namespace detail {
30 :
31 : // Helper base for result storage and return_void/return_value
32 : template<typename T>
33 : struct task_return_base
34 : {
35 : std::optional<T> result_;
36 :
37 1194 : void return_value(T value)
38 : {
39 1194 : result_ = std::move(value);
40 1194 : }
41 :
42 108 : T&& result() noexcept
43 : {
44 108 : return std::move(*result_);
45 : }
46 : };
47 :
48 : template<>
49 : struct task_return_base<void>
50 : {
51 1205 : void return_void()
52 : {
53 1205 : }
54 : };
55 :
56 : } // namespace detail
57 :
58 : /** Lazy coroutine task satisfying @ref IoLaunchableTask.
59 :
60 : Use `task<T>` as the return type for coroutines that perform I/O
61 : and return a value of type `T`. The coroutine body does not start
62 : executing until the task is awaited, enabling efficient composition
63 : without unnecessary eager execution.
64 :
65 : The task participates in the I/O awaitable protocol: when awaited,
66 : it receives the caller's executor and stop token, propagating them
67 : to nested `co_await` expressions. This enables cancellation and
68 : proper completion dispatch across executor boundaries.
69 :
70 : @tparam T The result type. Use `task<>` for `task<void>`.
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 : @see IoLaunchableTask, IoAwaitableTask, run, run_async
95 : */
96 : template<typename T = void>
97 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
98 : task
99 : {
100 : struct promise_type
101 : : io_awaitable_support<promise_type>
102 : , detail::task_return_base<T>
103 : {
104 : private:
105 : friend task;
106 : union { std::exception_ptr ep_; };
107 : bool has_ep_;
108 :
109 : public:
110 3630 : promise_type() noexcept
111 3630 : : has_ep_(false)
112 : {
113 3630 : }
114 :
115 3630 : ~promise_type()
116 : {
117 3630 : if(has_ep_)
118 1228 : ep_.~exception_ptr();
119 3630 : }
120 :
121 2727 : std::exception_ptr exception() const noexcept
122 : {
123 2727 : if(has_ep_)
124 1438 : return ep_;
125 1289 : return {};
126 : }
127 :
128 3630 : task get_return_object()
129 : {
130 3630 : return task{std::coroutine_handle<promise_type>::from_promise(*this)};
131 : }
132 :
133 3630 : auto initial_suspend() noexcept
134 : {
135 : struct awaiter
136 : {
137 : promise_type* p_;
138 :
139 3630 : bool await_ready() const noexcept
140 : {
141 3630 : return false;
142 : }
143 :
144 3630 : void await_suspend(coro) const noexcept
145 : {
146 : // Capture TLS allocator while it's still valid
147 3630 : p_->set_frame_allocator(current_frame_allocator());
148 3630 : }
149 :
150 3628 : void await_resume() const noexcept
151 : {
152 : // Restore TLS when body starts executing
153 3628 : auto* fa = p_->frame_allocator();
154 3628 : if(fa && fa != current_frame_allocator())
155 4 : current_frame_allocator() = fa;
156 3628 : }
157 : };
158 3630 : return awaiter{this};
159 : }
160 :
161 3627 : auto final_suspend() noexcept
162 : {
163 : struct awaiter
164 : {
165 : promise_type* p_;
166 :
167 3627 : bool await_ready() const noexcept
168 : {
169 3627 : return false;
170 : }
171 :
172 3627 : coro await_suspend(coro) const noexcept
173 : {
174 3627 : return p_->complete();
175 : }
176 :
177 0 : void await_resume() const noexcept
178 : {
179 0 : }
180 : };
181 3627 : return awaiter{this};
182 : }
183 :
184 1228 : void unhandled_exception()
185 : {
186 1228 : new (&ep_) std::exception_ptr(std::current_exception());
187 1228 : has_ep_ = true;
188 1228 : }
189 :
190 : template<class Awaitable>
191 : struct transform_awaiter
192 : {
193 : std::decay_t<Awaitable> a_;
194 : promise_type* p_;
195 :
196 7205 : bool await_ready() noexcept
197 : {
198 7205 : return a_.await_ready();
199 : }
200 :
201 7204 : decltype(auto) await_resume()
202 : {
203 : // Restore TLS before body resumes
204 7204 : auto* fa = p_->frame_allocator();
205 7204 : if(fa && fa != current_frame_allocator())
206 9 : current_frame_allocator() = fa;
207 7204 : return a_.await_resume();
208 : }
209 :
210 : template<class Promise>
211 2060 : auto await_suspend(std::coroutine_handle<Promise> h) noexcept
212 : {
213 2060 : return a_.await_suspend(h, p_->executor(), p_->stop_token());
214 : }
215 : };
216 :
217 : template<class Awaitable>
218 7205 : auto transform_awaitable(Awaitable&& a)
219 : {
220 : using A = std::decay_t<Awaitable>;
221 : if constexpr (IoAwaitable<A>)
222 : {
223 : return transform_awaiter<Awaitable>{
224 8930 : std::forward<Awaitable>(a), this};
225 : }
226 : else
227 : {
228 : static_assert(sizeof(A) == 0, "requires IoAwaitable");
229 : }
230 1725 : }
231 : };
232 :
233 : std::coroutine_handle<promise_type> h_;
234 :
235 : /// Destroy the task and its coroutine frame if owned.
236 7988 : ~task()
237 : {
238 7988 : if(h_)
239 1602 : h_.destroy();
240 7988 : }
241 :
242 : /// Return false; tasks are never immediately ready.
243 1474 : bool await_ready() const noexcept
244 : {
245 1474 : return false;
246 : }
247 :
248 : /// Return the result or rethrow any stored exception.
249 1599 : auto await_resume()
250 : {
251 1599 : if(h_.promise().has_ep_)
252 507 : std::rethrow_exception(h_.promise().ep_);
253 : if constexpr (! std::is_void_v<T>)
254 1069 : return std::move(*h_.promise().result_);
255 : else
256 23 : return;
257 : }
258 :
259 : /// Start execution with the caller's context.
260 1587 : coro await_suspend(coro cont,
261 : executor_ref const& caller_ex, std::stop_token const& token)
262 : {
263 1587 : h_.promise().set_continuation(cont, caller_ex);
264 1587 : h_.promise().set_executor(caller_ex);
265 1587 : h_.promise().set_stop_token(token);
266 1587 : return h_;
267 : }
268 :
269 : /// Return the coroutine handle.
270 2044 : std::coroutine_handle<promise_type> handle() const noexcept
271 : {
272 2044 : return h_;
273 : }
274 :
275 : /** Release ownership of the coroutine frame.
276 :
277 : After calling this, destroying the task does not destroy the
278 : coroutine frame. The caller becomes responsible for the frame's
279 : lifetime.
280 :
281 : @par Postconditions
282 : `handle()` returns the original handle, but the task no longer
283 : owns it.
284 : */
285 2028 : void release() noexcept
286 : {
287 2028 : h_ = nullptr;
288 2028 : }
289 :
290 : task(task const&) = delete;
291 : task& operator=(task const&) = delete;
292 :
293 : /// Move construct, transferring ownership.
294 4358 : task(task&& other) noexcept
295 4358 : : h_(std::exchange(other.h_, nullptr))
296 : {
297 4358 : }
298 :
299 : /// Move assign, transferring ownership.
300 : task& operator=(task&& other) noexcept
301 : {
302 : if(this != &other)
303 : {
304 : if(h_)
305 : h_.destroy();
306 : h_ = std::exchange(other.h_, nullptr);
307 : }
308 : return *this;
309 : }
310 :
311 : private:
312 3630 : explicit task(std::coroutine_handle<promise_type> h)
313 3630 : : h_(h)
314 : {
315 3630 : }
316 : };
317 :
318 : } // namespace capy
319 : } // namespace boost
320 :
321 : #endif
|