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