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/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 1301x void return_value(T value)
39 {
40 1301x result_ = std::move(value);
41 1301x }
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 2065x void return_void()
53 {
54 2065x }
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 5110x promise_type() noexcept
112 5110x : has_ep_(false)
113 {
114 5110x }
115
116 5110x ~promise_type()
117 {
118 5110x if(has_ep_)
119 1603x ep_.~exception_ptr();
120 5110x }
121
122 4173x std::exception_ptr exception() const noexcept
123 {
124 4173x if(has_ep_)
125 2096x return ep_;
126 2077x return {};
127 }
128
129 5110x task get_return_object()
130 {
131 5110x return task{std::coroutine_handle<promise_type>::from_promise(*this)};
132 }
133
134 5110x 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 5110x return awaiter{this};
156 }
157
158 4969x 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 4969x return awaiter{this};
179 }
180
181 1603x void unhandled_exception()
182 {
183 1603x new (&ep_) std::exception_ptr(std::current_exception());
184 1603x has_ep_ = true;
185 1603x }
186
187 template<class Awaitable>
188 struct transform_awaiter
189 {
190 std::decay_t<Awaitable> a_;
191 promise_type* p_;
192
193 9214x bool await_ready() noexcept
194 {
195 9214x return a_.await_ready();
196 }
197
198 9076x decltype(auto) await_resume()
199 {
200 // Restore TLS before body resumes
201 9076x set_current_frame_allocator(p_->environment()->frame_allocator);
202 9076x return a_.await_resume();
203 }
204
205 template<class Promise>
206 2539x 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 2539x 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 9214x 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 11371x std::forward<Awaitable>(a), this};
224 }
225 else
226 {
227 static_assert(sizeof(A) == 0, "requires IoAwaitable");
228 }
229 2157x }
230 };
231
232 std::coroutine_handle<promise_type> h_;
233
234 /// Destroy the task and its coroutine frame if owned.
235 10902x ~task()
236 {
237 10902x if(h_)
238 1790x h_.destroy();
239 10902x }
240
241 /// Return false; tasks are never immediately ready.
242 1662x bool await_ready() const noexcept
243 {
244 1662x return false;
245 }
246
247 /// Return the result or rethrow any stored exception.
248 1787x auto await_resume()
249 {
250 1787x if(h_.promise().has_ep_)
251 554x std::rethrow_exception(h_.promise().ep_);
252 if constexpr (! std::is_void_v<T>)
253 1147x return std::move(*h_.promise().result_);
254 else
255 86x return;
256 }
257
258 /// Start execution with the caller's context.
259 1771x std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
260 {
261 1771x h_.promise().set_continuation(cont);
262 1771x h_.promise().set_environment(env);
263 1771x return h_;
264 }
265
266 /// Return the coroutine handle.
267 3339x std::coroutine_handle<promise_type> handle() const noexcept
268 {
269 3339x 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 3320x void release() noexcept
283 {
284 3320x h_ = nullptr;
285 3320x }
286
287 task(task const&) = delete;
288 task& operator=(task const&) = delete;
289
290 /// Construct by moving, transferring ownership.
291 5792x task(task&& other) noexcept
292 5792x : h_(std::exchange(other.h_, nullptr))
293 {
294 5792x }
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 5110x explicit task(std::coroutine_handle<promise_type> h)
310 5110x : h_(h)
311 {
312 5110x }
313 };
314
315 } // namespace capy
316 } // namespace boost
317
318 #endif
319