task.hpp

96.3% Lines (78/81) 93.3% Functions (897/961) 85.0% Branches (17/20)
task.hpp
Line Branch 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_support.hpp>
17 #include <boost/capy/ex/io_env.hpp>
18 #include <boost/capy/ex/frame_allocator.hpp>
19
20 #include <exception>
21 #include <optional>
22 #include <type_traits>
23 #include <utility>
24 #include <variant>
25
26 namespace boost {
27 namespace capy {
28
29 namespace detail {
30
31 // Helper base for result storage and return_void/return_value
32 template<typename T>
33 struct task_return_base
34 {
35 std::optional<T> result_;
36
37 1230 void return_value(T value)
38 {
39 1230 result_ = std::move(value);
40 1230 }
41
42 128 T&& result() noexcept
43 {
44 128 return std::move(*result_);
45 }
46 };
47
48 template<>
49 struct task_return_base<void>
50 {
51 1245 void return_void()
52 {
53 1245 }
54 };
55
56 } // namespace detail
57
58 /** Lazy coroutine task satisfying @ref IoRunnable.
59
60 Use `task<T>` as the return type for coroutines that perform I/O
61 and return a value of type `T`. The coroutine body does not start
62 executing until the task is awaited, enabling efficient composition
63 without unnecessary eager execution.
64
65 The task participates in the I/O awaitable protocol: when awaited,
66 it receives the caller's executor and stop token, propagating them
67 to nested `co_await` expressions. This enables cancellation and
68 proper completion dispatch across executor boundaries.
69
70 @tparam T The result type. Use `task<>` for `task<void>`.
71
72 @par Thread Safety
73 Distinct objects: Safe.
74 Shared objects: Unsafe.
75
76 @par Example
77
78 @code
79 task<int> compute_value()
80 {
81 auto [ec, n] = co_await stream.read_some( buf );
82 if( ec )
83 co_return 0;
84 co_return process( buf, n );
85 }
86
87 task<> run_session( tcp_socket sock )
88 {
89 int result = co_await compute_value();
90 // ...
91 }
92 @endcode
93
94 @see IoRunnable, IoAwaitable, run, run_async
95 */
96 template<typename T = void>
97 struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
98 task
99 {
100 struct promise_type
101 : io_awaitable_support<promise_type>
102 , detail::task_return_base<T>
103 {
104 private:
105 friend task;
106 union { std::exception_ptr ep_; };
107 bool has_ep_;
108
109 public:
110 3721 promise_type() noexcept
111 3721 : has_ep_(false)
112 {
113 3721 }
114
115 3721 ~promise_type()
116 {
117
2/2
✓ Branch 0 taken 1238 times.
✓ Branch 1 taken 2483 times.
3721 if(has_ep_)
118 1238 ep_.~exception_ptr();
119 3721 }
120
121 2758 std::exception_ptr exception() const noexcept
122 {
123
2/2
✓ Branch 0 taken 1448 times.
✓ Branch 1 taken 1310 times.
2758 if(has_ep_)
124 1448 return ep_;
125 1310 return {};
126 }
127
128 3721 task get_return_object()
129 {
130 3721 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
131 }
132
133 3721 auto initial_suspend() noexcept
134 {
135 struct awaiter
136 {
137 promise_type* p_;
138
139 144 bool await_ready() const noexcept
140 {
141 144 return false;
142 }
143
144 144 void await_suspend(std::coroutine_handle<>) const noexcept
145 {
146 144 }
147
148 144 void await_resume() const noexcept
149 {
150 // Restore TLS when body starts executing
151 144 auto* fa = p_->environment()->allocator;
152
3/6
✓ Branch 0 taken 144 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 144 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 144 times.
144 if(fa && fa != current_frame_allocator())
153 current_frame_allocator() = fa;
154 144 }
155 };
156 3721 return awaiter{this};
157 }
158
159 3713 auto final_suspend() noexcept
160 {
161 struct awaiter
162 {
163 promise_type* p_;
164
165 144 bool await_ready() const noexcept
166 {
167 144 return false;
168 }
169
170 144 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
171 {
172 144 return p_->continuation();
173 }
174
175 void await_resume() const noexcept
176 {
177 }
178 };
179 3713 return awaiter{this};
180 }
181
182 1238 void unhandled_exception()
183 {
184 1238 new (&ep_) std::exception_ptr(std::current_exception());
185 1238 has_ep_ = true;
186 1238 }
187
188 template<class Awaitable>
189 struct transform_awaiter
190 {
191 std::decay_t<Awaitable> a_;
192 promise_type* p_;
193
194 7270 bool await_ready() noexcept
195 {
196 7270 return a_.await_ready();
197 }
198
199 7265 decltype(auto) await_resume()
200 {
201 // Restore TLS before body resumes
202 7265 auto* fa = p_->environment()->allocator;
203
6/6
✓ Branch 0 taken 7187 times.
✓ Branch 1 taken 78 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 7177 times.
✓ Branch 5 taken 10 times.
✓ Branch 6 taken 7255 times.
7265 if(fa && fa != current_frame_allocator())
204 10 current_frame_allocator() = fa;
205 7265 return a_.await_resume();
206 }
207
208 template<class Promise>
209 2110 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
210 {
211 2110 return a_.await_suspend(h, p_->environment());
212 }
213 };
214
215 template<class Awaitable>
216 7270 auto transform_awaitable(Awaitable&& a)
217 {
218 using A = std::decay_t<Awaitable>;
219 if constexpr (IoAwaitable<A>)
220 {
221 return transform_awaiter<Awaitable>{
222 9057 std::forward<Awaitable>(a), this};
223 }
224 else
225 {
226 static_assert(sizeof(A) == 0, "requires IoAwaitable");
227 }
228 1787 }
229 };
230
231 std::coroutine_handle<promise_type> h_;
232
233 /// Destroy the task and its coroutine frame if owned.
234 8160 ~task()
235 {
236
2/2
✓ Branch 1 taken 1622 times.
✓ Branch 2 taken 6538 times.
8160 if(h_)
237 1622 h_.destroy();
238 8160 }
239
240 /// Return false; tasks are never immediately ready.
241 1494 bool await_ready() const noexcept
242 {
243 1494 return false;
244 }
245
246 /// Return the result or rethrow any stored exception.
247 1619 auto await_resume()
248 {
249
2/2
✓ Branch 1 taken 510 times.
✓ Branch 2 taken 1109 times.
1619 if(h_.promise().has_ep_)
250 510 std::rethrow_exception(h_.promise().ep_);
251 if constexpr (! std::is_void_v<T>)
252 1085 return std::move(*h_.promise().result_);
253 else
254 24 return;
255 }
256
257 /// Start execution with the caller's context.
258 1606 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
259 {
260 1606 h_.promise().set_continuation(cont);
261 1606 h_.promise().set_environment(env);
262 1606 return h_;
263 }
264
265 /// Return the coroutine handle.
266 2115 std::coroutine_handle<promise_type> handle() const noexcept
267 {
268 2115 return h_;
269 }
270
271 /** Release ownership of the coroutine frame.
272
273 After calling this, destroying the task does not destroy the
274 coroutine frame. The caller becomes responsible for the frame's
275 lifetime.
276
277 @par Postconditions
278 `handle()` returns the original handle, but the task no longer
279 owns it.
280 */
281 2099 void release() noexcept
282 {
283 2099 h_ = nullptr;
284 2099 }
285
286 task(task const&) = delete;
287 task& operator=(task const&) = delete;
288
289 /// Move construct, transferring ownership.
290 4439 task(task&& other) noexcept
291 4439 : h_(std::exchange(other.h_, nullptr))
292 {
293 4439 }
294
295 /// Move assign, transferring ownership.
296 task& operator=(task&& other) noexcept
297 {
298 if(this != &other)
299 {
300 if(h_)
301 h_.destroy();
302 h_ = std::exchange(other.h_, nullptr);
303 }
304 return *this;
305 }
306
307 private:
308 3721 explicit task(std::coroutine_handle<promise_type> h)
309 3721 : h_(h)
310 {
311 3721 }
312 };
313
314 } // namespace capy
315 } // namespace boost
316
317 #endif
318