include/boost/capy/task.hpp

96.2% Lines (75/78) 92.4% Functions (1044/1130)
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 1270x void return_value(T value)
39 {
40 1270x result_ = std::move(value);
41 1270x }
42
43 148x T&& result() noexcept
44 {
45 148x return std::move(*result_);
46 }
47 };
48
49 template<>
50 struct task_return_base<void>
51 {
52 1871x void return_void()
53 {
54 1871x }
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 @tparam T The result type. Use `task<>` for `task<void>`.
72
73 @par Thread Safety
74 Distinct objects: Safe.
75 Shared objects: Unsafe.
76
77 @par Example
78
79 @code
80 task<int> compute_value()
81 {
82 auto [ec, n] = co_await stream.read_some( buf );
83 if( ec )
84 co_return 0;
85 co_return process( buf, n );
86 }
87
88 task<> run_session( tcp_socket sock )
89 {
90 int result = co_await compute_value();
91 // ...
92 }
93 @endcode
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 4716x promise_type() noexcept
112 4716x : has_ep_(false)
113 {
114 4716x }
115
116 4716x ~promise_type()
117 {
118 4716x if(has_ep_)
119 1567x ep_.~exception_ptr();
120 4716x }
121
122 3939x std::exception_ptr exception() const noexcept
123 {
124 3939x if(has_ep_)
125 2058x return ep_;
126 1881x return {};
127 }
128
129 4716x task get_return_object()
130 {
131 4716x return task{std::coroutine_handle<promise_type>::from_promise(*this)};
132 }
133
134 4716x 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 4716x return awaiter{this};
156 }
157
158 4708x 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 4708x return awaiter{this};
179 }
180
181 1567x void unhandled_exception()
182 {
183 1567x new (&ep_) std::exception_ptr(std::current_exception());
184 1567x has_ep_ = true;
185 1567x }
186
187 template<class Awaitable>
188 struct transform_awaiter
189 {
190 std::decay_t<Awaitable> a_;
191 promise_type* p_;
192
193 8610x bool await_ready() noexcept
194 {
195 8610x return a_.await_ready();
196 }
197
198 8605x decltype(auto) await_resume()
199 {
200 // Restore TLS before body resumes
201 8605x set_current_frame_allocator(p_->environment()->frame_allocator);
202 8605x return a_.await_resume();
203 }
204
205 template<class Promise>
206 2230x 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 2230x 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 8610x 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 10465x std::forward<Awaitable>(a), this};
224 }
225 else
226 {
227 static_assert(sizeof(A) == 0, "requires IoAwaitable");
228 }
229 1855x }
230 };
231
232 std::coroutine_handle<promise_type> h_;
233
234 /// Destroy the task and its coroutine frame if owned.
235 10384x ~task()
236 {
237 10384x if(h_)
238 1744x h_.destroy();
239 10384x }
240
241 /// Return false; tasks are never immediately ready.
242 1616x bool await_ready() const noexcept
243 {
244 1616x return false;
245 }
246
247 /// Return the result or rethrow any stored exception.
248 1741x auto await_resume()
249 {
250 1741x if(h_.promise().has_ep_)
251 537x std::rethrow_exception(h_.promise().ep_);
252 if constexpr (! std::is_void_v<T>)
253 1120x return std::move(*h_.promise().result_);
254 else
255 84x return;
256 }
257
258 /// Start execution with the caller's context.
259 1725x std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
260 {
261 1725x h_.promise().set_continuation(cont);
262 1725x h_.promise().set_environment(env);
263 1725x return h_;
264 }
265
266 /// Return the coroutine handle.
267 2991x std::coroutine_handle<promise_type> handle() const noexcept
268 {
269 2991x 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 2972x void release() noexcept
283 {
284 2972x h_ = nullptr;
285 2972x }
286
287 task(task const&) = delete;
288 task& operator=(task const&) = delete;
289
290 /// Move construct, transferring ownership.
291 5668x task(task&& other) noexcept
292 5668x : h_(std::exchange(other.h_, nullptr))
293 {
294 5668x }
295
296 /// Move assign, 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 4716x explicit task(std::coroutine_handle<promise_type> h)
310 4716x : h_(h)
311 {
312 4716x }
313 };
314
315 } // namespace capy
316 } // namespace boost
317
318 #endif
319