include/boost/capy/task.hpp

96.2% Lines (75/78) 92.8% Functions (1129/1217)
Line TLA Hits 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_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 1286x void return_value(T value)
39 {
40 1286x result_ = std::move(value);
41 1286x }
42
43 152x T&& result() noexcept
44 {
45 152x return std::move(*result_);
46 }
47 };
48
49 template<>
50 struct task_return_base<void>
51 {
52 2045x void return_void()
53 {
54 2045x }
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 5050x promise_type() noexcept
112 5050x : has_ep_(false)
113 {
114 5050x }
115
116 5050x ~promise_type()
117 {
118 5050x if(has_ep_)
119 1573x ep_.~exception_ptr();
120 5050x }
121
122 4123x std::exception_ptr exception() const noexcept
123 {
124 4123x if(has_ep_)
125 2066x return ep_;
126 2057x return {};
127 }
128
129 5050x task get_return_object()
130 {
131 5050x return task{std::coroutine_handle<promise_type>::from_promise(*this)};
132 }
133
134 5050x auto initial_suspend() noexcept
135 {
136 struct awaiter
137 {
138 promise_type* p_;
139
140 144x bool await_ready() const noexcept
141 {
142 144x return false;
143 }
144
145 144x void await_suspend(std::coroutine_handle<>) const noexcept
146 {
147 144x }
148
149 144x void await_resume() const noexcept
150 {
151 // Restore TLS when body starts executing
152 144x set_current_frame_allocator(p_->environment()->frame_allocator);
153 144x }
154 };
155 5050x return awaiter{this};
156 }
157
158 4904x auto final_suspend() noexcept
159 {
160 struct awaiter
161 {
162 promise_type* p_;
163
164 144x bool await_ready() const noexcept
165 {
166 144x return false;
167 }
168
169 144x std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
170 {
171 144x return p_->continuation();
172 }
173
174 void await_resume() const noexcept
175 {
176 }
177 };
178 4904x return awaiter{this};
179 }
180
181 1573x void unhandled_exception()
182 {
183 1573x new (&ep_) std::exception_ptr(std::current_exception());
184 1573x has_ep_ = true;
185 1573x }
186
187 template<class Awaitable>
188 struct transform_awaiter
189 {
190 std::decay_t<Awaitable> a_;
191 promise_type* p_;
192
193 8890x bool await_ready() noexcept
194 {
195 8890x return a_.await_ready();
196 }
197
198 8747x decltype(auto) await_resume()
199 {
200 // Restore TLS before body resumes
201 8747x set_current_frame_allocator(p_->environment()->frame_allocator);
202 8747x return a_.await_resume();
203 }
204
205 template<class Promise>
206 2509x auto await_suspend(std::coroutine_handle<Promise> h) noexcept
207 {
208 using R = decltype(a_.await_suspend(h, p_->environment()));
209 if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
210 2509x return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
211 else
212 return a_.await_suspend(h, p_->environment());
213 }
214 };
215
216 template<class Awaitable>
217 8890x auto transform_awaitable(Awaitable&& a)
218 {
219 using A = std::decay_t<Awaitable>;
220 if constexpr (IoAwaitable<A>)
221 {
222 return transform_awaiter<Awaitable>{
223 11017x std::forward<Awaitable>(a), this};
224 }
225 else
226 {
227 static_assert(sizeof(A) == 0, "requires IoAwaitable");
228 }
229 2127x }
230 };
231
232 std::coroutine_handle<promise_type> h_;
233
234 /// Destroy the task and its coroutine frame if owned.
235 10782x ~task()
236 {
237 10782x if(h_)
238 1760x h_.destroy();
239 10782x }
240
241 /// Return false; tasks are never immediately ready.
242 1632x bool await_ready() const noexcept
243 {
244 1632x return false;
245 }
246
247 /// Return the result or rethrow any stored exception.
248 1757x auto await_resume()
249 {
250 1757x if(h_.promise().has_ep_)
251 539x std::rethrow_exception(h_.promise().ep_);
252 if constexpr (! std::is_void_v<T>)
253 1132x return std::move(*h_.promise().result_);
254 else
255 86x return;
256 }
257
258 /// Start execution with the caller's context.
259 1741x std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
260 {
261 1741x h_.promise().set_continuation(cont);
262 1741x h_.promise().set_environment(env);
263 1741x return h_;
264 }
265
266 /// Return the coroutine handle.
267 3309x std::coroutine_handle<promise_type> handle() const noexcept
268 {
269 3309x return h_;
270 }
271
272 /** Release ownership of the coroutine frame.
273
274 After calling this, destroying the task does not destroy the
275 coroutine frame. The caller becomes responsible for the frame's
276 lifetime.
277
278 @par Postconditions
279 `handle()` returns the original handle, but the task no longer
280 owns it.
281 */
282 3290x void release() noexcept
283 {
284 3290x h_ = nullptr;
285 3290x }
286
287 task(task const&) = delete;
288 task& operator=(task const&) = delete;
289
290 /// Construct by moving, transferring ownership.
291 5732x task(task&& other) noexcept
292 5732x : h_(std::exchange(other.h_, nullptr))
293 {
294 5732x }
295
296 /// Assign by moving, transferring ownership.
297 task& operator=(task&& other) noexcept
298 {
299 if(this != &other)
300 {
301 if(h_)
302 h_.destroy();
303 h_ = std::exchange(other.h_, nullptr);
304 }
305 return *this;
306 }
307
308 private:
309 5050x explicit task(std::coroutine_handle<promise_type> h)
310 5050x : h_(h)
311 {
312 5050x }
313 };
314
315 } // namespace capy
316 } // namespace boost
317
318 #endif
319