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

97.2% Lines (69/71) 78.5% Functions (383/488) 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 835 void return_value(T value)
38 {
39 835 result_ = std::move(value);
40 835 }
41
42 62 T&& result() noexcept
43 {
44 62 return std::move(*result_);
45 }
46 };
47
48 template<>
49 struct task_return_base<void>
50 {
51 957 void return_void()
52 {
53 957 }
54 };
55
56 } // namespace detail
57
58 /** A coroutine task type implementing the affine awaitable protocol.
59
60 This task type represents an asynchronous operation that can be awaited.
61 It implements the affine awaitable protocol where `await_suspend` receives
62 the caller's executor, enabling proper completion dispatch across executor
63 boundaries.
64
65 @tparam T The return type of the task. Defaults to void.
66
67 Key features:
68 @li Lazy execution - the coroutine does not start until awaited
69 @li Symmetric transfer - uses coroutine handle returns for efficient
70 resumption
71 @li Executor inheritance - inherits caller's executor unless explicitly
72 bound
73
74 The task uses `[[clang::coro_await_elidable]]` (when available) to enable
75 heap allocation elision optimization (HALO) for nested coroutine calls.
76
77 @see executor_ref
78 */
79 template<typename T = void>
80 struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
81 task
82 {
83 struct promise_type
84 : io_awaitable_support<promise_type>
85 , detail::task_return_base<T>
86 {
87 std::exception_ptr ep_;
88
89 2204 std::exception_ptr exception() const noexcept
90 {
91 2204 return ep_;
92 }
93
94 2869 task get_return_object()
95 {
96 2869 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
97 }
98
99 2869 auto initial_suspend() noexcept
100 {
101 struct awaiter
102 {
103 promise_type* p_;
104
105 279 bool await_ready() const noexcept
106 {
107 279 return false;
108 }
109
110 279 void await_suspend(coro) const noexcept
111 {
112 // Capture TLS allocator while it's still valid
113 279 p_->set_frame_allocator(current_frame_allocator());
114 279 }
115
116 279 void await_resume() const noexcept
117 {
118 // Restore TLS when body starts executing
119
2/2
✓ Branch 1 taken 255 times.
✓ Branch 2 taken 24 times.
279 if(p_->frame_allocator())
120 255 current_frame_allocator() = p_->frame_allocator();
121 279 }
122 };
123 2869 return awaiter{this};
124 }
125
126 2866 auto final_suspend() noexcept
127 {
128 struct awaiter
129 {
130 promise_type* p_;
131
132 279 bool await_ready() const noexcept
133 {
134 279 return false;
135 }
136
137 279 coro await_suspend(coro) const noexcept
138 {
139 279 return p_->complete();
140 }
141
142 void await_resume() const noexcept
143 {
144 }
145 };
146 2866 return awaiter{this};
147 }
148
149 1074 void unhandled_exception()
150 {
151 1074 ep_ = std::current_exception();
152 1074 }
153
154 template<class Awaitable>
155 struct transform_awaiter
156 {
157 std::decay_t<Awaitable> a_;
158 promise_type* p_;
159
160 6845 bool await_ready()
161 {
162 6845 return a_.await_ready();
163 }
164
165 6844 decltype(auto) await_resume()
166 {
167 // Restore TLS before body resumes
168
2/2
✓ Branch 1 taken 6780 times.
✓ Branch 2 taken 64 times.
6844 if(p_->frame_allocator())
169 6780 current_frame_allocator() = p_->frame_allocator();
170 6844 return a_.await_resume();
171 }
172
173 template<class Promise>
174 1813 auto await_suspend(std::coroutine_handle<Promise> h)
175 {
176
1/1
✓ Branch 5 taken 1597 times.
1813 return a_.await_suspend(h, p_->executor(), p_->stop_token());
177 }
178 };
179
180 template<class Awaitable>
181 6845 auto transform_awaitable(Awaitable&& a)
182 {
183 using A = std::decay_t<Awaitable>;
184 if constexpr (IoAwaitable<A>)
185 {
186 return transform_awaiter<Awaitable>{
187 8085 std::forward<Awaitable>(a), this};
188 }
189 else
190 {
191 static_assert(sizeof(A) == 0, "requires IoAwaitable");
192 }
193 1240 }
194 };
195
196 std::coroutine_handle<promise_type> h_;
197
198 5675 ~task()
199 {
200
2/2
✓ Branch 1 taken 1230 times.
✓ Branch 2 taken 4445 times.
5675 if(h_)
201 1230 h_.destroy();
202 5675 }
203
204 1103 bool await_ready() const noexcept
205 {
206 1103 return false;
207 }
208
209 1228 auto await_resume()
210 {
211
2/2
✓ Branch 2 taken 462 times.
✓ Branch 3 taken 766 times.
1228 if(h_.promise().ep_)
212 462 std::rethrow_exception(h_.promise().ep_);
213 if constexpr (! std::is_void_v<T>)
214 756 return std::move(*h_.promise().result_);
215 else
216 10 return;
217 }
218
219 // IoAwaitable: receive caller's executor and stop_token for completion dispatch
220 1216 coro await_suspend(coro cont, executor_ref caller_ex, std::stop_token token)
221 {
222 1216 h_.promise().set_continuation(cont, caller_ex);
223 1216 h_.promise().set_executor(caller_ex);
224 1216 h_.promise().set_stop_token(token);
225 1216 return h_;
226 }
227
228 /** Return the coroutine handle.
229
230 @return The coroutine handle.
231 */
232 1654 std::coroutine_handle<promise_type> handle() const noexcept
233 {
234 1654 return h_;
235 }
236
237 /** Release ownership of the coroutine handle.
238
239 After calling this, the task no longer owns the handle and will
240 not destroy it. The caller is responsible for the handle's lifetime.
241 */
242 1639 void release() noexcept
243 {
244 1639 h_ = nullptr;
245 1639 }
246
247 // Non-copyable
248 task(task const&) = delete;
249 task& operator=(task const&) = delete;
250
251 // Movable
252 2806 task(task&& other) noexcept
253 2806 : h_(std::exchange(other.h_, nullptr))
254 {
255 2806 }
256
257 task& operator=(task&& other) noexcept
258 {
259 if(this != &other)
260 {
261 if(h_)
262 h_.destroy();
263 h_ = std::exchange(other.h_, nullptr);
264 }
265 return *this;
266 }
267
268 private:
269 2869 explicit task(std::coroutine_handle<promise_type> h)
270 2869 : h_(h)
271 {
272 2869 }
273 };
274
275 } // namespace capy
276 } // namespace boost
277
278 #endif
279