GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 62.3% 356 / 0 / 571
Functions: 65.1% 138 / 0 / 212
Branches: 38.6% 34 / 0 / 88

libs/capy/include/boost/capy/task.hpp
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 98 void return_value(T value)
37 {
38 98 result_ = std::move(value);
39 98 }
40
41 52 T&& result() noexcept
42 {
43 52 return std::move(*result_);
44 }
45 };
46
47 template<>
48 struct task_return_base<void>
49 {
50 57 void return_void()
51 {
52 57 }
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 108 std::exception_ptr exception() const noexcept
91 {
92 108 return ep_;
93 }
94
95 178 task get_return_object()
96 {
97 178 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
98 }
99
100 178 auto initial_suspend() noexcept
101 {
102 struct awaiter
103 {
104 promise_type* p_;
105
106 16 bool await_ready() const noexcept
107 {
108 16 return false;
109 }
110
111 16 void await_suspend(coro) const noexcept
112 {
113 // Capture TLS allocator while it's still valid
114 16 p_->alloc_ = get_frame_allocator();
115 16 }
116
117 16 void await_resume() const noexcept
118 {
119 // Restore TLS when body starts executing
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if(p_->alloc_)
121 set_frame_allocator(*p_->alloc_);
122 16 }
123 };
124 178 return awaiter{this};
125 }
126
127 177 auto final_suspend() noexcept
128 {
129 struct awaiter
130 {
131 promise_type* p_;
132
133 16 bool await_ready() const noexcept
134 {
135 16 return false;
136 }
137
138 16 coro await_suspend(coro) const noexcept
139 {
140 16 return p_->complete();
141 }
142
143 void await_resume() const noexcept
144 {
145 }
146 };
147 177 return awaiter{this};
148 }
149
150 // return_void() or return_value() inherited from task_return_base
151
152 22 void unhandled_exception()
153 {
154 22 ep_ = std::current_exception();
155 22 }
156
157 template<class Awaitable>
158 struct transform_awaiter
159 {
160 std::decay_t<Awaitable> a_;
161 promise_type* p_;
162
163 102 bool await_ready()
164 {
165 102 return a_.await_ready();
166 }
167
168 102 auto await_resume()
169 {
170 // Restore TLS before body resumes
171
14/46
boost::capy::task<bool>::promise_type::transform_awaiter<boost::capy::task<std::stop_token> >::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
boost::capy::task<boost::capy::io_result<unsigned long> >::promise_type::transform_awaiter<boost::capy::(anonymous namespace)::mock_error_stream::read_some<boost::capy::consuming_buffers<boost::capy::mutable_buffer> >(boost::capy::consuming_buffers<boost::capy::mutable_buffer> const&)::awaitable>::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
boost::capy::task<boost::capy::io_result<unsigned long> >::promise_type::transform_awaiter<boost::capy::(anonymous namespace)::mock_error_stream::write_some<boost::capy::consuming_buffers<boost::capy::const_buffer> >(boost::capy::consuming_buffers<boost::capy::const_buffer> const&)::awaitable>::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
boost::capy::task<boost::capy::io_result<unsigned long> >::promise_type::transform_awaiter<boost::capy::(anonymous namespace)::mock_read_awaitable>::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
boost::capy::task<boost::capy::io_result<unsigned long> >::promise_type::transform_awaiter<boost::capy::(anonymous namespace)::mock_write_awaitable>::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
boost::capy::task<int>::promise_type::transform_awaiter<boost::capy::detail::run_on_awaitable<boost::capy::test::custom_task<int>, boost::capy::test_executor> >::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
boost::capy::task<int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
boost::capy::task<int>::promise_type::transform_awaiter<boost::capy::task<std::tuple<int, int> > >::await_resume():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
boost::capy::task<int>::promise_type::transform_awaiter<boost::capy::task<void> >::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
boost::capy::task<std::tuple<int, int, int, int, int, int, int, int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, int, int, int, int, int, int, int> >::await_resume():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
boost::capy::task<std::tuple<int, int, int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, int, int> >::await_resume():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
boost::capy::task<std::tuple<int, int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, int> >::await_resume():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
boost::capy::task<std::tuple<int, int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, void, int> >::await_resume():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
boost::capy::task<std::tuple<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, void> >::await_resume():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
boost::capy::task<std::tuple<int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, void> >::await_resume():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
boost::capy::task<std::tuple<int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int> >::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
boost::capy::task<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::await_resume():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
boost::capy::task<void>::promise_type::transform_awaiter<boost::capy::async_event::wait_awaiter>::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
boost::capy::task<void>::promise_type::transform_awaiter<boost::capy::detail::run_on_awaitable<boost::capy::test::custom_task<void>, boost::capy::test_executor> >::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
boost::capy::task<void>::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<void, void, void> >::await_resume():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
boost::capy::task<void>::promise_type::transform_awaiter<boost::capy::task<boost::capy::io_result<unsigned long> > >::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
boost::capy::task<void>::promise_type::transform_awaiter<boost::capy::task<int> >::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
boost::capy::task<void>::promise_type::transform_awaiter<boost::capy::task<void> >::await_resume():
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
102 if(p_->alloc_)
172 set_frame_allocator(*p_->alloc_);
173 102 return a_.await_resume();
174 }
175
176 template<class Promise>
177 65 auto await_suspend(std::coroutine_handle<Promise> h)
178 {
179
3/12
auto boost::capy::task<bool>::promise_type::transform_awaiter<boost::capy::task<std::stop_token> >::await_suspend<boost::capy::task<bool>::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<bool>::promise_type>):
✓ Branch 5 taken 1 time.
auto boost::capy::task<int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::task<int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<int>::promise_type>):
✗ Branch 5 not taken.
auto boost::capy::task<std::tuple<int, int, int, int, int, int, int, int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, int, int, int, int, int, int, int> >::await_suspend<boost::capy::task<std::tuple<int, int, int, int, int, int, int, int> >::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<std::tuple<int, int, int, int, int, int, int, int> >::promise_type>):
✗ Branch 5 not taken.
auto boost::capy::task<std::tuple<int, int, int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, int, int> >::await_suspend<boost::capy::task<std::tuple<int, int, int> >::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<std::tuple<int, int, int> >::promise_type>):
✗ Branch 5 not taken.
auto boost::capy::task<std::tuple<int, int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, int> >::await_suspend<boost::capy::task<std::tuple<int, int> >::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<std::tuple<int, int> >::promise_type>):
✗ Branch 5 not taken.
auto boost::capy::task<std::tuple<int, int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, void, int> >::await_suspend<boost::capy::task<std::tuple<int, int> >::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<std::tuple<int, int> >::promise_type>):
✗ Branch 5 not taken.
auto boost::capy::task<std::tuple<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, void> >::await_suspend<boost::capy::task<std::tuple<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<std::tuple<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type>):
✗ Branch 5 not taken.
auto boost::capy::task<std::tuple<int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int, void> >::await_suspend<boost::capy::task<std::tuple<int> >::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<std::tuple<int> >::promise_type>):
✗ Branch 5 not taken.
auto boost::capy::task<std::tuple<int> >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<int> >::await_suspend<boost::capy::task<std::tuple<int> >::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<std::tuple<int> >::promise_type>):
✓ Branch 5 taken 1 time.
auto boost::capy::task<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::await_suspend<boost::capy::task<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type>):
✗ Branch 5 not taken.
auto boost::capy::task<void>::promise_type::transform_awaiter<boost::capy::detail::when_all_launcher<void, void, void> >::await_suspend<boost::capy::task<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<void>::promise_type>):
✗ Branch 5 not taken.
auto boost::capy::task<void>::promise_type::transform_awaiter<boost::capy::task<boost::capy::io_result<unsigned long> > >::await_suspend<boost::capy::task<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::task<void>::promise_type>):
✓ Branch 5 taken 16 times.
65 return a_.await_suspend(h, p_->executor(), p_->stop_token());
180 }
181 };
182
183 template<class Awaitable>
184 102 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 153 std::forward<Awaitable>(a), this};
192 }
193 else
194 {
195 static_assert(sizeof(A) == 0, "requires IoAwaitable");
196 }
197 51 }
198 };
199
200 std::coroutine_handle<promise_type> h_;
201
202 277 ~task()
203 {
204
11/16
boost::capy::task<bool>::~task():
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
boost::capy::task<boost::capy::io_result<unsigned long> >::~task():
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 16 times.
boost::capy::task<int>::~task():
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 113 times.
boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::~task():
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
boost::capy::task<std::stop_token>::~task():
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
boost::capy::task<std::tuple<int, int> >::~task():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::task<std::tuple<int> >::~task():
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
boost::capy::task<void>::~task():
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 78 times.
277 if(h_)
205 51 h_.destroy();
206 277 }
207
208 51 bool await_ready() const noexcept
209 {
210 51 return false;
211 }
212
213 50 auto await_resume()
214 {
215
5/12
boost::capy::task<boost::capy::io_result<unsigned long> >::await_resume():
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
boost::capy::task<int>::await_resume():
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 21 times.
boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::await_resume():
✗ Branch 2 not taken.
✗ Branch 3 not taken.
boost::capy::task<std::stop_token>::await_resume():
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
boost::capy::task<std::tuple<int, int> >::await_resume():
✗ Branch 2 not taken.
✗ Branch 3 not taken.
boost::capy::task<void>::await_resume():
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
50 if(h_.promise().ep_)
216 4 std::rethrow_exception(h_.promise().ep_);
217 if constexpr (! std::is_void_v<T>)
218 38 return std::move(*h_.promise().result_);
219 else
220 8 return;
221 }
222
223 // IoAwaitable: receive caller's executor and stop_token for completion dispatch
224 template<typename Ex>
225 50 coro await_suspend(coro cont, Ex const& caller_ex, std::stop_token token)
226 {
227 50 h_.promise().set_continuation(cont, caller_ex);
228 50 h_.promise().set_executor(caller_ex);
229 50 h_.promise().set_stop_token(token);
230 50 return h_;
231 }
232
233 /** Return the coroutine handle.
234
235 @return The coroutine handle.
236 */
237 130 std::coroutine_handle<promise_type> handle() const noexcept
238 {
239 130 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 127 void release() noexcept
248 {
249 127 h_ = nullptr;
250 127 }
251
252 // Non-copyable
253 task(task const&) = delete;
254 task& operator=(task const&) = delete;
255
256 // Movable
257 99 task(task&& other) noexcept
258 99 : h_(std::exchange(other.h_, nullptr))
259 {
260 99 }
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 178 explicit task(std::coroutine_handle<promise_type> h)
275 178 : h_(h)
276 {
277 178 }
278 };
279
280 } // namespace capy
281 } // namespace boost
282
283 #endif
284