GCC Code Coverage Report


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