include/boost/capy/task.hpp

96.2% Lines (75/78) 92.1% Functions (962/1044)
include/boost/capy/task.hpp
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 1259 void return_value(T value)
39 {
40 1259 result_ = std::move(value);
41 1259 }
42
43 143 T&& result() noexcept
44 {
45 143 return std::move(*result_);
46 }
47 };
48
49 template<>
50 struct task_return_base<void>
51 {
52 1865 void return_void()
53 {
54 1865 }
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 4699 promise_type() noexcept
112 4699 : has_ep_(false)
113 {
114 4699 }
115
116 4699 ~promise_type()
117 {
118 4699 if(has_ep_)
119 1567 ep_.~exception_ptr();
120 4699 }
121
122 3931 std::exception_ptr exception() const noexcept
123 {
124 3931 if(has_ep_)
125 2058 return ep_;
126 1873 return {};
127 }
128
129 4699 task get_return_object()
130 {
131 4699 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
132 }
133
134 4699 auto initial_suspend() noexcept
135 {
136 struct awaiter
137 {
138 promise_type* p_;
139
140 144 bool await_ready() const noexcept
141 {
142 144 return false;
143 }
144
145 144 void await_suspend(std::coroutine_handle<>) const noexcept
146 {
147 144 }
148
149 144 void await_resume() const noexcept
150 {
151 // Restore TLS when body starts executing
152 144 set_current_frame_allocator(p_->environment()->frame_allocator);
153 144 }
154 };
155 4699 return awaiter{this};
156 }
157
158 4691 auto final_suspend() noexcept
159 {
160 struct awaiter
161 {
162 promise_type* p_;
163
164 144 bool await_ready() const noexcept
165 {
166 144 return false;
167 }
168
169 144 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
170 {
171 144 return p_->continuation();
172 }
173
174 void await_resume() const noexcept
175 {
176 }
177 };
178 4691 return awaiter{this};
179 }
180
181 1567 void unhandled_exception()
182 {
183 1567 new (&ep_) std::exception_ptr(std::current_exception());
184 1567 has_ep_ = true;
185 1567 }
186
187 template<class Awaitable>
188 struct transform_awaiter
189 {
190 std::decay_t<Awaitable> a_;
191 promise_type* p_;
192
193 8603 bool await_ready() noexcept
194 {
195 8603 return a_.await_ready();
196 }
197
198 8598 decltype(auto) await_resume()
199 {
200 // Restore TLS before body resumes
201 8598 set_current_frame_allocator(p_->environment()->frame_allocator);
202 8598 return a_.await_resume();
203 }
204
205 template<class Promise>
206 2223 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 2223 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 8603 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 10453 std::forward<Awaitable>(a), this};
224 }
225 else
226 {
227 static_assert(sizeof(A) == 0, "requires IoAwaitable");
228 }
229 1850 }
230 };
231
232 std::coroutine_handle<promise_type> h_;
233
234 /// Destroy the task and its coroutine frame if owned.
235 10336 ~task()
236 {
237 10336 if(h_)
238 1735 h_.destroy();
239 10336 }
240
241 /// Return false; tasks are never immediately ready.
242 1607 bool await_ready() const noexcept
243 {
244 1607 return false;
245 }
246
247 /// Return the result or rethrow any stored exception.
248 1732 auto await_resume()
249 {
250 1732 if(h_.promise().has_ep_)
251 537 std::rethrow_exception(h_.promise().ep_);
252 if constexpr (! std::is_void_v<T>)
253 1114 return std::move(*h_.promise().result_);
254 else
255 81 return;
256 }
257
258 /// Start execution with the caller's context.
259 1719 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
260 {
261 1719 h_.promise().set_continuation(cont);
262 1719 h_.promise().set_environment(env);
263 1719 return h_;
264 }
265
266 /// Return the coroutine handle.
267 2980 std::coroutine_handle<promise_type> handle() const noexcept
268 {
269 2980 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 2964 void release() noexcept
283 {
284 2964 h_ = nullptr;
285 2964 }
286
287 task(task const&) = delete;
288 task& operator=(task const&) = delete;
289
290 /// Move construct, transferring ownership.
291 5637 task(task&& other) noexcept
292 5637 : h_(std::exchange(other.h_, nullptr))
293 {
294 5637 }
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 4699 explicit task(std::coroutine_handle<promise_type> h)
310 4699 : h_(h)
311 {
312 4699 }
313 };
314
315 } // namespace capy
316 } // namespace boost
317
318 #endif
319