GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/task.hpp
Date: 2026-01-22 22:47:31
Exec Total Coverage
Lines: 67 71 94.4%
Functions: 103 172 59.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 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 25 void return_void()
51 {
52 25 }
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 183 std::exception_ptr exception() const noexcept
91 {
92 183 return ep_;
93 }
94
95 260 task get_return_object()
96 {
97 260 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
98 }
99
100 260 auto initial_suspend() noexcept
101 {
102 struct awaiter
103 {
104 promise_type* p_;
105
106 130 bool await_ready() const noexcept
107 {
108 130 return false;
109 }
110
111 130 void await_suspend(coro) const noexcept
112 {
113 // Capture TLS allocator while it's still valid
114 130 p_->alloc_ = get_frame_allocator();
115 130 }
116
117 129 void await_resume() const noexcept
118 {
119 // Restore TLS when body starts executing
120 129 if(p_->alloc_)
121 set_frame_allocator(*p_->alloc_);
122 129 }
123 };
124 260 return awaiter{this};
125 }
126
127 258 auto final_suspend() noexcept
128 {
129 struct awaiter
130 {
131 promise_type* p_;
132
133 129 bool await_ready() const noexcept
134 {
135 129 return false;
136 }
137
138 129 coro await_suspend(coro) const noexcept
139 {
140 129 return p_->complete();
141 }
142
143 void await_resume() const noexcept
144 {
145 }
146 };
147 258 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 70 bool await_ready()
164 {
165 70 return a_.await_ready();
166 }
167
168 70 auto await_resume()
169 {
170 // Restore TLS before body resumes
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
70 if(p_->alloc_)
172 set_frame_allocator(*p_->alloc_);
173 70 return a_.await_resume();
174 }
175
176 template<class Promise>
177 70 auto await_suspend(std::coroutine_handle<Promise> h)
178 {
179
1/1
✓ Branch 5 taken 2 times.
70 return a_.await_suspend(h, p_->executor(), p_->stop_token());
180 }
181 };
182
183 template<class Awaitable>
184 70 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 138 std::forward<Awaitable>(a), this};
192 }
193 else
194 {
195 static_assert(sizeof(A) == 0, "requires IoAwaitable");
196 }
197 68 }
198 };
199
200 std::coroutine_handle<promise_type> h_;
201
202 424 ~task()
203 {
204
2/2
✓ Branch 1 taken 35 times.
✓ Branch 2 taken 177 times.
424 if(h_)
205 70 h_.destroy();
206 424 }
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 195 std::coroutine_handle<promise_type> handle() const noexcept
238 {
239 195 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 189 void release() noexcept
248 {
249 189 h_ = nullptr;
250 189 }
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 260 explicit task(std::coroutine_handle<promise_type> h)
275 260 : h_(h)
276 {
277 260 }
278 };
279
280 } // namespace capy
281 } // namespace boost
282
283 #endif
284