GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/task.hpp
Date: 2026-01-15 23:24:40
Exec Total Coverage
Lines: 68 71 95.8%
Functions: 204 208 98.1%
Branches: 7 7 100.0%

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