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/ex/any_dispatcher.hpp>
15 : #include <boost/capy/concept/affine_awaitable.hpp>
16 : #include <boost/capy/concept/stoppable_awaitable.hpp>
17 : #include <boost/capy/ex/frame_allocator.hpp>
18 : #include <boost/capy/ex/make_affine.hpp>
19 :
20 : #include <exception>
21 : #include <optional>
22 : #include <stop_token>
23 : #include <type_traits>
24 : #include <utility>
25 : #include <variant>
26 :
27 : namespace boost {
28 : namespace capy {
29 :
30 : namespace detail {
31 :
32 : // Helper base for result storage and return_void/return_value
33 : template<typename T>
34 : struct task_return_base
35 : {
36 : std::optional<T> result_;
37 :
38 135 : void return_value(T value)
39 : {
40 135 : result_ = std::move(value);
41 135 : }
42 : };
43 :
44 : template<>
45 : struct task_return_base<void>
46 : {
47 33 : void return_void()
48 : {
49 33 : }
50 : };
51 :
52 : } // namespace detail
53 :
54 : /** A coroutine task type implementing the affine awaitable protocol.
55 :
56 : This task type represents an asynchronous operation that can be awaited.
57 : It implements the affine awaitable protocol where `await_suspend` receives
58 : the caller's executor, enabling proper completion dispatch across executor
59 : boundaries.
60 :
61 : @tparam T The return type of the task. Defaults to void.
62 :
63 : Key features:
64 : @li Lazy execution - the coroutine does not start until awaited
65 : @li Symmetric transfer - uses coroutine handle returns for efficient
66 : resumption
67 : @li Executor inheritance - inherits caller's executor unless explicitly
68 : bound
69 :
70 : The task uses `[[clang::coro_await_elidable]]` (when available) to enable
71 : heap allocation elision optimization (HALO) for nested coroutine calls.
72 :
73 : @see any_dispatcher
74 : */
75 : template<typename T = void>
76 : struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
77 : task
78 : {
79 : struct promise_type
80 : : frame_allocating_base
81 : , detail::task_return_base<T>
82 : {
83 : any_dispatcher ex_;
84 : any_dispatcher caller_ex_;
85 : any_coro continuation_;
86 : std::exception_ptr ep_;
87 : #if BOOST_CAPY_HAS_STOP_TOKEN
88 : std::stop_token stop_token_;
89 : #endif
90 : detail::frame_allocator_base* alloc_ = nullptr;
91 : bool needs_dispatch_ = false;
92 :
93 205 : task get_return_object()
94 : {
95 205 : return task{std::coroutine_handle<promise_type>::from_promise(*this)};
96 : }
97 :
98 205 : auto initial_suspend() noexcept
99 : {
100 : struct awaiter
101 : {
102 : promise_type* p_;
103 :
104 205 : bool await_ready() const noexcept
105 : {
106 205 : return false;
107 : }
108 :
109 205 : void await_suspend(any_coro) const noexcept
110 : {
111 : // Capture TLS allocator while it's still valid
112 205 : p_->alloc_ = get_frame_allocator();
113 205 : }
114 :
115 204 : void await_resume() const noexcept
116 : {
117 : // Restore TLS when body starts executing
118 204 : if(p_->alloc_)
119 175 : set_frame_allocator(*p_->alloc_);
120 204 : }
121 : };
122 205 : return awaiter{this};
123 : }
124 :
125 204 : auto final_suspend() noexcept
126 : {
127 : struct awaiter
128 : {
129 : promise_type* p_;
130 :
131 204 : bool await_ready() const noexcept
132 : {
133 204 : return false;
134 : }
135 :
136 204 : any_coro await_suspend(any_coro) const noexcept
137 : {
138 204 : if(p_->continuation_)
139 : {
140 : // Same dispatcher: true symmetric transfer
141 187 : if(!p_->needs_dispatch_)
142 187 : return p_->continuation_;
143 0 : return p_->caller_ex_(p_->continuation_);
144 : }
145 17 : return std::noop_coroutine();
146 : }
147 :
148 0 : void await_resume() const noexcept
149 : {
150 0 : }
151 : };
152 204 : return awaiter{this};
153 : }
154 :
155 : // return_void() or return_value() inherited from task_return_base
156 :
157 36 : void unhandled_exception()
158 : {
159 36 : ep_ = std::current_exception();
160 36 : }
161 :
162 : template<class Awaitable>
163 : struct transform_awaiter
164 : {
165 : std::decay_t<Awaitable> a_;
166 : promise_type* p_;
167 :
168 123 : bool await_ready()
169 : {
170 123 : return a_.await_ready();
171 : }
172 :
173 123 : auto await_resume()
174 : {
175 : // Restore TLS before body resumes
176 123 : if(p_->alloc_)
177 111 : set_frame_allocator(*p_->alloc_);
178 123 : return a_.await_resume();
179 : }
180 :
181 : template<class Promise>
182 123 : auto await_suspend(std::coroutine_handle<Promise> h)
183 : {
184 : #if BOOST_CAPY_HAS_STOP_TOKEN
185 : using A = std::decay_t<Awaitable>;
186 : if constexpr (stoppable_awaitable<A, any_dispatcher>)
187 89 : return a_.await_suspend(h, p_->ex_, p_->stop_token_);
188 : else
189 : #endif
190 34 : return a_.await_suspend(h, p_->ex_);
191 : }
192 : };
193 :
194 : template<class Awaitable>
195 123 : auto await_transform(Awaitable&& a)
196 : {
197 : using A = std::decay_t<Awaitable>;
198 : if constexpr (affine_awaitable<A, any_dispatcher>)
199 : {
200 : // Zero-overhead path for affine awaitables
201 : return transform_awaiter<Awaitable>{
202 222 : std::forward<Awaitable>(a), this};
203 : }
204 : else
205 : {
206 : // Trampoline fallback for legacy awaitables
207 : return make_affine(std::forward<Awaitable>(a), ex_);
208 : }
209 99 : }
210 : };
211 :
212 : std::coroutine_handle<promise_type> h_;
213 :
214 638 : ~task()
215 : {
216 638 : if(h_)
217 127 : h_.destroy();
218 638 : }
219 :
220 127 : bool await_ready() const noexcept
221 : {
222 127 : return false;
223 : }
224 :
225 126 : auto await_resume()
226 : {
227 126 : if(h_.promise().ep_)
228 21 : std::rethrow_exception(h_.promise().ep_);
229 : if constexpr (! std::is_void_v<T>)
230 91 : return std::move(*h_.promise().result_);
231 : else
232 14 : return;
233 : }
234 :
235 : // Affine awaitable: receive caller's dispatcher for completion dispatch
236 : template<dispatcher D>
237 : any_coro await_suspend(any_coro continuation, D const& caller_ex)
238 : {
239 : h_.promise().caller_ex_ = caller_ex;
240 : h_.promise().continuation_ = continuation;
241 : h_.promise().ex_ = caller_ex;
242 : h_.promise().needs_dispatch_ = false;
243 : return h_;
244 : }
245 :
246 : #if BOOST_CAPY_HAS_STOP_TOKEN
247 : // Stoppable awaitable: receive caller's dispatcher and stop_token
248 : template<dispatcher D>
249 126 : any_coro await_suspend(any_coro continuation, D const& caller_ex, std::stop_token token)
250 : {
251 126 : h_.promise().caller_ex_ = caller_ex;
252 126 : h_.promise().continuation_ = continuation;
253 126 : h_.promise().ex_ = caller_ex;
254 126 : h_.promise().stop_token_ = token;
255 126 : h_.promise().needs_dispatch_ = false;
256 126 : return h_;
257 : }
258 : #endif
259 :
260 : /** Release ownership of the coroutine handle.
261 :
262 : After calling this, the task no longer owns the handle and will
263 : not destroy it. The caller is responsible for the handle's lifetime.
264 :
265 : @return The coroutine handle, or nullptr if already released.
266 : */
267 81 : auto release() noexcept ->
268 : std::coroutine_handle<promise_type>
269 : {
270 81 : return std::exchange(h_, nullptr);
271 : }
272 :
273 : // Non-copyable
274 : task(task const&) = delete;
275 : task& operator=(task const&) = delete;
276 :
277 : // Movable
278 433 : task(task&& other) noexcept
279 433 : : h_(std::exchange(other.h_, nullptr))
280 : {
281 433 : }
282 :
283 : task& operator=(task&& other) noexcept
284 : {
285 : if(this != &other)
286 : {
287 : if(h_)
288 : h_.destroy();
289 : h_ = std::exchange(other.h_, nullptr);
290 : }
291 : return *this;
292 : }
293 :
294 : private:
295 205 : explicit task(std::coroutine_handle<promise_type> h)
296 205 : : h_(h)
297 : {
298 205 : }
299 : };
300 :
301 : } // namespace capy
302 : } // namespace boost
303 :
304 : #endif
|