GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/io_awaitable.hpp
Date: 2026-01-21 22:59:20
Exec Total Coverage
Lines: 29 29 100.0%
Functions: 45 45 100.0%
Branches: 0 0 -%

Line Branch Exec Source
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 320 void set_stop_token(capy::stop_token token) noexcept
184 {
185 320 stop_token_ = token;
186 320 }
187
188 /** Return the stored stop token.
189
190 @return The stop token, or a default-constructed token if none was set.
191 */
192 129 capy::stop_token const& stop_token() const noexcept
193 {
194 129 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 366 void set_executor(executor_ref ex) noexcept
205 {
206 366 ex_ = ex;
207 366 }
208
209 /** Return the stored executor.
210
211 @return The executor, or a default-constructed executor_ref if none was set.
212 */
213 129 executor_ref executor() const noexcept
214 {
215 129 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
303