include/boost/capy/task.hpp

97.4% Lines (75/77) 93.4% Functions (900/964) 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 1230 void return_value(T value)
38 {
39 1230 result_ = std::move(value);
40 1230 }
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 1245 void return_void()
52 {
53 1245 }
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 3721 promise_type() noexcept
111 3721 : has_ep_(false)
112 {
113 3721 }
114
115 3721 ~promise_type()
116 {
117
2/2
✓ Branch 0 taken 1238 times.
✓ Branch 1 taken 2483 times.
3721 if(has_ep_)
118 1238 ep_.~exception_ptr();
119 3721 }
120
121 2764 std::exception_ptr exception() const noexcept
122 {
123
2/2
✓ Branch 0 taken 1454 times.
✓ Branch 1 taken 1310 times.
2764 if(has_ep_)
124 1454 return ep_;
125 1310 return {};
126 }
127
128 3721 task get_return_object()
129 {
130 3721 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
131 }
132
133 3721 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 3721 return awaiter{this};
155 }
156
157 3713 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 3713 return awaiter{this};
178 }
179
180 1238 void unhandled_exception()
181 {
182 1238 new (&ep_) std::exception_ptr(std::current_exception());
183 1238 has_ep_ = true;
184 1238 }
185
186 template<class Awaitable>
187 struct transform_awaiter
188 {
189 std::decay_t<Awaitable> a_;
190 promise_type* p_;
191
192 7270 bool await_ready() noexcept
193 {
194 7270 return a_.await_ready();
195 }
196
197 7265 decltype(auto) await_resume()
198 {
199 // Restore TLS before body resumes
200 7265 set_current_frame_allocator(p_->environment()->allocator);
201 7265 return a_.await_resume();
202 }
203
204 template<class Promise>
205 2110 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
206 {
207 2110 return a_.await_suspend(h, p_->environment());
208 }
209 };
210
211 template<class Awaitable>
212 7270 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 9057 std::forward<Awaitable>(a), this};
219 }
220 else
221 {
222 static_assert(sizeof(A) == 0, "requires IoAwaitable");
223 }
224 1787 }
225 };
226
227 std::coroutine_handle<promise_type> h_;
228
229 /// Destroy the task and its coroutine frame if owned.
230 8180 ~task()
231 {
232
2/2
✓ Branch 1 taken 1622 times.
✓ Branch 2 taken 6558 times.
8180 if(h_)
233 1622 h_.destroy();
234 8180 }
235
236 /// Return false; tasks are never immediately ready.
237 1494 bool await_ready() const noexcept
238 {
239 1494 return false;
240 }
241
242 /// Return the result or rethrow any stored exception.
243 1619 auto await_resume()
244 {
245
2/2
✓ Branch 1 taken 510 times.
✓ Branch 2 taken 1109 times.
1619 if(h_.promise().has_ep_)
246 510 std::rethrow_exception(h_.promise().ep_);
247 if constexpr (! std::is_void_v<T>)
248 1085 return std::move(*h_.promise().result_);
249 else
250 24 return;
251 }
252
253 /// Start execution with the caller's context.
254 1606 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
255 {
256 1606 h_.promise().set_continuation(cont);
257 1606 h_.promise().set_environment(env);
258 1606 return h_;
259 }
260
261 /// Return the coroutine handle.
262 2115 std::coroutine_handle<promise_type> handle() const noexcept
263 {
264 2115 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 2099 void release() noexcept
278 {
279 2099 h_ = nullptr;
280 2099 }
281
282 task(task const&) = delete;
283 task& operator=(task const&) = delete;
284
285 /// Move construct, transferring ownership.
286 4459 task(task&& other) noexcept
287 4459 : h_(std::exchange(other.h_, nullptr))
288 {
289 4459 }
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 3721 explicit task(std::coroutine_handle<promise_type> h)
305 3721 : h_(h)
306 {
307 3721 }
308 };
309
310 } // namespace capy
311 } // namespace boost
312
313 #endif
314