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 :
17 : #include <boost/capy/concept/io_awaitable.hpp>
18 : #include <boost/capy/concept/io_awaitable_task.hpp>
19 : #include <boost/capy/concept/io_launchable_task.hpp>
20 :
21 : #include <coroutine>
22 : #include <exception>
23 : #include <stop_token>
24 : #include <type_traits>
25 :
26 : namespace boost {
27 : namespace capy {
28 :
29 : /** Tag type for coroutine stop token retrieval.
30 :
31 : This tag is returned by @ref get_stop_token and intercepted by a
32 : promise type's `await_transform` to yield the coroutine's current
33 : stop token. The tag itself carries no data; it serves only as a
34 : sentinel for compile-time dispatch.
35 :
36 : @see get_stop_token
37 : @see io_awaitable_support
38 : */
39 : struct get_stop_token_tag {};
40 :
41 : /** Tag type for coroutine executor retrieval.
42 :
43 : This tag is returned by @ref get_executor and intercepted by a
44 : promise type's `await_transform` to yield the coroutine's current
45 : executor. The tag itself carries no data; it serves only as a
46 : sentinel for compile-time dispatch.
47 :
48 : @see get_executor
49 : @see io_awaitable_support
50 : */
51 : struct get_executor_tag {};
52 :
53 : /** Return a tag that yields the current stop token when awaited.
54 :
55 : Use `co_await get_stop_token()` inside a coroutine whose promise
56 : type supports stop token access (e.g., inherits from
57 : @ref io_awaitable_support). The returned stop token reflects whatever
58 : token was passed to this coroutine when it was awaited.
59 :
60 : @par Example
61 : @code
62 : task<void> cancellable_work()
63 : {
64 : auto token = co_await get_stop_token();
65 : for (int i = 0; i < 1000; ++i)
66 : {
67 : if (token.stop_requested())
68 : co_return; // Exit gracefully on cancellation
69 : co_await process_chunk(i);
70 : }
71 : }
72 : @endcode
73 :
74 : @par Behavior
75 : @li If no stop token was propagated, returns a default-constructed
76 : `std::stop_token` (where `stop_possible()` returns `false`).
77 : @li The returned token remains valid for the coroutine's lifetime.
78 : @li This operation never suspends; `await_ready()` always returns `true`.
79 :
80 : @return A tag that `await_transform` intercepts to return the stop token.
81 :
82 : @see get_stop_token_tag
83 : @see io_awaitable_support
84 : */
85 16 : inline get_stop_token_tag get_stop_token() noexcept
86 : {
87 16 : return {};
88 : }
89 :
90 : /** Return a tag that yields the current executor when awaited.
91 :
92 : Use `co_await get_executor()` inside a coroutine whose promise
93 : type supports executor access (e.g., inherits from
94 : @ref io_awaitable_support). The returned executor reflects the
95 : executor this coroutine is bound to.
96 :
97 : @par Example
98 : @code
99 : task<void> example()
100 : {
101 : executor_ref ex = co_await get_executor();
102 : // ex is the executor this coroutine is bound to
103 : }
104 : @endcode
105 :
106 : @par Behavior
107 : @li If no executor was set, returns a default-constructed
108 : `executor_ref` (where `operator bool()` returns `false`).
109 : @li This operation never suspends; `await_ready()` always returns `true`.
110 :
111 : @return A tag that `await_transform` intercepts to return the executor.
112 :
113 : @see get_executor_tag
114 : @see io_awaitable_support
115 : */
116 4 : inline get_executor_tag get_executor() noexcept
117 : {
118 4 : return {};
119 : }
120 :
121 : /** CRTP mixin that adds I/O awaitable support to a promise type.
122 :
123 : Inherit from this class to enable these capabilities in your coroutine:
124 :
125 : 1. **Stop token storage** — The mixin stores the `std::stop_token`
126 : that was passed when your coroutine was awaited.
127 :
128 : 2. **Stop token access** — Coroutine code can retrieve the token via
129 : `co_await get_stop_token()`.
130 :
131 : 3. **Executor storage** — The mixin stores the `executor_ref`
132 : that this coroutine is bound to.
133 :
134 : 4. **Executor access** — Coroutine code can retrieve the executor via
135 : `co_await get_executor()`.
136 :
137 : @tparam Derived The derived promise type (CRTP pattern).
138 :
139 : @par Basic Usage
140 :
141 : For coroutines that need to access their stop token or executor:
142 :
143 : @code
144 : struct my_task
145 : {
146 : struct promise_type : io_awaitable_support<promise_type>
147 : {
148 : my_task get_return_object();
149 : std::suspend_always initial_suspend() noexcept;
150 : std::suspend_always final_suspend() noexcept;
151 : void return_void();
152 : void unhandled_exception();
153 : };
154 :
155 : // ... awaitable interface ...
156 : };
157 :
158 : my_task example()
159 : {
160 : auto token = co_await get_stop_token();
161 : auto ex = co_await get_executor();
162 : // Use token and ex...
163 : }
164 : @endcode
165 :
166 : @par Custom Awaitable Transformation
167 :
168 : If your promise needs to transform awaitables (e.g., for affinity or
169 : logging), override `transform_awaitable` instead of `await_transform`:
170 :
171 : @code
172 : struct promise_type : io_awaitable_support<promise_type>
173 : {
174 : template<typename A>
175 : auto transform_awaitable(A&& a)
176 : {
177 : // Your custom transformation logic
178 : return std::forward<A>(a);
179 : }
180 : };
181 : @endcode
182 :
183 : The mixin's `await_transform` intercepts @ref get_stop_token_tag and
184 : @ref get_executor_tag, then delegates all other awaitables to your
185 : `transform_awaitable`.
186 :
187 : @par Making Your Coroutine an IoAwaitable
188 :
189 : The mixin handles the "inside the coroutine" part—accessing the token
190 : and executor. To receive these when your coroutine is awaited (satisfying
191 : @ref IoAwaitable), implement the `await_suspend` overload on your
192 : coroutine return type:
193 :
194 : @code
195 : struct my_task
196 : {
197 : struct promise_type : io_awaitable_support<promise_type> { ... };
198 :
199 : std::coroutine_handle<promise_type> h_;
200 :
201 : // IoAwaitable await_suspend receives and stores the token and executor
202 : template<class Ex>
203 : coro await_suspend(coro cont, Ex const& ex, std::stop_token token)
204 : {
205 : h_.promise().set_stop_token(token);
206 : h_.promise().set_executor(ex);
207 : // ... rest of suspend logic ...
208 : }
209 : };
210 : @endcode
211 :
212 : @par Thread Safety
213 : The stop token and executor are stored during `await_suspend` and read
214 : during `co_await get_stop_token()` or `co_await get_executor()`. These
215 : occur on the same logical thread of execution, so no synchronization
216 : is required.
217 :
218 : @see get_stop_token
219 : @see get_executor
220 : @see IoAwaitable
221 : */
222 : template<typename Derived>
223 : class io_awaitable_support
224 : {
225 : executor_ref executor_;
226 : std::stop_token stop_token_;
227 : coro cont_;
228 : executor_ref caller_ex_;
229 :
230 : public:
231 : /** Store continuation and caller's executor for completion dispatch.
232 :
233 : Call this from your coroutine type's `await_suspend` overload to
234 : set up the completion path. On completion, the coroutine will
235 : resume the continuation, dispatching through the caller's executor
236 : if it differs from this coroutine's executor.
237 :
238 : @param cont The continuation to resume on completion.
239 : @param caller_ex The caller's executor for completion dispatch.
240 : */
241 148 : void set_continuation(coro cont, executor_ref caller_ex) noexcept
242 : {
243 148 : cont_ = cont;
244 148 : caller_ex_ = caller_ex;
245 148 : }
246 :
247 : /** Return the handle to resume on completion with dispatch-awareness.
248 :
249 : If no continuation was set, returns `std::noop_coroutine()`.
250 : If the coroutine's executor matches the caller's executor, returns
251 : the continuation directly for symmetric transfer.
252 : Otherwise, dispatches through the caller's executor first.
253 :
254 : Call this from your `final_suspend` awaiter's `await_suspend`.
255 :
256 : @return A coroutine handle for symmetric transfer.
257 : */
258 181 : coro complete() const noexcept
259 : {
260 181 : if(!cont_)
261 33 : return std::noop_coroutine();
262 148 : if(executor_ == caller_ex_)
263 148 : return cont_;
264 0 : return caller_ex_.dispatch(cont_);
265 : }
266 :
267 : /** Store a stop token for later retrieval.
268 :
269 : Call this from your coroutine type's `await_suspend`
270 : overload to make the token available via `co_await get_stop_token()`.
271 :
272 : @param token The stop token to store.
273 : */
274 150 : void set_stop_token(std::stop_token token) noexcept
275 : {
276 150 : stop_token_ = token;
277 150 : }
278 :
279 : /** Return the stored stop token.
280 :
281 : @return The stop token, or a default-constructed token if none was set.
282 : */
283 67 : std::stop_token const& stop_token() const noexcept
284 : {
285 67 : return stop_token_;
286 : }
287 :
288 : /** Store an executor for later retrieval.
289 :
290 : Call this from your coroutine type's `await_suspend`
291 : overload to make the executor available via `co_await get_executor()`.
292 :
293 : @param ex The executor to store.
294 : */
295 150 : void set_executor(executor_ref ex) noexcept
296 : {
297 150 : executor_ = ex;
298 150 : }
299 :
300 : /** Return the stored executor.
301 :
302 : @return The executor, or a default-constructed executor_ref if none was set.
303 : */
304 67 : executor_ref executor() const noexcept
305 : {
306 67 : return executor_;
307 : }
308 :
309 : /** Transform an awaitable before co_await.
310 :
311 : Override this in your derived promise type to customize how
312 : awaitables are transformed. The default implementation passes
313 : the awaitable through unchanged.
314 :
315 : @param a The awaitable expression from `co_await a`.
316 :
317 : @return The transformed awaitable.
318 : */
319 : template<typename A>
320 : decltype(auto) transform_awaitable(A&& a)
321 : {
322 : return std::forward<A>(a);
323 : }
324 :
325 : /** Intercept co_await expressions.
326 :
327 : This function handles @ref get_stop_token_tag and @ref get_executor_tag
328 : specially, returning an awaiter that yields the stored value. All other
329 : awaitables are delegated to @ref transform_awaitable.
330 :
331 : @param t The awaited expression.
332 :
333 : @return An awaiter for the expression.
334 : */
335 : template<typename T>
336 121 : auto await_transform(T&& t)
337 : {
338 : if constexpr (std::is_same_v<std::decay_t<T>, get_stop_token_tag>)
339 : {
340 : struct awaiter
341 : {
342 : std::stop_token token_;
343 :
344 14 : bool await_ready() const noexcept
345 : {
346 14 : return true;
347 : }
348 :
349 1 : void await_suspend(coro) const noexcept
350 : {
351 1 : }
352 :
353 13 : std::stop_token await_resume() const noexcept
354 : {
355 13 : return token_;
356 : }
357 : };
358 15 : return awaiter{stop_token_};
359 : }
360 : else if constexpr (std::is_same_v<std::decay_t<T>, get_executor_tag>)
361 : {
362 : struct awaiter
363 : {
364 : executor_ref executor_;
365 :
366 2 : bool await_ready() const noexcept
367 : {
368 2 : return true;
369 : }
370 :
371 1 : void await_suspend(coro) const noexcept
372 : {
373 1 : }
374 :
375 1 : executor_ref await_resume() const noexcept
376 : {
377 1 : return executor_;
378 : }
379 : };
380 3 : return awaiter{executor_};
381 : }
382 : else
383 : {
384 52 : return static_cast<Derived*>(this)->transform_awaitable(
385 103 : std::forward<T>(t));
386 : }
387 : }
388 : };
389 :
390 : } // namespace capy
391 : } // namespace boost
392 :
393 : #endif
|