GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/task.hpp
Date: 2026-01-18 18:26:31
Exec Total Coverage
Lines: 65 70 92.9%
Functions: 169 174 97.1%
Branches: 6 7 85.7%

Line Branch Exec Source
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/any_executor_ref.hpp>
17 #include <boost/capy/ex/frame_allocator.hpp>
18 #include <boost/capy/ex/get_stop_token.hpp>
19 #include <boost/capy/ex/make_affine.hpp>
20 #include <boost/capy/ex/stop_token_support.hpp>
21
22 #include <exception>
23 #include <optional>
24 #include <type_traits>
25 #include <utility>
26 #include <variant>
27
28 namespace boost {
29 namespace capy {
30
31 namespace detail {
32
33 // Helper base for result storage and return_void/return_value
34 template<typename T>
35 struct task_return_base
36 {
37 std::optional<T> result_;
38
39 298 void return_value(T value)
40 {
41 298 result_ = std::move(value);
42 298 }
43 };
44
45 template<>
46 struct task_return_base<void>
47 {
48 37 void return_void()
49 {
50 37 }
51 };
52
53 } // namespace detail
54
55 /** A coroutine task type implementing the affine awaitable protocol.
56
57 This task type represents an asynchronous operation that can be awaited.
58 It implements the affine awaitable protocol where `await_suspend` receives
59 the caller's executor, enabling proper completion dispatch across executor
60 boundaries.
61
62 @tparam T The return type of the task. Defaults to void.
63
64 Key features:
65 @li Lazy execution - the coroutine does not start until awaited
66 @li Symmetric transfer - uses coroutine handle returns for efficient
67 resumption
68 @li Executor inheritance - inherits caller's executor unless explicitly
69 bound
70
71 The task uses `[[clang::coro_await_elidable]]` (when available) to enable
72 heap allocation elision optimization (HALO) for nested coroutine calls.
73
74 @see any_executor_ref
75 */
76 template<typename T = void>
77 struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
78 task
79 {
80 struct promise_type
81 : frame_allocating_base
82 #if BOOST_CAPY_HAS_STOP_TOKEN
83 , stop_token_support<promise_type>
84 #endif
85 , detail::task_return_base<T>
86 {
87 any_executor_ref ex_;
88 any_executor_ref caller_ex_;
89 any_coro continuation_;
90 std::exception_ptr ep_;
91 detail::frame_allocator_base* alloc_ = nullptr;
92 bool needs_dispatch_ = false;
93
94 448 task get_return_object()
95 {
96 448 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
97 }
98
99 448 auto initial_suspend() noexcept
100 {
101 struct awaiter
102 {
103 promise_type* p_;
104
105 224 bool await_ready() const noexcept
106 {
107 224 return false;
108 }
109
110 224 void await_suspend(any_coro) const noexcept
111 {
112 // Capture TLS allocator while it's still valid
113 224 p_->alloc_ = get_frame_allocator();
114 224 }
115
116 223 void await_resume() const noexcept
117 {
118 // Restore TLS when body starts executing
119 223 if(p_->alloc_)
120 set_frame_allocator(*p_->alloc_);
121 223 }
122 };
123 448 return awaiter{this};
124 }
125
126 446 auto final_suspend() noexcept
127 {
128 struct awaiter
129 {
130 promise_type* p_;
131
132 223 bool await_ready() const noexcept
133 {
134 223 return false;
135 }
136
137 223 any_coro await_suspend(any_coro) const noexcept
138 {
139 223 if(p_->continuation_)
140 {
141 // Same executor: true symmetric transfer
142 205 if(!p_->needs_dispatch_)
143 205 return p_->continuation_;
144 return p_->caller_ex_.dispatch(p_->continuation_);
145 }
146 18 return std::noop_coroutine();
147 }
148
149 void await_resume() const noexcept
150 {
151 }
152 };
153 446 return awaiter{this};
154 }
155
156 // return_void() or return_value() inherited from task_return_base
157
158 74 void unhandled_exception()
159 {
160 74 ep_ = std::current_exception();
161 74 }
162
163 template<class Awaitable>
164 struct transform_awaiter
165 {
166 std::decay_t<Awaitable> a_;
167 promise_type* p_;
168
169 197 bool await_ready()
170 {
171 197 return a_.await_ready();
172 }
173
174 197 auto await_resume()
175 {
176 // Restore TLS before body resumes
177
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
197 if(p_->alloc_)
178 set_frame_allocator(*p_->alloc_);
179 197 return a_.await_resume();
180 }
181
182 template<class Promise>
183 197 auto await_suspend(std::coroutine_handle<Promise> h)
184 {
185 #if BOOST_CAPY_HAS_STOP_TOKEN
186
1/1
✓ Branch 4 taken 99 times.
197 return a_.await_suspend(h, p_->ex_, p_->stop_token());
187 #else
188 return a_.await_suspend(h, p_->ex_, std::stop_token{});
189 #endif
190 }
191 };
192
193 template<class Awaitable>
194 197 auto transform_awaitable(Awaitable&& a)
195 {
196 using A = std::decay_t<Awaitable>;
197 if constexpr (IoAwaitable<A, any_executor_ref>)
198 {
199 // Zero-overhead path for I/O awaitables
200 return transform_awaiter<Awaitable>{
201 346 std::forward<Awaitable>(a), this};
202 }
203 else
204 {
205 // Trampoline fallback for legacy awaitables
206 return make_affine(std::forward<Awaitable>(a), ex_);
207 }
208 149 }
209
210 #if !BOOST_CAPY_HAS_STOP_TOKEN
211 // Without stop token support, provide await_transform directly
212 template<class Awaitable>
213 auto await_transform(Awaitable&& a)
214 {
215 return transform_awaitable(std::forward<Awaitable>(a));
216 }
217 #endif
218 };
219
220 std::coroutine_handle<promise_type> h_;
221
222 1180 ~task()
223 {
224
2/2
✓ Branch 1 taken 113 times.
✓ Branch 2 taken 477 times.
1180 if(h_)
225 226 h_.destroy();
226 1180 }
227
228 225 bool await_ready() const noexcept
229 {
230 225 return false;
231 }
232
233 223 auto await_resume()
234 {
235
2/2
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 96 times.
223 if(h_.promise().ep_)
236 32 std::rethrow_exception(h_.promise().ep_);
237 if constexpr (! std::is_void_v<T>)
238 157 return std::move(*h_.promise().result_);
239 else
240 34 return;
241 }
242
243 // IoAwaitable: receive caller's executor and stop_token for completion dispatch
244 template<typename Ex>
245 223 any_coro await_suspend(any_coro continuation, Ex const& caller_ex, std::stop_token token)
246 {
247 223 h_.promise().caller_ex_ = caller_ex;
248 223 h_.promise().continuation_ = continuation;
249 223 h_.promise().ex_ = caller_ex;
250 #if BOOST_CAPY_HAS_STOP_TOKEN
251 223 h_.promise().set_stop_token(token);
252 #else
253 (void)token;
254 #endif
255 223 h_.promise().needs_dispatch_ = false;
256 223 return h_;
257 }
258
259 /** Release ownership of the coroutine handle.
260
261 After calling this, the task no longer owns the handle and will
262 not destroy it. The caller is responsible for the handle's lifetime.
263
264 @return The coroutine handle, or nullptr if already released.
265 */
266 228 auto release() noexcept ->
267 std::coroutine_handle<promise_type>
268 {
269 228 return std::exchange(h_, nullptr);
270 }
271
272 // Non-copyable
273 task(task const&) = delete;
274 task& operator=(task const&) = delete;
275
276 // Movable
277 731 task(task&& other) noexcept
278 731 : h_(std::exchange(other.h_, nullptr))
279 {
280 731 }
281
282 task& operator=(task&& other) noexcept
283 {
284 if(this != &other)
285 {
286 if(h_)
287 h_.destroy();
288 h_ = std::exchange(other.h_, nullptr);
289 }
290 return *this;
291 }
292
293 private:
294 448 explicit task(std::coroutine_handle<promise_type> h)
295 448 : h_(h)
296 {
297 448 }
298 };
299
300 } // namespace capy
301 } // namespace boost
302
303 #endif
304