GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/task.hpp
Date: 2026-01-18 19:29:34
Exec Total Coverage
Lines: 65 70 92.9%
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/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 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_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 448 task get_return_object()
93 {
94 448 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
95 }
96
97 448 auto initial_suspend() noexcept
98 {
99 struct awaiter
100 {
101 promise_type* p_;
102
103 224 bool await_ready() const noexcept
104 {
105 224 return false;
106 }
107
108 224 void await_suspend(any_coro) const noexcept
109 {
110 // Capture TLS allocator while it's still valid
111 224 p_->alloc_ = get_frame_allocator();
112 224 }
113
114 223 void await_resume() const noexcept
115 {
116 // Restore TLS when body starts executing
117 223 if(p_->alloc_)
118 set_frame_allocator(*p_->alloc_);
119 223 }
120 };
121 448 return awaiter{this};
122 }
123
124 446 auto final_suspend() noexcept
125 {
126 struct awaiter
127 {
128 promise_type* p_;
129
130 223 bool await_ready() const noexcept
131 {
132 223 return false;
133 }
134
135 223 any_coro await_suspend(any_coro) const noexcept
136 {
137 223 if(p_->continuation_)
138 {
139 // Same executor: true symmetric transfer
140 205 if(!p_->needs_dispatch_)
141 205 return p_->continuation_;
142 return p_->caller_ex_.dispatch(p_->continuation_);
143 }
144 18 return std::noop_coroutine();
145 }
146
147 void await_resume() const noexcept
148 {
149 }
150 };
151 446 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 197 bool await_ready()
168 {
169 197 return a_.await_ready();
170 }
171
172 197 auto await_resume()
173 {
174 // Restore TLS before body resumes
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
197 if(p_->alloc_)
176 set_frame_allocator(*p_->alloc_);
177 197 return a_.await_resume();
178 }
179
180 template<class Promise>
181 197 auto await_suspend(std::coroutine_handle<Promise> h)
182 {
183
1/1
✓ Branch 4 taken 99 times.
197 return a_.await_suspend(h, p_->ex_, p_->stop_token());
184 }
185 };
186
187 template<class Awaitable>
188 197 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 346 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 149 }
203 };
204
205 std::coroutine_handle<promise_type> h_;
206
207 1180 ~task()
208 {
209
2/2
✓ Branch 1 taken 113 times.
✓ Branch 2 taken 477 times.
1180 if(h_)
210 226 h_.destroy();
211 1180 }
212
213 225 bool await_ready() const noexcept
214 {
215 225 return false;
216 }
217
218 223 auto await_resume()
219 {
220
2/2
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 96 times.
223 if(h_.promise().ep_)
221 32 std::rethrow_exception(h_.promise().ep_);
222 if constexpr (! std::is_void_v<T>)
223 157 return std::move(*h_.promise().result_);
224 else
225 34 return;
226 }
227
228 // IoAwaitable: receive caller's executor and stop_token for completion dispatch
229 template<typename Ex>
230 223 any_coro await_suspend(any_coro continuation, Ex const& caller_ex, std::stop_token token)
231 {
232 223 h_.promise().caller_ex_ = caller_ex;
233 223 h_.promise().continuation_ = continuation;
234 223 h_.promise().ex_ = caller_ex;
235 223 h_.promise().set_stop_token(token);
236 223 h_.promise().needs_dispatch_ = false;
237 223 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 228 auto release() noexcept ->
248 std::coroutine_handle<promise_type>
249 {
250 228 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 731 task(task&& other) noexcept
259 731 : h_(std::exchange(other.h_, nullptr))
260 {
261 731 }
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 448 explicit task(std::coroutine_handle<promise_type> h)
276 448 : h_(h)
277 {
278 448 }
279 };
280
281 } // namespace capy
282 } // namespace boost
283
284 #endif
285