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