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

97.2% Lines (69/71) 76.4% Functions (339/444) 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 815 void return_value(T value)
38 {
39 815 result_ = std::move(value);
40 815 }
41
42 52 T&& result() noexcept
43 {
44 52 return std::move(*result_);
45 }
46 };
47
48 template<>
49 struct task_return_base<void>
50 {
51 953 void return_void()
52 {
53 953 }
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 2192 std::exception_ptr exception() const noexcept
90 {
91 2192 return ep_;
92 }
93
94 2845 task get_return_object()
95 {
96 2845 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
97 }
98
99 2845 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 2845 return awaiter{this};
124 }
125
126 2842 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 2842 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 6833 bool await_ready()
161 {
162 6833 return a_.await_ready();
163 }
164
165 6694 decltype(auto) await_resume()
166 {
167 // Restore TLS before body resumes
168
2/2
✓ Branch 1 taken 6630 times.
✓ Branch 2 taken 64 times.
6694 if(p_->frame_allocator())
169 6630 current_frame_allocator() = p_->frame_allocator();
170 6694 return a_.await_resume();
171 }
172
173 template<class Promise>
174 1801 auto await_suspend(std::coroutine_handle<Promise> h)
175 {
176
1/1
✓ Branch 5 taken 1459 times.
1801 return a_.await_suspend(h, p_->executor(), p_->stop_token());
177 }
178 };
179
180 template<class Awaitable>
181 6833 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 8061 std::forward<Awaitable>(a), this};
188 }
189 else
190 {
191 static_assert(sizeof(A) == 0, "requires IoAwaitable");
192 }
193 1228 }
194 };
195
196 std::coroutine_handle<promise_type> h_;
197
198 5615 ~task()
199 {
200
2/2
✓ Branch 1 taken 1218 times.
✓ Branch 2 taken 4397 times.
5615 if(h_)
201 1218 h_.destroy();
202 5615 }
203
204 1091 bool await_ready() const noexcept
205 {
206 1091 return false;
207 }
208
209 1216 auto await_resume()
210 {
211
2/2
✓ Branch 2 taken 462 times.
✓ Branch 3 taken 754 times.
1216 if(h_.promise().ep_)
212 462 std::rethrow_exception(h_.promise().ep_);
213 if constexpr (! std::is_void_v<T>)
214 746 return std::move(*h_.promise().result_);
215 else
216 8 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 1630 std::coroutine_handle<promise_type> handle() const noexcept
233 {
234 1630 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 1627 void release() noexcept
243 {
244 1627 h_ = nullptr;
245 1627 }
246
247 // Non-copyable
248 task(task const&) = delete;
249 task& operator=(task const&) = delete;
250
251 // Movable
252 2770 task(task&& other) noexcept
253 2770 : h_(std::exchange(other.h_, nullptr))
254 {
255 2770 }
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 2845 explicit task(std::coroutine_handle<promise_type> h)
270 2845 : h_(h)
271 {
272 2845 }
273 };
274
275 } // namespace capy
276 } // namespace boost
277
278 #endif
279