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 3698 promise_type() noexcept
111 3698 : has_ep_(false)
112 {
113 3698 }
114
115 3698 ~promise_type()
116 {
117
2/2
✓ Branch 0 taken 1229 times.
✓ Branch 1 taken 2469 times.
3698 if(has_ep_)
118 1229 ep_.~exception_ptr();
119 3698 }
120
121 2748 std::exception_ptr exception() const noexcept
122 {
123
2/2
✓ Branch 0 taken 1440 times.
✓ Branch 1 taken 1308 times.
2748 if(has_ep_)
124 1440 return ep_;
125 1308 return {};
126 }
127
128 3698 task get_return_object()
129 {
130 3698 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
131 }
132
133 3698 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 3698 return awaiter{this};
157 }
158
159 3691 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 3691 return awaiter{this};
180 }
181
182 1229 void unhandled_exception()
183 {
184 1229 new (&ep_) std::exception_ptr(std::current_exception());
185 1229 has_ep_ = true;
186 1229 }
187
188 template<class Awaitable>
189 struct transform_awaiter
190 {
191 std::decay_t<Awaitable> a_;
192 promise_type* p_;
193
194 7256 bool await_ready() noexcept
195 {
196 7256 return a_.await_ready();
197 }
198
199 7251 decltype(auto) await_resume()
200 {
201 // Restore TLS before body resumes
202 7251 auto* fa = p_->environment()->allocator;
203
6/6
✓ Branch 0 taken 7184 times.
✓ Branch 1 taken 67 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 7175 times.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 7242 times.
7251 if(fa && fa != current_frame_allocator())
204 9 current_frame_allocator() = fa;
205 7251 return a_.await_resume();
206 }
207
208 template<class Promise>
209 2098 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
210 {
211 2098 return a_.await_suspend(h, p_->environment());
212 }
213 };
214
215 template<class Awaitable>
216 7256 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 9030 std::forward<Awaitable>(a), this};
223 }
224 else
225 {
226 static_assert(sizeof(A) == 0, "requires IoAwaitable");
227 }
228 1774 }
229 };
230
231 std::coroutine_handle<promise_type> h_;
232
233 /// Destroy the task and its coroutine frame if owned.
234 8128 ~task()
235 {
236
2/2
✓ Branch 1 taken 1616 times.
✓ Branch 2 taken 6512 times.
8128 if(h_)
237 1616 h_.destroy();
238 8128 }
239
240 /// Return false; tasks are never immediately ready.
241 1489 bool await_ready() const noexcept
242 {
243 1489 return false;
244 }
245
246 /// Return the result or rethrow any stored exception.
247 1614 auto await_resume()
248 {
249
2/2
✓ Branch 1 taken 507 times.
✓ Branch 2 taken 1107 times.
1614 if(h_.promise().has_ep_)
250 507 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 1601 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
259 {
260 1601 h_.promise().set_continuation(cont);
261 1601 h_.promise().set_environment(env);
262 1601 return h_;
263 }
264
265 /// Return the coroutine handle.
266 2098 std::coroutine_handle<promise_type> handle() const noexcept
267 {
268 2098 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 2082 void release() noexcept
282 {
283 2082 h_ = nullptr;
284 2082 }
285
286 task(task const&) = delete;
287 task& operator=(task const&) = delete;
288
289 /// Move construct, transferring ownership.
290 4430 task(task&& other) noexcept
291 4430 : h_(std::exchange(other.h_, nullptr))
292 {
293 4430 }
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 3698 explicit task(std::coroutine_handle<promise_type> h)
309 3698 : h_(h)
310 {
311 3698 }
312 };
313
314 } // namespace capy
315 } // namespace boost
316
317 #endif
318