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