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/concept/io_awaitable.hpp>
16 : #include <boost/capy/ex/any_executor_ref.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 134 : void return_value(T value)
40 : {
41 134 : result_ = std::move(value);
42 134 : }
43 : };
44 :
45 : template<>
46 : struct task_return_base<void>
47 : {
48 28 : void return_void()
49 : {
50 28 : }
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_executor_ref
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 : , stop_token_support<promise_type>
83 : , detail::task_return_base<T>
84 : {
85 : any_executor_ref ex_;
86 : any_executor_ref caller_ex_;
87 : any_coro continuation_;
88 : std::exception_ptr ep_;
89 : detail::frame_allocator_base* alloc_ = nullptr;
90 : bool needs_dispatch_ = false;
91 :
92 200 : task get_return_object()
93 : {
94 200 : return task{std::coroutine_handle<promise_type>::from_promise(*this)};
95 : }
96 :
97 200 : auto initial_suspend() noexcept
98 : {
99 : struct awaiter
100 : {
101 : promise_type* p_;
102 :
103 200 : bool await_ready() const noexcept
104 : {
105 200 : return false;
106 : }
107 :
108 200 : void await_suspend(any_coro) const noexcept
109 : {
110 : // Capture TLS allocator while it's still valid
111 200 : p_->alloc_ = get_frame_allocator();
112 200 : }
113 :
114 199 : void await_resume() const noexcept
115 : {
116 : // Restore TLS when body starts executing
117 199 : if(p_->alloc_)
118 0 : set_frame_allocator(*p_->alloc_);
119 199 : }
120 : };
121 200 : return awaiter{this};
122 : }
123 :
124 199 : auto final_suspend() noexcept
125 : {
126 : struct awaiter
127 : {
128 : promise_type* p_;
129 :
130 199 : bool await_ready() const noexcept
131 : {
132 199 : return false;
133 : }
134 :
135 199 : any_coro await_suspend(any_coro) const noexcept
136 : {
137 199 : if(p_->continuation_)
138 : {
139 : // Same executor: true symmetric transfer
140 182 : if(!p_->needs_dispatch_)
141 182 : return p_->continuation_;
142 0 : return p_->caller_ex_.dispatch(p_->continuation_);
143 : }
144 17 : return std::noop_coroutine();
145 : }
146 :
147 0 : void await_resume() const noexcept
148 : {
149 0 : }
150 : };
151 199 : return awaiter{this};
152 : }
153 :
154 : // return_void() or return_value() inherited from task_return_base
155 :
156 37 : void unhandled_exception()
157 : {
158 37 : ep_ = std::current_exception();
159 37 : }
160 :
161 : template<class Awaitable>
162 : struct transform_awaiter
163 : {
164 : std::decay_t<Awaitable> a_;
165 : promise_type* p_;
166 :
167 64 : bool await_ready()
168 : {
169 64 : return a_.await_ready();
170 : }
171 :
172 64 : auto await_resume()
173 : {
174 : // Restore TLS before body resumes
175 64 : if(p_->alloc_)
176 0 : set_frame_allocator(*p_->alloc_);
177 64 : return a_.await_resume();
178 : }
179 :
180 : template<class Promise>
181 64 : auto await_suspend(std::coroutine_handle<Promise> h)
182 : {
183 64 : return a_.await_suspend(h, p_->ex_, p_->stop_token());
184 : }
185 : };
186 :
187 : template<class Awaitable>
188 64 : auto transform_awaitable(Awaitable&& a)
189 : {
190 : using A = std::decay_t<Awaitable>;
191 : if constexpr (IoAwaitable<A, any_executor_ref>)
192 : {
193 : // Zero-overhead path for I/O awaitables
194 : return transform_awaiter<Awaitable>{
195 104 : std::forward<Awaitable>(a), this};
196 : }
197 : else
198 : {
199 : static_assert("legacy tasks not supported");
200 : #if 0
201 : // Trampoline fallback for legacy awaitables
202 : return make_affine(std::forward<Awaitable>(a), ex_);
203 : #endif
204 : }
205 40 : }
206 : };
207 :
208 : std::coroutine_handle<promise_type> h_;
209 :
210 555 : ~task()
211 : {
212 555 : if(h_)
213 102 : h_.destroy();
214 555 : }
215 :
216 102 : bool await_ready() const noexcept
217 : {
218 102 : return false;
219 : }
220 :
221 101 : auto await_resume()
222 : {
223 101 : if(h_.promise().ep_)
224 16 : std::rethrow_exception(h_.promise().ep_);
225 : if constexpr (! std::is_void_v<T>)
226 72 : return std::move(*h_.promise().result_);
227 : else
228 13 : return;
229 : }
230 :
231 : // IoAwaitable: receive caller's executor and stop_token for completion dispatch
232 : template<typename Ex>
233 101 : any_coro await_suspend(any_coro continuation, Ex const& caller_ex, std::stop_token token)
234 : {
235 101 : h_.promise().caller_ex_ = caller_ex;
236 101 : h_.promise().continuation_ = continuation;
237 101 : h_.promise().ex_ = caller_ex;
238 101 : h_.promise().set_stop_token(token);
239 101 : h_.promise().needs_dispatch_ = false;
240 101 : return h_;
241 : }
242 :
243 : /** Release ownership of the coroutine handle.
244 :
245 : After calling this, the task no longer owns the handle and will
246 : not destroy it. The caller is responsible for the handle's lifetime.
247 :
248 : @return The coroutine handle, or nullptr if already released.
249 : */
250 101 : auto release() noexcept ->
251 : std::coroutine_handle<promise_type>
252 : {
253 101 : return std::exchange(h_, nullptr);
254 : }
255 :
256 : // Non-copyable
257 : task(task const&) = delete;
258 : task& operator=(task const&) = delete;
259 :
260 : // Movable
261 355 : task(task&& other) noexcept
262 355 : : h_(std::exchange(other.h_, nullptr))
263 : {
264 355 : }
265 :
266 : task& operator=(task&& other) noexcept
267 : {
268 : if(this != &other)
269 : {
270 : if(h_)
271 : h_.destroy();
272 : h_ = std::exchange(other.h_, nullptr);
273 : }
274 : return *this;
275 : }
276 :
277 : private:
278 200 : explicit task(std::coroutine_handle<promise_type> h)
279 200 : : h_(h)
280 : {
281 200 : }
282 : };
283 :
284 : } // namespace capy
285 : } // namespace boost
286 :
287 : #endif
|