libs/capy/include/boost/capy/task.hpp

96.3% Lines (78/81) 93.3% Functions (877/940) 85.0% Branches (17/20)
libs/capy/include/boost/capy/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 1229 void return_value(T value)
38 {
39 1229 result_ = std::move(value);
40 1229 }
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 1233 void return_void()
52 {
53 1233 }
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 3708 promise_type() noexcept
111 3708 : has_ep_(false)
112 {
113 3708 }
114
115 3708 ~promise_type()
116 {
117
2/2
✓ Branch 0 taken 1238 times.
✓ Branch 1 taken 2470 times.
3708 if(has_ep_)
118 1238 ep_.~exception_ptr();
119 3708 }
120
121 2756 std::exception_ptr exception() const noexcept
122 {
123
2/2
✓ Branch 0 taken 1448 times.
✓ Branch 1 taken 1308 times.
2756 if(has_ep_)
124 1448 return ep_;
125 1308 return {};
126 }
127
128 3708 task get_return_object()
129 {
130 3708 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
131 }
132
133 3708 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 3708 return awaiter{this};
157 }
158
159 3700 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 3700 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 7259 bool await_ready() noexcept
195 {
196 7259 return a_.await_ready();
197 }
198
199 7254 decltype(auto) await_resume()
200 {
201 // Restore TLS before body resumes
202 7254 auto* fa = p_->environment()->allocator;
203
6/6
✓ Branch 0 taken 7185 times.
✓ Branch 1 taken 69 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 7176 times.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 7245 times.
7254 if(fa && fa != current_frame_allocator())
204 9 current_frame_allocator() = fa;
205 7254 return a_.await_resume();
206 }
207
208 template<class Promise>
209 2101 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
210 {
211 2101 return a_.await_suspend(h, p_->environment());
212 }
213 };
214
215 template<class Awaitable>
216 7259 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 9036 std::forward<Awaitable>(a), this};
223 }
224 else
225 {
226 static_assert(sizeof(A) == 0, "requires IoAwaitable");
227 }
228 1777 }
229 };
230
231 std::coroutine_handle<promise_type> h_;
232
233 /// Destroy the task and its coroutine frame if owned.
234 8141 ~task()
235 {
236
2/2
✓ Branch 1 taken 1620 times.
✓ Branch 2 taken 6521 times.
8141 if(h_)
237 1620 h_.destroy();
238 8141 }
239
240 /// Return false; tasks are never immediately ready.
241 1492 bool await_ready() const noexcept
242 {
243 1492 return false;
244 }
245
246 /// Return the result or rethrow any stored exception.
247 1617 auto await_resume()
248 {
249
2/2
✓ Branch 1 taken 510 times.
✓ Branch 2 taken 1107 times.
1617 if(h_.promise().has_ep_)
250 510 std::rethrow_exception(h_.promise().ep_);
251 if constexpr (! std::is_void_v<T>)
252 1084 return std::move(*h_.promise().result_);
253 else
254 23 return;
255 }
256
257 /// Start execution with the caller's context.
258 1604 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
259 {
260 1604 h_.promise().set_continuation(cont);
261 1604 h_.promise().set_environment(env);
262 1604 return h_;
263 }
264
265 /// Return the coroutine handle.
266 2104 std::coroutine_handle<promise_type> handle() const noexcept
267 {
268 2104 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 2088 void release() noexcept
282 {
283 2088 h_ = nullptr;
284 2088 }
285
286 task(task const&) = delete;
287 task& operator=(task const&) = delete;
288
289 /// Move construct, transferring ownership.
290 4433 task(task&& other) noexcept
291 4433 : h_(std::exchange(other.h_, nullptr))
292 {
293 4433 }
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 3708 explicit task(std::coroutine_handle<promise_type> h)
309 3708 : h_(h)
310 {
311 3708 }
312 };
313
314 } // namespace capy
315 } // namespace boost
316
317 #endif
318