GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/task.hpp
Date: 2026-01-23 03:27:33
Exec Total Coverage
Lines: 67 71 94.4%
Functions: 110 180 61.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/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 163 void return_value(T value)
37 {
38 163 result_ = std::move(value);
39 163 }
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 26 void return_void()
51 {
52 26 }
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 185 std::exception_ptr exception() const noexcept
91 {
92 185 return ep_;
93 }
94
95 266 task get_return_object()
96 {
97 266 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
98 }
99
100 266 auto initial_suspend() noexcept
101 {
102 struct awaiter
103 {
104 promise_type* p_;
105
106 133 bool await_ready() const noexcept
107 {
108 133 return false;
109 }
110
111 133 void await_suspend(coro) const noexcept
112 {
113 // Capture TLS allocator while it's still valid
114 133 p_->alloc_ = get_frame_allocator();
115 133 }
116
117 131 void await_resume() const noexcept
118 {
119 // Restore TLS when body starts executing
120 131 if(p_->alloc_)
121 set_frame_allocator(*p_->alloc_);
122 131 }
123 };
124 266 return awaiter{this};
125 }
126
127 260 auto final_suspend() noexcept
128 {
129 struct awaiter
130 {
131 promise_type* p_;
132
133 130 bool await_ready() const noexcept
134 {
135 130 return false;
136 }
137
138 130 coro await_suspend(coro) const noexcept
139 {
140 130 return p_->complete();
141 }
142
143 void await_resume() const noexcept
144 {
145 }
146 };
147 260 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 74 bool await_ready()
164 {
165 74 return a_.await_ready();
166 }
167
168 72 auto await_resume()
169 {
170 // Restore TLS before body resumes
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
72 if(p_->alloc_)
172 set_frame_allocator(*p_->alloc_);
173 72 return a_.await_resume();
174 }
175
176 template<class Promise>
177 74 auto await_suspend(std::coroutine_handle<Promise> h)
178 {
179
1/1
✓ Branch 5 taken 3 times.
74 return a_.await_suspend(h, p_->executor(), p_->stop_token());
180 }
181 };
182
183 template<class Awaitable>
184 74 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 144 std::forward<Awaitable>(a), this};
192 }
193 else
194 {
195 static_assert(sizeof(A) == 0, "requires IoAwaitable");
196 }
197 70 }
198 };
199
200 std::coroutine_handle<promise_type> h_;
201
202 430 ~task()
203 {
204
2/2
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 179 times.
430 if(h_)
205 72 h_.destroy();
206 430 }
207
208 68 bool await_ready() const noexcept
209 {
210 68 return false;
211 }
212
213 66 auto await_resume()
214 {
215
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 30 times.
66 if(h_.promise().ep_)
216 8 std::rethrow_exception(h_.promise().ep_);
217 if constexpr (! std::is_void_v<T>)
218 42 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 66 coro await_suspend(coro cont, Ex const& caller_ex, std::stop_token token)
226 {
227 66 h_.promise().set_continuation(cont, caller_ex);
228 66 h_.promise().set_executor(caller_ex);
229 66 h_.promise().set_stop_token(token);
230 66 return h_;
231 }
232
233 /** Return the coroutine handle.
234
235 @return The coroutine handle.
236 */
237 199 std::coroutine_handle<promise_type> handle() const noexcept
238 {
239 199 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 193 void release() noexcept
248 {
249 193 h_ = nullptr;
250 193 }
251
252 // Non-copyable
253 task(task const&) = delete;
254 task& operator=(task const&) = delete;
255
256 // Movable
257 163 task(task&& other) noexcept
258 163 : h_(std::exchange(other.h_, nullptr))
259 {
260 163 }
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 266 explicit task(std::coroutine_handle<promise_type> h)
275 266 : h_(h)
276 {
277 266 }
278 };
279
280 } // namespace capy
281 } // namespace boost
282
283 #endif
284