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/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_IO_AWAITABLE_HPP
11 : #define BOOST_CAPY_IO_AWAITABLE_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/coro.hpp>
15 : #include <boost/capy/ex/executor_ref.hpp>
16 : #include <boost/capy/ex/get_stop_token.hpp>
17 : #include <boost/capy/ex/stop_token.hpp>
18 :
19 : #include <coroutine>
20 : #include <type_traits>
21 :
22 : namespace boost {
23 : namespace capy {
24 :
25 : /** Tag type for coroutine executor retrieval.
26 :
27 : This tag is returned by @ref get_executor and intercepted by a
28 : promise type's `await_transform` to yield the coroutine's current
29 : executor. The tag itself carries no data; it serves only as a
30 : sentinel for compile-time dispatch.
31 :
32 : @see get_executor
33 : @see io_awaitable_support
34 : */
35 : struct get_executor_tag {};
36 :
37 : /** Return a tag that yields the current executor when awaited.
38 :
39 : Use `co_await get_executor()` inside a coroutine whose promise
40 : type supports executor access (e.g., inherits from
41 : @ref io_awaitable_support). The returned executor reflects the
42 : executor this coroutine is bound to.
43 :
44 : @par Example
45 : @code
46 : task<void> example()
47 : {
48 : executor_ref ex = co_await get_executor();
49 : // ex is the executor this coroutine is bound to
50 : }
51 : @endcode
52 :
53 : @par Behavior
54 : @li If no executor was set, returns a default-constructed
55 : `executor_ref` (where `operator bool()` returns `false`).
56 : @li This operation never suspends; `await_ready()` always returns `true`.
57 :
58 : @return A tag that `await_transform` intercepts to return the executor.
59 :
60 : @see get_executor_tag
61 : @see io_awaitable_support
62 : */
63 4 : inline get_executor_tag get_executor() noexcept
64 : {
65 4 : return {};
66 : }
67 :
68 : /** CRTP mixin that adds I/O awaitable support to a promise type.
69 :
70 : Inherit from this class to enable these capabilities in your coroutine:
71 :
72 : 1. **Stop token storage** — The mixin stores the `capy::stop_token`
73 : that was passed when your coroutine was awaited.
74 :
75 : 2. **Stop token access** — Coroutine code can retrieve the token via
76 : `co_await get_stop_token()`.
77 :
78 : 3. **Executor storage** — The mixin stores the `executor_ref`
79 : that this coroutine is bound to.
80 :
81 : 4. **Executor access** — Coroutine code can retrieve the executor via
82 : `co_await get_executor()`.
83 :
84 : @tparam Derived The derived promise type (CRTP pattern).
85 :
86 : @par Basic Usage
87 :
88 : For coroutines that need to access their stop token or executor:
89 :
90 : @code
91 : struct my_task
92 : {
93 : struct promise_type : io_awaitable_support<promise_type>
94 : {
95 : my_task get_return_object();
96 : std::suspend_always initial_suspend() noexcept;
97 : std::suspend_always final_suspend() noexcept;
98 : void return_void();
99 : void unhandled_exception();
100 : };
101 :
102 : // ... awaitable interface ...
103 : };
104 :
105 : my_task example()
106 : {
107 : auto token = co_await get_stop_token();
108 : auto ex = co_await get_executor();
109 : // Use token and ex...
110 : }
111 : @endcode
112 :
113 : @par Custom Awaitable Transformation
114 :
115 : If your promise needs to transform awaitables (e.g., for affinity or
116 : logging), override `transform_awaitable` instead of `await_transform`:
117 :
118 : @code
119 : struct promise_type : io_awaitable_support<promise_type>
120 : {
121 : template<typename A>
122 : auto transform_awaitable(A&& a)
123 : {
124 : // Your custom transformation logic
125 : return std::forward<A>(a);
126 : }
127 : };
128 : @endcode
129 :
130 : The mixin's `await_transform` intercepts @ref get_stop_token_tag and
131 : @ref get_executor_tag, then delegates all other awaitables to your
132 : `transform_awaitable`.
133 :
134 : @par Making Your Coroutine an IoAwaitable
135 :
136 : The mixin handles the "inside the coroutine" part—accessing the token
137 : and executor. To receive these when your coroutine is awaited (satisfying
138 : @ref IoAwaitable), implement the `await_suspend` overload on your
139 : coroutine return type:
140 :
141 : @code
142 : struct my_task
143 : {
144 : struct promise_type : io_awaitable_support<promise_type> { ... };
145 :
146 : std::coroutine_handle<promise_type> h_;
147 :
148 : // IoAwaitable await_suspend receives and stores the token and executor
149 : template<class Ex>
150 : coro await_suspend(coro cont, Ex const& ex, capy::stop_token token)
151 : {
152 : h_.promise().set_stop_token(token);
153 : h_.promise().set_executor(ex);
154 : // ... rest of suspend logic ...
155 : }
156 : };
157 : @endcode
158 :
159 : @par Thread Safety
160 : The stop token and executor are stored during `await_suspend` and read
161 : during `co_await get_stop_token()` or `co_await get_executor()`. These
162 : occur on the same logical thread of execution, so no synchronization
163 : is required.
164 :
165 : @see get_stop_token
166 : @see get_executor
167 : @see IoAwaitable
168 : */
169 : template<typename Derived>
170 : class io_awaitable_support
171 : {
172 : capy::stop_token stop_token_;
173 : executor_ref ex_;
174 :
175 : public:
176 : /** Store a stop token for later retrieval.
177 :
178 : Call this from your coroutine type's `await_suspend`
179 : overload to make the token available via `co_await get_stop_token()`.
180 :
181 : @param token The stop token to store.
182 : */
183 161 : void set_stop_token(capy::stop_token token) noexcept
184 : {
185 161 : stop_token_ = token;
186 161 : }
187 :
188 : /** Return the stored stop token.
189 :
190 : @return The stop token, or a default-constructed token if none was set.
191 : */
192 66 : capy::stop_token const& stop_token() const noexcept
193 : {
194 66 : return stop_token_;
195 : }
196 :
197 : /** Store an executor for later retrieval.
198 :
199 : Call this from your coroutine type's `await_suspend`
200 : overload to make the executor available via `co_await get_executor()`.
201 :
202 : @param ex The executor to store.
203 : */
204 184 : void set_executor(executor_ref ex) noexcept
205 : {
206 184 : ex_ = ex;
207 184 : }
208 :
209 : /** Return the stored executor.
210 :
211 : @return The executor, or a default-constructed executor_ref if none was set.
212 : */
213 66 : executor_ref executor() const noexcept
214 : {
215 66 : return ex_;
216 : }
217 :
218 : /** Transform an awaitable before co_await.
219 :
220 : Override this in your derived promise type to customize how
221 : awaitables are transformed. The default implementation passes
222 : the awaitable through unchanged.
223 :
224 : @param a The awaitable expression from `co_await a`.
225 :
226 : @return The transformed awaitable.
227 : */
228 : template<typename A>
229 : decltype(auto) transform_awaitable(A&& a)
230 : {
231 : return std::forward<A>(a);
232 : }
233 :
234 : /** Intercept co_await expressions.
235 :
236 : This function handles @ref get_stop_token_tag and @ref get_executor_tag
237 : specially, returning an awaiter that yields the stored value. All other
238 : awaitables are delegated to @ref transform_awaitable.
239 :
240 : @param t The awaited expression.
241 :
242 : @return An awaiter for the expression.
243 : */
244 : template<typename T>
245 81 : auto await_transform(T&& t)
246 : {
247 : if constexpr (std::is_same_v<std::decay_t<T>, get_stop_token_tag>)
248 : {
249 : struct awaiter
250 : {
251 : capy::stop_token token_;
252 :
253 12 : bool await_ready() const noexcept
254 : {
255 12 : return true;
256 : }
257 :
258 1 : void await_suspend(coro) const noexcept
259 : {
260 1 : }
261 :
262 11 : capy::stop_token await_resume() const noexcept
263 : {
264 11 : return token_;
265 : }
266 : };
267 13 : return awaiter{stop_token_};
268 : }
269 : else if constexpr (std::is_same_v<std::decay_t<T>, get_executor_tag>)
270 : {
271 : struct awaiter
272 : {
273 : executor_ref ex_;
274 :
275 2 : bool await_ready() const noexcept
276 : {
277 2 : return true;
278 : }
279 :
280 1 : void await_suspend(coro) const noexcept
281 : {
282 1 : }
283 :
284 1 : executor_ref await_resume() const noexcept
285 : {
286 1 : return ex_;
287 : }
288 : };
289 3 : return awaiter{ex_};
290 : }
291 : else
292 : {
293 25 : return static_cast<Derived*>(this)->transform_awaitable(
294 65 : std::forward<T>(t));
295 : }
296 : }
297 : };
298 :
299 : } // namespace capy
300 : } // namespace boost
301 :
302 : #endif
|