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

97.2% Lines (69/71) 92.2% Functions (660/716) 100.0% Branches (9/9)
libs/capy/include/boost/capy/task.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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 std::exception_ptr ep_;
105
106 2702 std::exception_ptr exception() const noexcept
107 {
108 2702 return ep_;
109 }
110
111 3630 task get_return_object()
112 {
113 3630 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
114 }
115
116 3630 auto initial_suspend() noexcept
117 {
118 struct awaiter
119 {
120 promise_type* p_;
121
122 144 bool await_ready() const noexcept
123 {
124 144 return false;
125 }
126
127 144 void await_suspend(coro) const noexcept
128 {
129 // Capture TLS allocator while it's still valid
130 144 p_->set_frame_allocator(current_frame_allocator());
131 144 }
132
133 144 void await_resume() const noexcept
134 {
135 // Restore TLS when body starts executing
136
2/2
✓ Branch 1 taken 140 times.
✓ Branch 2 taken 4 times.
144 if(p_->frame_allocator())
137 140 current_frame_allocator() = p_->frame_allocator();
138 144 }
139 };
140 3630 return awaiter{this};
141 }
142
143 3627 auto final_suspend() noexcept
144 {
145 struct awaiter
146 {
147 promise_type* p_;
148
149 144 bool await_ready() const noexcept
150 {
151 144 return false;
152 }
153
154 144 coro await_suspend(coro) const noexcept
155 {
156 144 return p_->complete();
157 }
158
159 void await_resume() const noexcept
160 {
161 }
162 };
163 3627 return awaiter{this};
164 }
165
166 1228 void unhandled_exception()
167 {
168 1228 ep_ = std::current_exception();
169 1228 }
170
171 template<class Awaitable>
172 struct transform_awaiter
173 {
174 std::decay_t<Awaitable> a_;
175 promise_type* p_;
176
177 7205 bool await_ready()
178 {
179 7205 return a_.await_ready();
180 }
181
182 7204 decltype(auto) await_resume()
183 {
184 // Restore TLS before body resumes
185
2/2
✓ Branch 1 taken 7139 times.
✓ Branch 2 taken 65 times.
7204 if(p_->frame_allocator())
186 7139 current_frame_allocator() = p_->frame_allocator();
187 7204 return a_.await_resume();
188 }
189
190 template<class Promise>
191 2060 auto await_suspend(std::coroutine_handle<Promise> h)
192 {
193
1/1
✓ Branch 5 taken 1451 times.
2060 return a_.await_suspend(h, p_->executor(), p_->stop_token());
194 }
195 };
196
197 template<class Awaitable>
198 7205 auto transform_awaitable(Awaitable&& a)
199 {
200 using A = std::decay_t<Awaitable>;
201 if constexpr (IoAwaitable<A>)
202 {
203 return transform_awaiter<Awaitable>{
204 8930 std::forward<Awaitable>(a), this};
205 }
206 else
207 {
208 static_assert(sizeof(A) == 0, "requires IoAwaitable");
209 }
210 1725 }
211 };
212
213 std::coroutine_handle<promise_type> h_;
214
215 /// Destroy the task and its coroutine frame if owned.
216 7988 ~task()
217 {
218
2/2
✓ Branch 1 taken 1602 times.
✓ Branch 2 taken 6386 times.
7988 if(h_)
219 1602 h_.destroy();
220 7988 }
221
222 /// Return false; tasks are never immediately ready.
223 1474 bool await_ready() const noexcept
224 {
225 1474 return false;
226 }
227
228 /// Return the result or rethrow any stored exception.
229 1599 auto await_resume()
230 {
231
2/2
✓ Branch 2 taken 507 times.
✓ Branch 3 taken 1092 times.
1599 if(h_.promise().ep_)
232 507 std::rethrow_exception(h_.promise().ep_);
233 if constexpr (! std::is_void_v<T>)
234 1069 return std::move(*h_.promise().result_);
235 else
236 23 return;
237 }
238
239 /// Start execution with the caller's context.
240 1587 coro await_suspend(coro cont, executor_ref caller_ex, std::stop_token token)
241 {
242 1587 h_.promise().set_continuation(cont, caller_ex);
243 1587 h_.promise().set_executor(caller_ex);
244 1587 h_.promise().set_stop_token(token);
245 1587 return h_;
246 }
247
248 /// Return the coroutine handle.
249 2044 std::coroutine_handle<promise_type> handle() const noexcept
250 {
251 2044 return h_;
252 }
253
254 /** Release ownership of the coroutine frame.
255
256 After calling this, destroying the task does not destroy the
257 coroutine frame. The caller becomes responsible for the frame's
258 lifetime.
259
260 @par Postconditions
261 `handle()` returns the original handle, but the task no longer
262 owns it.
263 */
264 2028 void release() noexcept
265 {
266 2028 h_ = nullptr;
267 2028 }
268
269 task(task const&) = delete;
270 task& operator=(task const&) = delete;
271
272 /// Move construct, transferring ownership.
273 4358 task(task&& other) noexcept
274 4358 : h_(std::exchange(other.h_, nullptr))
275 {
276 4358 }
277
278 /// Move assign, transferring ownership.
279 task& operator=(task&& other) noexcept
280 {
281 if(this != &other)
282 {
283 if(h_)
284 h_.destroy();
285 h_ = std::exchange(other.h_, nullptr);
286 }
287 return *this;
288 }
289
290 private:
291 3630 explicit task(std::coroutine_handle<promise_type> h)
292 3630 : h_(h)
293 {
294 3630 }
295 };
296
297 } // namespace capy
298 } // namespace boost
299
300 #endif
301