GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/task.hpp
Date: 2026-01-19 00:56:52
Exec Total Coverage
Lines: 65 70 92.9%
Functions: 161 166 97.0%
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 268 void return_value(T value)
40 {
41 268 result_ = std::move(value);
42 268 }
43 };
44
45 template<>
46 struct task_return_base<void>
47 {
48 28 void return_void()
49 {
50 28 }
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 , stop_token_support<promise_type>
83 , detail::task_return_base<T>
84 {
85 any_executor_ref ex_;
86 any_executor_ref caller_ex_;
87 any_coro continuation_;
88 std::exception_ptr ep_;
89 detail::frame_allocator_base* alloc_ = nullptr;
90 bool needs_dispatch_ = false;
91
92 400 task get_return_object()
93 {
94 400 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
95 }
96
97 400 auto initial_suspend() noexcept
98 {
99 struct awaiter
100 {
101 promise_type* p_;
102
103 200 bool await_ready() const noexcept
104 {
105 200 return false;
106 }
107
108 200 void await_suspend(any_coro) const noexcept
109 {
110 // Capture TLS allocator while it's still valid
111 200 p_->alloc_ = get_frame_allocator();
112 200 }
113
114 199 void await_resume() const noexcept
115 {
116 // Restore TLS when body starts executing
117 199 if(p_->alloc_)
118 set_frame_allocator(*p_->alloc_);
119 199 }
120 };
121 400 return awaiter{this};
122 }
123
124 398 auto final_suspend() noexcept
125 {
126 struct awaiter
127 {
128 promise_type* p_;
129
130 199 bool await_ready() const noexcept
131 {
132 199 return false;
133 }
134
135 199 any_coro await_suspend(any_coro) const noexcept
136 {
137 199 if(p_->continuation_)
138 {
139 // Same executor: true symmetric transfer
140 182 if(!p_->needs_dispatch_)
141 182 return p_->continuation_;
142 return p_->caller_ex_.dispatch(p_->continuation_);
143 }
144 17 return std::noop_coroutine();
145 }
146
147 void await_resume() const noexcept
148 {
149 }
150 };
151 398 return awaiter{this};
152 }
153
154 // return_void() or return_value() inherited from task_return_base
155
156 74 void unhandled_exception()
157 {
158 74 ep_ = std::current_exception();
159 74 }
160
161 template<class Awaitable>
162 struct transform_awaiter
163 {
164 std::decay_t<Awaitable> a_;
165 promise_type* p_;
166
167 127 bool await_ready()
168 {
169 127 return a_.await_ready();
170 }
171
172 127 auto await_resume()
173 {
174 // Restore TLS before body resumes
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 64 times.
127 if(p_->alloc_)
176 set_frame_allocator(*p_->alloc_);
177 127 return a_.await_resume();
178 }
179
180 template<class Promise>
181 127 auto await_suspend(std::coroutine_handle<Promise> h)
182 {
183
1/1
✓ Branch 4 taken 64 times.
127 return a_.await_suspend(h, p_->ex_, p_->stop_token());
184 }
185 };
186
187 template<class Awaitable>
188 127 auto transform_awaitable(Awaitable&& a)
189 {
190 using A = std::decay_t<Awaitable>;
191 if constexpr (IoAwaitable<A, any_executor_ref>)
192 {
193 // Zero-overhead path for I/O awaitables
194 return transform_awaiter<Awaitable>{
195 206 std::forward<Awaitable>(a), this};
196 }
197 else
198 {
199 static_assert("legacy tasks not supported");
200 #if 0
201 // Trampoline fallback for legacy awaitables
202 return make_affine(std::forward<Awaitable>(a), ex_);
203 #endif
204 }
205 79 }
206 };
207
208 std::coroutine_handle<promise_type> h_;
209
210 1110 ~task()
211 {
212
2/2
✓ Branch 1 taken 102 times.
✓ Branch 2 taken 453 times.
1110 if(h_)
213 204 h_.destroy();
214 1110 }
215
216 203 bool await_ready() const noexcept
217 {
218 203 return false;
219 }
220
221 201 auto await_resume()
222 {
223
2/2
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 85 times.
201 if(h_.promise().ep_)
224 32 std::rethrow_exception(h_.promise().ep_);
225 if constexpr (! std::is_void_v<T>)
226 143 return std::move(*h_.promise().result_);
227 else
228 26 return;
229 }
230
231 // IoAwaitable: receive caller's executor and stop_token for completion dispatch
232 template<typename Ex>
233 201 any_coro await_suspend(any_coro continuation, Ex const& caller_ex, std::stop_token token)
234 {
235 201 h_.promise().caller_ex_ = caller_ex;
236 201 h_.promise().continuation_ = continuation;
237 201 h_.promise().ex_ = caller_ex;
238 201 h_.promise().set_stop_token(token);
239 201 h_.promise().needs_dispatch_ = false;
240 201 return h_;
241 }
242
243 /** Release ownership of the coroutine handle.
244
245 After calling this, the task no longer owns the handle and will
246 not destroy it. The caller is responsible for the handle's lifetime.
247
248 @return The coroutine handle, or nullptr if already released.
249 */
250 202 auto release() noexcept ->
251 std::coroutine_handle<promise_type>
252 {
253 202 return std::exchange(h_, nullptr);
254 }
255
256 // Non-copyable
257 task(task const&) = delete;
258 task& operator=(task const&) = delete;
259
260 // Movable
261 709 task(task&& other) noexcept
262 709 : h_(std::exchange(other.h_, nullptr))
263 {
264 709 }
265
266 task& operator=(task&& other) noexcept
267 {
268 if(this != &other)
269 {
270 if(h_)
271 h_.destroy();
272 h_ = std::exchange(other.h_, nullptr);
273 }
274 return *this;
275 }
276
277 private:
278 400 explicit task(std::coroutine_handle<promise_type> h)
279 400 : h_(h)
280 {
281 400 }
282 };
283
284 } // namespace capy
285 } // namespace boost
286
287 #endif
288