GCC Code Coverage Report


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