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 115 : void return_value(T value)
39 : {
40 115 : result_ = std::move(value);
41 115 : }
42 : };
43 :
44 : template<>
45 : struct task_return_base<void>
46 : {
47 26 : void return_void()
48 : {
49 26 : }
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 177 : task get_return_object()
94 : {
95 177 : return task{std::coroutine_handle<promise_type>::from_promise(*this)};
96 : }
97 :
98 177 : auto initial_suspend() noexcept
99 : {
100 : struct awaiter
101 : {
102 : promise_type* p_;
103 :
104 177 : bool await_ready() const noexcept
105 : {
106 177 : return false;
107 : }
108 :
109 177 : void await_suspend(any_coro) const noexcept
110 : {
111 : // Capture TLS allocator while it's still valid
112 177 : p_->alloc_ = get_frame_allocator();
113 177 : }
114 :
115 176 : void await_resume() const noexcept
116 : {
117 : // Restore TLS when body starts executing
118 176 : if(p_->alloc_)
119 147 : set_frame_allocator(*p_->alloc_);
120 176 : }
121 : };
122 177 : return awaiter{this};
123 : }
124 :
125 176 : auto final_suspend() noexcept
126 : {
127 : struct awaiter
128 : {
129 : promise_type* p_;
130 :
131 176 : bool await_ready() const noexcept
132 : {
133 176 : return false;
134 : }
135 :
136 176 : any_coro await_suspend(any_coro) const noexcept
137 : {
138 176 : if(p_->continuation_)
139 : {
140 : // Same dispatcher: true symmetric transfer
141 159 : if(!p_->needs_dispatch_)
142 159 : 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 176 : return awaiter{this};
153 : }
154 :
155 : // return_void() or return_value() inherited from task_return_base
156 :
157 35 : void unhandled_exception()
158 : {
159 35 : ep_ = std::current_exception();
160 35 : }
161 :
162 : template<class Awaitable>
163 : struct transform_awaiter
164 : {
165 : std::decay_t<Awaitable> a_;
166 : promise_type* p_;
167 :
168 101 : bool await_ready()
169 : {
170 101 : return a_.await_ready();
171 : }
172 :
173 101 : auto await_resume()
174 : {
175 : // Restore TLS before body resumes
176 101 : if(p_->alloc_)
177 89 : set_frame_allocator(*p_->alloc_);
178 101 : return a_.await_resume();
179 : }
180 :
181 : template<class Promise>
182 101 : 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 78 : return a_.await_suspend(h, p_->ex_, p_->stop_token_);
188 : else
189 : #endif
190 23 : return a_.await_suspend(h, p_->ex_);
191 : }
192 : };
193 :
194 : template<class Awaitable>
195 101 : 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 178 : 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 77 : }
210 : };
211 :
212 : std::coroutine_handle<promise_type> h_;
213 :
214 720 : ~task()
215 : {
216 720 : if(h_)
217 160 : h_.destroy();
218 720 : }
219 :
220 160 : bool await_ready() const noexcept
221 : {
222 160 : return false;
223 : }
224 :
225 159 : auto await_resume()
226 : {
227 159 : if(h_.promise().ep_)
228 31 : std::rethrow_exception(h_.promise().ep_);
229 : if constexpr (! std::is_void_v<T>)
230 107 : return std::move(*h_.promise().result_);
231 : else
232 21 : return;
233 : }
234 :
235 : // Affine awaitable: receive caller's dispatcher for completion dispatch
236 : template<dispatcher D>
237 44 : any_coro await_suspend(any_coro continuation, D const& caller_ex)
238 : {
239 44 : h_.promise().caller_ex_ = caller_ex;
240 44 : h_.promise().continuation_ = continuation;
241 44 : h_.promise().ex_ = caller_ex;
242 44 : h_.promise().needs_dispatch_ = false;
243 44 : 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 115 : any_coro await_suspend(any_coro continuation, D const& caller_ex, std::stop_token token)
250 : {
251 115 : h_.promise().caller_ex_ = caller_ex;
252 115 : h_.promise().continuation_ = continuation;
253 115 : h_.promise().ex_ = caller_ex;
254 115 : h_.promise().stop_token_ = token;
255 115 : h_.promise().needs_dispatch_ = false;
256 115 : 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 20 : auto release() noexcept ->
268 : std::coroutine_handle<promise_type>
269 : {
270 20 : 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 543 : task(task&& other) noexcept
279 543 : : h_(std::exchange(other.h_, nullptr))
280 : {
281 543 : }
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 177 : explicit task(std::coroutine_handle<promise_type> h)
296 177 : : h_(h)
297 : {
298 177 : }
299 : };
300 :
301 : } // namespace capy
302 : } // namespace boost
303 :
304 : #endif
|