GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/task.hpp
Date: 2026-01-15 20:40:20
Exec Total Coverage
Lines: 74 77 96.1%
Functions: 197 201 98.0%
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 230 void return_value(T value)
39 {
40 230 result_ = std::move(value);
41 230 }
42 };
43
44 template<>
45 struct task_return_base<void>
46 {
47 26 void return_void()
48 {
49 26 }
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 354 task get_return_object()
94 {
95 354 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
96 }
97
98 354 auto initial_suspend() noexcept
99 {
100 struct awaiter
101 {
102 promise_type* p_;
103
104 177 bool await_ready() const noexcept
105 {
106 177 return false;
107 }
108
109 177 void await_suspend(any_coro) const noexcept
110 {
111 // Capture TLS allocator while it's still valid
112 177 p_->alloc_ = get_frame_allocator();
113 177 }
114
115 176 void await_resume() const noexcept
116 {
117 // Restore TLS when body starts executing
118 176 if(p_->alloc_)
119 147 set_frame_allocator(*p_->alloc_);
120 176 }
121 };
122 354 return awaiter{this};
123 }
124
125 352 auto final_suspend() noexcept
126 {
127 struct awaiter
128 {
129 promise_type* p_;
130
131 176 bool await_ready() const noexcept
132 {
133 176 return false;
134 }
135
136 176 any_coro await_suspend(any_coro) const noexcept
137 {
138 176 if(p_->continuation_)
139 {
140 // Same dispatcher: true symmetric transfer
141 159 if(!p_->needs_dispatch_)
142 159 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 352 return awaiter{this};
153 }
154
155 // return_void() or return_value() inherited from task_return_base
156
157 70 void unhandled_exception()
158 {
159 70 ep_ = std::current_exception();
160 70 }
161
162 template<class Awaitable>
163 struct transform_awaiter
164 {
165 std::decay_t<Awaitable> a_;
166 promise_type* p_;
167
168 202 bool await_ready()
169 {
170 202 return a_.await_ready();
171 }
172
173 202 auto await_resume()
174 {
175 // Restore TLS before body resumes
176
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 12 times.
202 if(p_->alloc_)
177 178 set_frame_allocator(*p_->alloc_);
178 202 return a_.await_resume();
179 }
180
181 template<class Promise>
182 202 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 78 times.
156 return a_.await_suspend(h, p_->ex_, p_->stop_token_);
188 else
189 #endif
190 46 return a_.await_suspend(h, p_->ex_);
191 }
192 };
193
194 template<class Awaitable>
195 202 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 356 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 154 }
210 };
211
212 std::coroutine_handle<promise_type> h_;
213
214 1440 ~task()
215 {
216
2/2
✓ Branch 1 taken 160 times.
✓ Branch 2 taken 560 times.
1440 if(h_)
217 320 h_.destroy();
218 1440 }
219
220 320 bool await_ready() const noexcept
221 {
222 320 return false;
223 }
224
225 318 auto await_resume()
226 {
227
2/2
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 128 times.
318 if(h_.promise().ep_)
228 62 std::rethrow_exception(h_.promise().ep_);
229 if constexpr (! std::is_void_v<T>)
230 214 return std::move(*h_.promise().result_);
231 else
232 42 return;
233 }
234
235 // Affine awaitable: receive caller's dispatcher for completion dispatch
236 template<dispatcher D>
237 88 any_coro await_suspend(any_coro continuation, D const& caller_ex)
238 {
239 88 h_.promise().caller_ex_ = caller_ex;
240 88 h_.promise().continuation_ = continuation;
241 88 h_.promise().ex_ = caller_ex;
242 88 h_.promise().needs_dispatch_ = false;
243 88 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 230 any_coro await_suspend(any_coro continuation, D const& caller_ex, std::stop_token token)
250 {
251 230 h_.promise().caller_ex_ = caller_ex;
252 230 h_.promise().continuation_ = continuation;
253 230 h_.promise().ex_ = caller_ex;
254 230 h_.promise().stop_token_ = token;
255 230 h_.promise().needs_dispatch_ = false;
256 230 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 40 auto release() noexcept ->
268 std::coroutine_handle<promise_type>
269 {
270 40 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 1086 task(task&& other) noexcept
279 1086 : h_(std::exchange(other.h_, nullptr))
280 {
281 1086 }
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 354 explicit task(std::coroutine_handle<promise_type> h)
296 354 : h_(h)
297 {
298 354 }
299 };
300
301 } // namespace capy
302 } // namespace boost
303
304 #endif
305