include/boost/capy/task.hpp

97.4% Lines (75/77) 92.1% Functions (962/1044)
include/boost/capy/task.hpp
Line TLA 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 4699 if(has_ep_)
118 1567 ep_.~exception_ptr();
119 4699 }
120
121 3931 std::exception_ptr exception() const noexcept
122 {
123 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 #ifdef _MSC_VER
208 // Workaround: MSVC stores the coroutine_handle<> return
209 // value on the coroutine frame via hidden __$ReturnUdt$.
210 // After await_suspend publishes the handle to another
211 // thread, that thread can resume/destroy the frame before
212 // __resume reads the handle back for the symmetric
213 // transfer tail-call, causing a use-after-free.
214 using R = decltype(a_.await_suspend(h, p_->environment()));
215 if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
216 a_.await_suspend(h, p_->environment()).resume();
217 else
218 return a_.await_suspend(h, p_->environment());
219 #else
220 2223 return a_.await_suspend(h, p_->environment());
221 #endif
222 }
223 };
224
225 template<class Awaitable>
226 8603 auto transform_awaitable(Awaitable&& a)
227 {
228 using A = std::decay_t<Awaitable>;
229 if constexpr (IoAwaitable<A>)
230 {
231 return transform_awaiter<Awaitable>{
232 10453 std::forward<Awaitable>(a), this};
233 }
234 else
235 {
236 static_assert(sizeof(A) == 0, "requires IoAwaitable");
237 }
238 1850 }
239 };
240
241 std::coroutine_handle<promise_type> h_;
242
243 /// Destroy the task and its coroutine frame if owned.
244 10336 ~task()
245 {
246 10336 if(h_)
247 1735 h_.destroy();
248 10336 }
249
250 /// Return false; tasks are never immediately ready.
251 1607 bool await_ready() const noexcept
252 {
253 1607 return false;
254 }
255
256 /// Return the result or rethrow any stored exception.
257 1732 auto await_resume()
258 {
259 1732 if(h_.promise().has_ep_)
260 537 std::rethrow_exception(h_.promise().ep_);
261 if constexpr (! std::is_void_v<T>)
262 1114 return std::move(*h_.promise().result_);
263 else
264 81 return;
265 }
266
267 /// Start execution with the caller's context.
268 1719 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
269 {
270 1719 h_.promise().set_continuation(cont);
271 1719 h_.promise().set_environment(env);
272 1719 return h_;
273 }
274
275 /// Return the coroutine handle.
276 2980 std::coroutine_handle<promise_type> handle() const noexcept
277 {
278 2980 return h_;
279 }
280
281 /** Release ownership of the coroutine frame.
282
283 After calling this, destroying the task does not destroy the
284 coroutine frame. The caller becomes responsible for the frame's
285 lifetime.
286
287 @par Postconditions
288 `handle()` returns the original handle, but the task no longer
289 owns it.
290 */
291 2964 void release() noexcept
292 {
293 2964 h_ = nullptr;
294 2964 }
295
296 task(task const&) = delete;
297 task& operator=(task const&) = delete;
298
299 /// Move construct, transferring ownership.
300 5637 task(task&& other) noexcept
301 5637 : h_(std::exchange(other.h_, nullptr))
302 {
303 5637 }
304
305 /// Move assign, transferring ownership.
306 task& operator=(task&& other) noexcept
307 {
308 if(this != &other)
309 {
310 if(h_)
311 h_.destroy();
312 h_ = std::exchange(other.h_, nullptr);
313 }
314 return *this;
315 }
316
317 private:
318 4699 explicit task(std::coroutine_handle<promise_type> h)
319 4699 : h_(h)
320 {
321 4699 }
322 };
323
324 } // namespace capy
325 } // namespace boost
326
327 #endif
328