GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/task.hpp
Date: 2026-01-17 14:56:58
Exec Total Coverage
Lines: 66 71 93.0%
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/ex/any_dispatcher.hpp>
15 #include <boost/capy/concept/affine_awaitable.hpp>
16 #include <boost/capy/concept/stoppable_awaitable.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_dispatcher
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_dispatcher ex_;
88 any_dispatcher 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 dispatcher: true symmetric transfer
142 205 if(!p_->needs_dispatch_)
143 205 return p_->continuation_;
144 return p_->caller_ex_(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 using A = std::decay_t<Awaitable>;
187 if constexpr (stoppable_awaitable<A, any_dispatcher>)
188
1/1
✓ Branch 4 taken 75 times.
149 return a_.await_suspend(h, p_->ex_, p_->stop_token());
189 else
190 #endif
191 48 return a_.await_suspend(h, p_->ex_);
192 }
193 };
194
195 template<class Awaitable>
196 197 auto transform_awaitable(Awaitable&& a)
197 {
198 using A = std::decay_t<Awaitable>;
199 if constexpr (affine_awaitable<A, any_dispatcher>)
200 {
201 // Zero-overhead path for affine awaitables
202 return transform_awaiter<Awaitable>{
203 346 std::forward<Awaitable>(a), this};
204 }
205 else
206 {
207 // Trampoline fallback for legacy awaitables
208 return make_affine(std::forward<Awaitable>(a), ex_);
209 }
210 149 }
211
212 #if !BOOST_CAPY_HAS_STOP_TOKEN
213 // Without stop token support, provide await_transform directly
214 template<class Awaitable>
215 auto await_transform(Awaitable&& a)
216 {
217 return transform_awaitable(std::forward<Awaitable>(a));
218 }
219 #endif
220 };
221
222 std::coroutine_handle<promise_type> h_;
223
224 1180 ~task()
225 {
226
2/2
✓ Branch 1 taken 113 times.
✓ Branch 2 taken 477 times.
1180 if(h_)
227 226 h_.destroy();
228 1180 }
229
230 225 bool await_ready() const noexcept
231 {
232 225 return false;
233 }
234
235 223 auto await_resume()
236 {
237
2/2
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 96 times.
223 if(h_.promise().ep_)
238 32 std::rethrow_exception(h_.promise().ep_);
239 if constexpr (! std::is_void_v<T>)
240 157 return std::move(*h_.promise().result_);
241 else
242 34 return;
243 }
244
245 // Affine awaitable: receive caller's dispatcher for completion dispatch
246 template<dispatcher D>
247 any_coro await_suspend(any_coro continuation, D const& caller_ex)
248 {
249 h_.promise().caller_ex_ = caller_ex;
250 h_.promise().continuation_ = continuation;
251 h_.promise().ex_ = caller_ex;
252 h_.promise().needs_dispatch_ = false;
253 return h_;
254 }
255
256 #if BOOST_CAPY_HAS_STOP_TOKEN
257 // Stoppable awaitable: receive caller's dispatcher and stop_token
258 template<dispatcher D>
259 223 any_coro await_suspend(any_coro continuation, D const& caller_ex, std::stop_token token)
260 {
261 223 h_.promise().caller_ex_ = caller_ex;
262 223 h_.promise().continuation_ = continuation;
263 223 h_.promise().ex_ = caller_ex;
264 223 h_.promise().set_stop_token(token);
265 223 h_.promise().needs_dispatch_ = false;
266 223 return h_;
267 }
268 #endif
269
270 /** Release ownership of the coroutine handle.
271
272 After calling this, the task no longer owns the handle and will
273 not destroy it. The caller is responsible for the handle's lifetime.
274
275 @return The coroutine handle, or nullptr if already released.
276 */
277 228 auto release() noexcept ->
278 std::coroutine_handle<promise_type>
279 {
280 228 return std::exchange(h_, nullptr);
281 }
282
283 // Non-copyable
284 task(task const&) = delete;
285 task& operator=(task const&) = delete;
286
287 // Movable
288 731 task(task&& other) noexcept
289 731 : h_(std::exchange(other.h_, nullptr))
290 {
291 731 }
292
293 task& operator=(task&& other) noexcept
294 {
295 if(this != &other)
296 {
297 if(h_)
298 h_.destroy();
299 h_ = std::exchange(other.h_, nullptr);
300 }
301 return *this;
302 }
303
304 private:
305 448 explicit task(std::coroutine_handle<promise_type> h)
306 448 : h_(h)
307 {
308 448 }
309 };
310
311 } // namespace capy
312 } // namespace boost
313
314 #endif
315