GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/task.hpp
Date: 2026-01-18 20:48:06
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 // Trampoline fallback for legacy awaitables
200 return make_affine(std::forward<Awaitable>(a), ex_);
201 }
202 79 }
203 };
204
205 std::coroutine_handle<promise_type> h_;
206
207 1110 ~task()
208 {
209
2/2
✓ Branch 1 taken 102 times.
✓ Branch 2 taken 453 times.
1110 if(h_)
210 204 h_.destroy();
211 1110 }
212
213 203 bool await_ready() const noexcept
214 {
215 203 return false;
216 }
217
218 201 auto await_resume()
219 {
220
2/2
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 85 times.
201 if(h_.promise().ep_)
221 32 std::rethrow_exception(h_.promise().ep_);
222 if constexpr (! std::is_void_v<T>)
223 143 return std::move(*h_.promise().result_);
224 else
225 26 return;
226 }
227
228 // IoAwaitable: receive caller's executor and stop_token for completion dispatch
229 template<typename Ex>
230 201 any_coro await_suspend(any_coro continuation, Ex const& caller_ex, std::stop_token token)
231 {
232 201 h_.promise().caller_ex_ = caller_ex;
233 201 h_.promise().continuation_ = continuation;
234 201 h_.promise().ex_ = caller_ex;
235 201 h_.promise().set_stop_token(token);
236 201 h_.promise().needs_dispatch_ = false;
237 201 return h_;
238 }
239
240 /** Release ownership of the coroutine handle.
241
242 After calling this, the task no longer owns the handle and will
243 not destroy it. The caller is responsible for the handle's lifetime.
244
245 @return The coroutine handle, or nullptr if already released.
246 */
247 202 auto release() noexcept ->
248 std::coroutine_handle<promise_type>
249 {
250 202 return std::exchange(h_, nullptr);
251 }
252
253 // Non-copyable
254 task(task const&) = delete;
255 task& operator=(task const&) = delete;
256
257 // Movable
258 709 task(task&& other) noexcept
259 709 : h_(std::exchange(other.h_, nullptr))
260 {
261 709 }
262
263 task& operator=(task&& other) noexcept
264 {
265 if(this != &other)
266 {
267 if(h_)
268 h_.destroy();
269 h_ = std::exchange(other.h_, nullptr);
270 }
271 return *this;
272 }
273
274 private:
275 400 explicit task(std::coroutine_handle<promise_type> h)
276 400 : h_(h)
277 {
278 400 }
279 };
280
281 } // namespace capy
282 } // namespace boost
283
284 #endif
285