GCC Code Coverage Report


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