TLA Line data 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 HIT 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 4699 : bool await_ready() const noexcept
140 : {
141 4699 : return false;
142 : }
143 :
144 4699 : void await_suspend(std::coroutine_handle<>) const noexcept
145 : {
146 4699 : }
147 :
148 4696 : void await_resume() const noexcept
149 : {
150 : // Restore TLS when body starts executing
151 4696 : set_current_frame_allocator(p_->environment()->allocator);
152 4696 : }
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 4691 : bool await_ready() const noexcept
164 : {
165 4691 : return false;
166 : }
167 :
168 4691 : std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
169 : {
170 4691 : return p_->continuation();
171 : }
172 :
173 MIS 0 : void await_resume() const noexcept
174 : {
175 0 : }
176 : };
177 HIT 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
|