Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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 835 : void return_value(T value)
38 : {
39 835 : result_ = std::move(value);
40 835 : }
41 :
42 62 : T&& result() noexcept
43 : {
44 62 : return std::move(*result_);
45 : }
46 : };
47 :
48 : template<>
49 : struct task_return_base<void>
50 : {
51 957 : void return_void()
52 : {
53 957 : }
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.failed() )
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 : std::exception_ptr ep_;
105 :
106 2204 : std::exception_ptr exception() const noexcept
107 : {
108 2204 : return ep_;
109 : }
110 :
111 2869 : task get_return_object()
112 : {
113 2869 : return task{std::coroutine_handle<promise_type>::from_promise(*this)};
114 : }
115 :
116 2869 : auto initial_suspend() noexcept
117 : {
118 : struct awaiter
119 : {
120 : promise_type* p_;
121 :
122 2869 : bool await_ready() const noexcept
123 : {
124 2869 : return false;
125 : }
126 :
127 2869 : void await_suspend(coro) const noexcept
128 : {
129 : // Capture TLS allocator while it's still valid
130 2869 : p_->set_frame_allocator(current_frame_allocator());
131 2869 : }
132 :
133 2867 : void await_resume() const noexcept
134 : {
135 : // Restore TLS when body starts executing
136 2867 : if(p_->frame_allocator())
137 2794 : current_frame_allocator() = p_->frame_allocator();
138 2867 : }
139 : };
140 2869 : return awaiter{this};
141 : }
142 :
143 2866 : auto final_suspend() noexcept
144 : {
145 : struct awaiter
146 : {
147 : promise_type* p_;
148 :
149 2866 : bool await_ready() const noexcept
150 : {
151 2866 : return false;
152 : }
153 :
154 2866 : coro await_suspend(coro) const noexcept
155 : {
156 2866 : return p_->complete();
157 : }
158 :
159 0 : void await_resume() const noexcept
160 : {
161 0 : }
162 : };
163 2866 : return awaiter{this};
164 : }
165 :
166 1074 : void unhandled_exception()
167 : {
168 1074 : ep_ = std::current_exception();
169 1074 : }
170 :
171 : template<class Awaitable>
172 : struct transform_awaiter
173 : {
174 : std::decay_t<Awaitable> a_;
175 : promise_type* p_;
176 :
177 6845 : bool await_ready()
178 : {
179 6845 : return a_.await_ready();
180 : }
181 :
182 6844 : decltype(auto) await_resume()
183 : {
184 : // Restore TLS before body resumes
185 6844 : if(p_->frame_allocator())
186 6780 : current_frame_allocator() = p_->frame_allocator();
187 6844 : return a_.await_resume();
188 : }
189 :
190 : template<class Promise>
191 1813 : auto await_suspend(std::coroutine_handle<Promise> h)
192 : {
193 1813 : return a_.await_suspend(h, p_->executor(), p_->stop_token());
194 : }
195 : };
196 :
197 : template<class Awaitable>
198 6845 : auto transform_awaitable(Awaitable&& a)
199 : {
200 : using A = std::decay_t<Awaitable>;
201 : if constexpr (IoAwaitable<A>)
202 : {
203 : return transform_awaiter<Awaitable>{
204 8085 : std::forward<Awaitable>(a), this};
205 : }
206 : else
207 : {
208 : static_assert(sizeof(A) == 0, "requires IoAwaitable");
209 : }
210 1240 : }
211 : };
212 :
213 : std::coroutine_handle<promise_type> h_;
214 :
215 : /// Destroy the task and its coroutine frame if owned.
216 5675 : ~task()
217 : {
218 5675 : if(h_)
219 1230 : h_.destroy();
220 5675 : }
221 :
222 : /// Return false; tasks are never immediately ready.
223 1103 : bool await_ready() const noexcept
224 : {
225 1103 : return false;
226 : }
227 :
228 : /// Return the result or rethrow any stored exception.
229 1228 : auto await_resume()
230 : {
231 1228 : if(h_.promise().ep_)
232 462 : std::rethrow_exception(h_.promise().ep_);
233 : if constexpr (! std::is_void_v<T>)
234 756 : return std::move(*h_.promise().result_);
235 : else
236 10 : return;
237 : }
238 :
239 : /// Start execution with the caller's context.
240 1216 : coro await_suspend(coro cont, executor_ref caller_ex, std::stop_token token)
241 : {
242 1216 : h_.promise().set_continuation(cont, caller_ex);
243 1216 : h_.promise().set_executor(caller_ex);
244 1216 : h_.promise().set_stop_token(token);
245 1216 : return h_;
246 : }
247 :
248 : /// Return the coroutine handle.
249 1654 : std::coroutine_handle<promise_type> handle() const noexcept
250 : {
251 1654 : return h_;
252 : }
253 :
254 : /** Release ownership of the coroutine frame.
255 :
256 : After calling this, destroying the task does not destroy the
257 : coroutine frame. The caller becomes responsible for the frame's
258 : lifetime.
259 :
260 : @par Postconditions
261 : `handle()` returns the original handle, but the task no longer
262 : owns it.
263 : */
264 1639 : void release() noexcept
265 : {
266 1639 : h_ = nullptr;
267 1639 : }
268 :
269 : task(task const&) = delete;
270 : task& operator=(task const&) = delete;
271 :
272 : /// Move construct, transferring ownership.
273 2806 : task(task&& other) noexcept
274 2806 : : h_(std::exchange(other.h_, nullptr))
275 : {
276 2806 : }
277 :
278 : /// Move assign, transferring ownership.
279 : task& operator=(task&& other) noexcept
280 : {
281 : if(this != &other)
282 : {
283 : if(h_)
284 : h_.destroy();
285 : h_ = std::exchange(other.h_, nullptr);
286 : }
287 : return *this;
288 : }
289 :
290 : private:
291 2869 : explicit task(std::coroutine_handle<promise_type> h)
292 2869 : : h_(h)
293 : {
294 2869 : }
295 : };
296 :
297 : } // namespace capy
298 : } // namespace boost
299 :
300 : #endif
|