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

96.4% Lines (80/83) 92.6% Functions (702/758) 90.0% Branches (18/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/executor_ref.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 1194 void return_value(T value)
38 {
39 1194 result_ = std::move(value);
40 1194 }
41
42 108 T&& result() noexcept
43 {
44 108 return std::move(*result_);
45 }
46 };
47
48 template<>
49 struct task_return_base<void>
50 {
51 1205 void return_void()
52 {
53 1205 }
54 };
55
56 } // namespace detail
57
58 /** Lazy coroutine task satisfying @ref IoLaunchableTask.
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 IoLaunchableTask, IoAwaitableTask, 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 3630 promise_type() noexcept
111 3630 : has_ep_(false)
112 {
113 3630 }
114
115 3630 ~promise_type()
116 {
117
2/2
✓ Branch 0 taken 1228 times.
✓ Branch 1 taken 2402 times.
3630 if(has_ep_)
118 1228 ep_.~exception_ptr();
119 3630 }
120
121 2727 std::exception_ptr exception() const noexcept
122 {
123
2/2
✓ Branch 0 taken 1438 times.
✓ Branch 1 taken 1289 times.
2727 if(has_ep_)
124 1438 return ep_;
125 1289 return {};
126 }
127
128 3630 task get_return_object()
129 {
130 3630 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
131 }
132
133 3630 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(coro) const noexcept
145 {
146 // Capture TLS allocator while it's still valid
147 144 p_->set_frame_allocator(current_frame_allocator());
148 144 }
149
150 144 void await_resume() const noexcept
151 {
152 // Restore TLS when body starts executing
153 144 auto* fa = p_->frame_allocator();
154
4/6
✓ Branch 0 taken 140 times.
✓ Branch 1 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 140 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 144 times.
144 if(fa && fa != current_frame_allocator())
155 current_frame_allocator() = fa;
156 144 }
157 };
158 3630 return awaiter{this};
159 }
160
161 3627 auto final_suspend() noexcept
162 {
163 struct awaiter
164 {
165 promise_type* p_;
166
167 144 bool await_ready() const noexcept
168 {
169 144 return false;
170 }
171
172 144 coro await_suspend(coro) const noexcept
173 {
174 144 return p_->complete();
175 }
176
177 void await_resume() const noexcept
178 {
179 }
180 };
181 3627 return awaiter{this};
182 }
183
184 1228 void unhandled_exception()
185 {
186 1228 new (&ep_) std::exception_ptr(std::current_exception());
187 1228 has_ep_ = true;
188 1228 }
189
190 template<class Awaitable>
191 struct transform_awaiter
192 {
193 std::decay_t<Awaitable> a_;
194 promise_type* p_;
195
196 7205 bool await_ready() noexcept
197 {
198 7205 return a_.await_ready();
199 }
200
201 7204 decltype(auto) await_resume()
202 {
203 // Restore TLS before body resumes
204 7204 auto* fa = p_->frame_allocator();
205
6/6
✓ Branch 0 taken 7139 times.
✓ Branch 1 taken 65 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 7130 times.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 7195 times.
7204 if(fa && fa != current_frame_allocator())
206 9 current_frame_allocator() = fa;
207 7204 return a_.await_resume();
208 }
209
210 template<class Promise>
211 2060 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
212 {
213 2060 return a_.await_suspend(h, p_->executor(), p_->stop_token());
214 }
215 };
216
217 template<class Awaitable>
218 7205 auto transform_awaitable(Awaitable&& a)
219 {
220 using A = std::decay_t<Awaitable>;
221 if constexpr (IoAwaitable<A>)
222 {
223 return transform_awaiter<Awaitable>{
224 8930 std::forward<Awaitable>(a), this};
225 }
226 else
227 {
228 static_assert(sizeof(A) == 0, "requires IoAwaitable");
229 }
230 1725 }
231 };
232
233 std::coroutine_handle<promise_type> h_;
234
235 /// Destroy the task and its coroutine frame if owned.
236 7988 ~task()
237 {
238
2/2
✓ Branch 1 taken 1602 times.
✓ Branch 2 taken 6386 times.
7988 if(h_)
239 1602 h_.destroy();
240 7988 }
241
242 /// Return false; tasks are never immediately ready.
243 1474 bool await_ready() const noexcept
244 {
245 1474 return false;
246 }
247
248 /// Return the result or rethrow any stored exception.
249 1599 auto await_resume()
250 {
251
2/2
✓ Branch 1 taken 507 times.
✓ Branch 2 taken 1092 times.
1599 if(h_.promise().has_ep_)
252 507 std::rethrow_exception(h_.promise().ep_);
253 if constexpr (! std::is_void_v<T>)
254 1069 return std::move(*h_.promise().result_);
255 else
256 23 return;
257 }
258
259 /// Start execution with the caller's context.
260 1587 coro await_suspend(coro cont,
261 executor_ref const& caller_ex, std::stop_token const& token)
262 {
263 1587 h_.promise().set_continuation(cont, caller_ex);
264 1587 h_.promise().set_executor(caller_ex);
265 1587 h_.promise().set_stop_token(token);
266 1587 return h_;
267 }
268
269 /// Return the coroutine handle.
270 2044 std::coroutine_handle<promise_type> handle() const noexcept
271 {
272 2044 return h_;
273 }
274
275 /** Release ownership of the coroutine frame.
276
277 After calling this, destroying the task does not destroy the
278 coroutine frame. The caller becomes responsible for the frame's
279 lifetime.
280
281 @par Postconditions
282 `handle()` returns the original handle, but the task no longer
283 owns it.
284 */
285 2028 void release() noexcept
286 {
287 2028 h_ = nullptr;
288 2028 }
289
290 task(task const&) = delete;
291 task& operator=(task const&) = delete;
292
293 /// Move construct, transferring ownership.
294 4358 task(task&& other) noexcept
295 4358 : h_(std::exchange(other.h_, nullptr))
296 {
297 4358 }
298
299 /// Move assign, transferring ownership.
300 task& operator=(task&& other) noexcept
301 {
302 if(this != &other)
303 {
304 if(h_)
305 h_.destroy();
306 h_ = std::exchange(other.h_, nullptr);
307 }
308 return *this;
309 }
310
311 private:
312 3630 explicit task(std::coroutine_handle<promise_type> h)
313 3630 : h_(h)
314 {
315 3630 }
316 };
317
318 } // namespace capy
319 } // namespace boost
320
321 #endif
322