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