include/boost/capy/task.hpp

97.4% Lines (75/77) 92.1% Functions (962/1044) 100.0% Branches (8/8)
include/boost/capy/task.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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/concept/io_awaitable.hpp>
16 #include <boost/capy/ex/io_awaitable_promise_base.hpp>
17 #include <boost/capy/ex/io_env.hpp>
18 #include <boost/capy/ex/frame_allocator.hpp>
19
20 #include <exception>
21 #include <optional>
22 #include <type_traits>
23 #include <utility>
24 #include <variant>
25
26 namespace boost {
27 namespace capy {
28
29 namespace detail {
30
31 // Helper base for result storage and return_void/return_value
32 template<typename T>
33 struct task_return_base
34 {
35 std::optional<T> result_;
36
37 1259 void return_value(T value)
38 {
39 1259 result_ = std::move(value);
40 1259 }
41
42 143 T&& result() noexcept
43 {
44 143 return std::move(*result_);
45 }
46 };
47
48 template<>
49 struct task_return_base<void>
50 {
51 1865 void return_void()
52 {
53 1865 }
54 };
55
56 } // namespace detail
57
58 /** Lazy coroutine task satisfying @ref IoRunnable.
59
60 Use `task<T>` as the return type for coroutines that perform I/O
61 and return a value of type `T`. The coroutine body does not start
62 executing until the task is awaited, enabling efficient composition
63 without unnecessary eager execution.
64
65 The task participates in the I/O awaitable protocol: when awaited,
66 it receives the caller's executor and stop token, propagating them
67 to nested `co_await` expressions. This enables cancellation and
68 proper completion dispatch across executor boundaries.
69
70 @tparam T The result type. Use `task<>` for `task<void>`.
71
72 @par Thread Safety
73 Distinct objects: Safe.
74 Shared objects: Unsafe.
75
76 @par Example
77
78 @code
79 task<int> compute_value()
80 {
81 auto [ec, n] = co_await stream.read_some( buf );
82 if( ec )
83 co_return 0;
84 co_return process( buf, n );
85 }
86
87 task<> run_session( tcp_socket sock )
88 {
89 int result = co_await compute_value();
90 // ...
91 }
92 @endcode
93
94 @see IoRunnable, IoAwaitable, run, run_async
95 */
96 template<typename T = void>
97 struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
98 task
99 {
100 struct promise_type
101 : io_awaitable_promise_base<promise_type>
102 , detail::task_return_base<T>
103 {
104 private:
105 friend task;
106 union { std::exception_ptr ep_; };
107 bool has_ep_;
108
109 public:
110 4699 promise_type() noexcept
111 4699 : has_ep_(false)
112 {
113 4699 }
114
115 4699 ~promise_type()
116 {
117
2/2
✓ Branch 0 taken 1567 times.
✓ Branch 1 taken 3132 times.
4699 if(has_ep_)
118 1567 ep_.~exception_ptr();
119 4699 }
120
121 3931 std::exception_ptr exception() const noexcept
122 {
123
2/2
✓ Branch 0 taken 2058 times.
✓ Branch 1 taken 1873 times.
3931 if(has_ep_)
124 2058 return ep_;
125 1873 return {};
126 }
127
128 4699 task get_return_object()
129 {
130 4699 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
131 }
132
133 4699 auto initial_suspend() noexcept
134 {
135 struct awaiter
136 {
137 promise_type* p_;
138
139 144 bool await_ready() const noexcept
140 {
141 144 return false;
142 }
143
144 144 void await_suspend(std::coroutine_handle<>) const noexcept
145 {
146 144 }
147
148 144 void await_resume() const noexcept
149 {
150 // Restore TLS when body starts executing
151 144 set_current_frame_allocator(p_->environment()->allocator);
152 144 }
153 };
154 4699 return awaiter{this};
155 }
156
157 4691 auto final_suspend() noexcept
158 {
159 struct awaiter
160 {
161 promise_type* p_;
162
163 144 bool await_ready() const noexcept
164 {
165 144 return false;
166 }
167
168 144 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
169 {
170 144 return p_->continuation();
171 }
172
173 void await_resume() const noexcept
174 {
175 }
176 };
177 4691 return awaiter{this};
178 }
179
180 1567 void unhandled_exception()
181 {
182 1567 new (&ep_) std::exception_ptr(std::current_exception());
183 1567 has_ep_ = true;
184 1567 }
185
186 template<class Awaitable>
187 struct transform_awaiter
188 {
189 std::decay_t<Awaitable> a_;
190 promise_type* p_;
191
192 8603 bool await_ready() noexcept
193 {
194 8603 return a_.await_ready();
195 }
196
197 8598 decltype(auto) await_resume()
198 {
199 // Restore TLS before body resumes
200 8598 set_current_frame_allocator(p_->environment()->allocator);
201 8598 return a_.await_resume();
202 }
203
204 template<class Promise>
205 2223 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
206 {
207 2223 return a_.await_suspend(h, p_->environment());
208 }
209 };
210
211 template<class Awaitable>
212 8603 auto transform_awaitable(Awaitable&& a)
213 {
214 using A = std::decay_t<Awaitable>;
215 if constexpr (IoAwaitable<A>)
216 {
217 return transform_awaiter<Awaitable>{
218 10453 std::forward<Awaitable>(a), this};
219 }
220 else
221 {
222 static_assert(sizeof(A) == 0, "requires IoAwaitable");
223 }
224 1850 }
225 };
226
227 std::coroutine_handle<promise_type> h_;
228
229 /// Destroy the task and its coroutine frame if owned.
230 10336 ~task()
231 {
232
2/2
✓ Branch 1 taken 1735 times.
✓ Branch 2 taken 8601 times.
10336 if(h_)
233 1735 h_.destroy();
234 10336 }
235
236 /// Return false; tasks are never immediately ready.
237 1607 bool await_ready() const noexcept
238 {
239 1607 return false;
240 }
241
242 /// Return the result or rethrow any stored exception.
243 1732 auto await_resume()
244 {
245
2/2
✓ Branch 1 taken 537 times.
✓ Branch 2 taken 1195 times.
1732 if(h_.promise().has_ep_)
246 537 std::rethrow_exception(h_.promise().ep_);
247 if constexpr (! std::is_void_v<T>)
248 1114 return std::move(*h_.promise().result_);
249 else
250 81 return;
251 }
252
253 /// Start execution with the caller's context.
254 1719 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
255 {
256 1719 h_.promise().set_continuation(cont);
257 1719 h_.promise().set_environment(env);
258 1719 return h_;
259 }
260
261 /// Return the coroutine handle.
262 2980 std::coroutine_handle<promise_type> handle() const noexcept
263 {
264 2980 return h_;
265 }
266
267 /** Release ownership of the coroutine frame.
268
269 After calling this, destroying the task does not destroy the
270 coroutine frame. The caller becomes responsible for the frame's
271 lifetime.
272
273 @par Postconditions
274 `handle()` returns the original handle, but the task no longer
275 owns it.
276 */
277 2964 void release() noexcept
278 {
279 2964 h_ = nullptr;
280 2964 }
281
282 task(task const&) = delete;
283 task& operator=(task const&) = delete;
284
285 /// Move construct, transferring ownership.
286 5637 task(task&& other) noexcept
287 5637 : h_(std::exchange(other.h_, nullptr))
288 {
289 5637 }
290
291 /// Move assign, transferring ownership.
292 task& operator=(task&& other) noexcept
293 {
294 if(this != &other)
295 {
296 if(h_)
297 h_.destroy();
298 h_ = std::exchange(other.h_, nullptr);
299 }
300 return *this;
301 }
302
303 private:
304 4699 explicit task(std::coroutine_handle<promise_type> h)
305 4699 : h_(h)
306 {
307 4699 }
308 };
309
310 } // namespace capy
311 } // namespace boost
312
313 #endif
314