include/boost/capy/task.hpp

96.2% Lines (75/78) 92.4% Functions (1040/1126)
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 1263x void return_value(T value)
39 {
40 1263x result_ = std::move(value);
41 1263x }
42
43 144x T&& result() noexcept
44 {
45 144x return std::move(*result_);
46 }
47 };
48
49 template<>
50 struct task_return_base<void>
51 {
52 1869x void return_void()
53 {
54 1869x }
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 4707x promise_type() noexcept
112 4707x : has_ep_(false)
113 {
114 4707x }
115
116 4707x ~promise_type()
117 {
118 4707x if(has_ep_)
119 1567x ep_.~exception_ptr();
120 4707x }
121
122 3933x std::exception_ptr exception() const noexcept
123 {
124 3933x if(has_ep_)
125 2058x return ep_;
126 1875x return {};
127 }
128
129 4707x task get_return_object()
130 {
131 4707x return task{std::coroutine_handle<promise_type>::from_promise(*this)};
132 }
133
134 4707x 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 4707x return awaiter{this};
156 }
157
158 4699x 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 4699x 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 8607x bool await_ready() noexcept
194 {
195 8607x return a_.await_ready();
196 }
197
198 8602x decltype(auto) await_resume()
199 {
200 // Restore TLS before body resumes
201 8602x set_current_frame_allocator(p_->environment()->frame_allocator);
202 8602x return a_.await_resume();
203 }
204
205 template<class Promise>
206 2227x 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 2227x 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 8607x 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 10459x std::forward<Awaitable>(a), this};
224 }
225 else
226 {
227 static_assert(sizeof(A) == 0, "requires IoAwaitable");
228 }
229 1852x }
230 };
231
232 std::coroutine_handle<promise_type> h_;
233
234 /// Destroy the task and its coroutine frame if owned.
235 10366x ~task()
236 {
237 10366x if(h_)
238 1741x h_.destroy();
239 10366x }
240
241 /// Return false; tasks are never immediately ready.
242 1613x bool await_ready() const noexcept
243 {
244 1613x return false;
245 }
246
247 /// Return the result or rethrow any stored exception.
248 1738x auto await_resume()
249 {
250 1738x if(h_.promise().has_ep_)
251 537x std::rethrow_exception(h_.promise().ep_);
252 if constexpr (! std::is_void_v<T>)
253 1117x 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 2982x std::coroutine_handle<promise_type> handle() const noexcept
268 {
269 2982x 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 2966x void release() noexcept
283 {
284 2966x h_ = nullptr;
285 2966x }
286
287 task(task const&) = delete;
288 task& operator=(task const&) = delete;
289
290 /// Move construct, transferring ownership.
291 5659x task(task&& other) noexcept
292 5659x : h_(std::exchange(other.h_, nullptr))
293 {
294 5659x }
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 4707x explicit task(std::coroutine_handle<promise_type> h)
310 4707x : h_(h)
311 {
312 4707x }
313 };
314
315 } // namespace capy
316 } // namespace boost
317
318 #endif
319