GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 79.8% 355 / 0 / 445
Functions: 87.3% 103 / 1 / 119
Branches: 44.3% 39 / 0 / 88

libs/capy/include/boost/capy/io_awaitable.hpp
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
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 37 inline get_stop_token_tag get_stop_token() noexcept
86 {
87 37 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 362 void set_continuation(coro cont, executor_ref caller_ex) noexcept
242 {
243 362 cont_ = cont;
244 362 caller_ex_ = caller_ex;
245 362 }
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 395 coro complete() const noexcept
259 {
260
21/44
boost::capy::io_awaitable_support<boost::capy::task<bool>::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
boost::capy::io_awaitable_support<boost::capy::task<boost::capy::io_result<unsigned long> >::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
boost::capy::io_awaitable_support<boost::capy::task<double>::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
boost::capy::io_awaitable_support<boost::capy::task<int>::promise_type>::complete() const:
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 218 times.
boost::capy::io_awaitable_support<boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type>::complete() const:
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 7 times.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, int> >::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double> > >::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<int> > >::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<std::monostate, int> > >::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<std::monostate> > >::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
boost::capy::io_awaitable_support<boost::capy::task<std::stop_token>::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int, int, int, int, int, int, int, int> >::promise_type>::complete() const:
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int, int, int> >::promise_type>::complete() const:
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int, int> >::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type>::complete() const:
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int> >::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type>::complete() const:
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<unsigned long>::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
boost::capy::io_awaitable_support<boost::capy::task<void>::promise_type>::complete() const:
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 49 times.
boost::capy::io_awaitable_support<boost::capy::test::custom_task<int>::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
boost::capy::io_awaitable_support<boost::capy::test::custom_task<void>::promise_type>::complete() const:
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
395 if(!cont_)
261 33 return std::noop_coroutine();
262
18/44
boost::capy::io_awaitable_support<boost::capy::task<bool>::promise_type>::complete() const:
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<boost::capy::io_result<unsigned long> >::promise_type>::complete() const:
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<double>::promise_type>::complete() const:
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<int>::promise_type>::complete() const:
✓ Branch 1 taken 218 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type>::complete() const:
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, int> >::promise_type>::complete() const:
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::promise_type>::complete() const:
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double> > >::promise_type>::complete() const:
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<int> > >::promise_type>::complete() const:
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<std::monostate, int> > >::promise_type>::complete() const:
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::pair<unsigned long, std::variant<std::monostate> > >::promise_type>::complete() const:
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::stop_token>::promise_type>::complete() const:
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int, int, int, int, int, int, int, int> >::promise_type>::complete() const:
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int, int, int> >::promise_type>::complete() const:
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int, int> >::promise_type>::complete() const:
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type>::complete() const:
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<int> >::promise_type>::complete() const:
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::promise_type>::complete() const:
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<unsigned long>::promise_type>::complete() const:
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::task<void>::promise_type>::complete() const:
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::test::custom_task<int>::promise_type>::complete() const:
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
boost::capy::io_awaitable_support<boost::capy::test::custom_task<void>::promise_type>::complete() const:
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
362 if(executor_ == caller_ex_)
263 362 return cont_;
264 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 364 void set_stop_token(std::stop_token token) noexcept
275 {
276 364 stop_token_ = token;
277 364 }
278
279 /** Return the stored stop token.
280
281 @return The stop token, or a default-constructed token if none was set.
282 */
283 164 std::stop_token const& stop_token() const noexcept
284 {
285 164 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 364 void set_executor(executor_ref ex) noexcept
296 {
297 364 executor_ = ex;
298 364 }
299
300 /** Return the stored executor.
301
302 @return The executor, or a default-constructed executor_ref if none was set.
303 */
304 164 executor_ref executor() const noexcept
305 {
306 164 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 239 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 35 bool await_ready() const noexcept
345 {
346 35 return true;
347 }
348
349 1 void await_suspend(coro) const noexcept
350 {
351 1 }
352
353 34 std::stop_token await_resume() const noexcept
354 {
355 34 return token_;
356 }
357 };
358 36 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 132 return static_cast<Derived*>(this)->transform_awaitable(
385 200 std::forward<T>(t));
386 }
387 }
388 };
389
390 } // namespace capy
391 } // namespace boost
392
393 #endif
394