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