GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/ex/async_op.hpp
Date: 2026-01-18 18:26:31
Exec Total Coverage
Lines: 25 26 96.2%
Functions: 12 15 80.0%
Branches: 8 14 57.1%

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_ASYNC_OP_HPP
11 #define BOOST_CAPY_ASYNC_OP_HPP
12
13 #include <boost/capy/detail/config.hpp>
14
15 #include <concepts>
16 #include <coroutine>
17 #include <exception>
18 #include <functional>
19 #include <memory>
20 #include <stop_token>
21 #include <variant>
22
23 namespace boost {
24 namespace capy {
25 namespace detail {
26
27 template<class T>
28 struct async_op_impl_base
29 {
30 48 virtual ~async_op_impl_base() = default;
31 virtual void start(std::function<void()> on_done) = 0;
32 virtual T get_result() = 0;
33 };
34
35 struct async_op_void_impl_base
36 {
37 virtual ~async_op_void_impl_base() = default;
38 virtual void start(std::function<void()> on_done) = 0;
39 virtual void get_result() = 0;
40 };
41
42 template<class T, class DeferredOp>
43 struct async_op_impl : async_op_impl_base<T>
44 {
45 DeferredOp op_;
46 std::variant<std::exception_ptr, T> result_{};
47
48 explicit
49 48 async_op_impl(DeferredOp&& op)
50 48 : op_(std::forward<DeferredOp>(op))
51 {
52 48 }
53
54 void
55 48 start(std::function<void()> on_done) override
56 {
57
1/1
✓ Branch 2 taken 24 times.
96 std::move(op_)(
58 120 [this, on_done = std::move(on_done)](auto&&... args) mutable
59 {
60
2/3
✓ Branch 2 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 10 taken 18 times.
24 result_.template emplace<1>(T{std::forward<decltype(args)>(args)...});
61 24 on_done();
62 });
63 48 }
64
65 T
66 48 get_result() override
67 {
68
2/6
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 24 times.
48 if (result_.index() == 0 && std::get<0>(result_))
69 std::rethrow_exception(std::get<0>(result_));
70 48 return std::move(std::get<1>(result_));
71 }
72 };
73
74 template<class DeferredOp>
75 struct async_op_void_impl : async_op_void_impl_base
76 {
77 DeferredOp op_;
78 std::exception_ptr exception_{};
79
80 explicit
81 async_op_void_impl(DeferredOp&& op)
82 : op_(std::forward<DeferredOp>(op))
83 {
84 }
85
86 void
87 start(std::function<void()> on_done) override
88 {
89 std::move(op_)(std::move(on_done));
90 }
91
92 void
93 get_result() override
94 {
95 if (exception_)
96 std::rethrow_exception(exception_);
97 }
98 };
99
100 } // detail
101
102 //-----------------------------------------------------------------------------
103
104 /** An awaitable wrapper for callback-based asynchronous operations.
105
106 This class template provides a bridge between traditional
107 callback-based asynchronous APIs and C++20 coroutines. It
108 wraps a deferred operation and makes it awaitable, allowing
109 seamless integration with coroutine-based code.
110
111 @par Thread Safety
112 Distinct objects may be accessed concurrently. Shared objects
113 require external synchronization.
114
115 @par Example
116 @code
117 // Wrap a callback-based timer
118 async_op<void> async_sleep(std::chrono::milliseconds ms)
119 {
120 return make_async_op<void>(
121 [ms](auto&& handler) {
122 // Start timer, call handler when done
123 start_timer(ms, std::move(handler));
124 });
125 }
126
127 task<void> example()
128 {
129 co_await async_sleep(std::chrono::milliseconds(100));
130 }
131 @endcode
132
133 @tparam T The type of value produced by the asynchronous operation.
134
135 @see make_async_op, task
136 */
137 template<class T>
138 class async_op
139 {
140 std::unique_ptr<detail::async_op_impl_base<T>> impl_;
141
142 // Workaround: clang fails to match friend function template declarations
143 #if defined(__clang__) && (__clang_major__ == 16 || \
144 (defined(__apple_build_version__) && __apple_build_version__ >= 15000000))
145 public:
146 #endif
147 explicit
148 24 async_op(std::unique_ptr<detail::async_op_impl_base<T>> p)
149 24 : impl_(std::move(p))
150 {
151 24 }
152 #if defined(__clang__) && (__clang_major__ == 16 || \
153 (defined(__apple_build_version__) && __apple_build_version__ >= 15000000))
154 private:
155 #endif
156
157 template<class U, class DeferredOp>
158 requires (!std::is_void_v<U>)
159 friend async_op<U>
160 make_async_op(DeferredOp&& op);
161
162 public:
163 /** Return whether the result is ready.
164
165 @return Always returns false; the operation must be started.
166 */
167 bool
168 24 await_ready() const noexcept
169 {
170 24 return false;
171 }
172
173 /** Suspend the caller and start the operation.
174
175 Initiates the asynchronous operation and arranges for
176 the caller to be resumed when it completes.
177
178 @param h The coroutine handle of the awaiting coroutine.
179 */
180 void
181 await_suspend(std::coroutine_handle<> h)
182 {
183 impl_->start([h]{ h.resume(); });
184 }
185
186 /** Suspend the caller with scheduler affinity (IoAwaitable protocol).
187
188 Initiates the asynchronous operation and arranges for
189 the caller to be resumed through the executor when
190 it completes, maintaining scheduler affinity.
191
192 @param h The coroutine handle of the awaiting coroutine.
193 @param ex The executor to resume through.
194 @param token The stop token for cancellation (currently unused).
195 */
196 template<typename Ex>
197 void
198 24 await_suspend(std::coroutine_handle<> h, Ex const& ex, std::stop_token = {})
199 {
200
3/3
✓ Branch 3 taken 24 times.
✓ Branch 8 taken 24 times.
✓ Branch 11 taken 24 times.
48 impl_->start([h, &ex]{ ex.dispatch(h).resume(); });
201 24 }
202
203 /** Return the result after completion.
204
205 @return The value produced by the asynchronous operation.
206
207 @throws Any exception that occurred during the operation.
208 */
209 [[nodiscard]]
210 T
211 24 await_resume()
212 {
213 24 return impl_->get_result();
214 }
215 };
216
217 //-----------------------------------------------------------------------------
218
219 /** An awaitable wrapper for callback-based operations with no result.
220
221 This specialization of async_op is used for asynchronous
222 operations that signal completion but do not produce a value,
223 such as timers, write operations, or connection establishment.
224
225 @par Thread Safety
226 Distinct objects may be accessed concurrently. Shared objects
227 require external synchronization.
228
229 @par Example
230 @code
231 // Wrap a callback-based timer
232 async_op<void> async_sleep(std::chrono::milliseconds ms)
233 {
234 return make_async_op<void>(
235 [ms](auto handler) {
236 start_timer(ms, [h = std::move(handler)]{ h(); });
237 });
238 }
239
240 task<void> example()
241 {
242 co_await async_sleep(std::chrono::milliseconds(100));
243 }
244 @endcode
245
246 @see async_op, make_async_op
247 */
248 template<>
249 class async_op<void>
250 {
251 std::unique_ptr<detail::async_op_void_impl_base> impl_;
252
253 // Workaround: clang fails to match friend function template declarations
254 #if defined(__clang__) && (__clang_major__ == 16 || \
255 (defined(__apple_build_version__) && __apple_build_version__ >= 15000000))
256 public:
257 #endif
258 explicit
259 async_op(std::unique_ptr<detail::async_op_void_impl_base> p)
260 : impl_(std::move(p))
261 {
262 }
263 #if defined(__clang__) && (__clang_major__ == 16 || \
264 (defined(__apple_build_version__) && __apple_build_version__ >= 15000000))
265 private:
266 #endif
267
268 template<class U, class DeferredOp>
269 requires std::is_void_v<U>
270 friend async_op<void>
271 make_async_op(DeferredOp&& op);
272
273 public:
274 /** Return whether the result is ready.
275
276 @return Always returns false; the operation must be started.
277 */
278 bool
279 await_ready() const noexcept
280 {
281 return false;
282 }
283
284 /** Suspend the caller and start the operation.
285
286 Initiates the asynchronous operation and arranges for
287 the caller to be resumed when it completes.
288
289 @param h The coroutine handle of the awaiting coroutine.
290 */
291 void
292 await_suspend(std::coroutine_handle<> h)
293 {
294 impl_->start([h]{ h.resume(); });
295 }
296
297 /** Suspend the caller with scheduler affinity (IoAwaitable protocol).
298
299 Initiates the asynchronous operation and arranges for
300 the caller to be resumed through the executor when
301 it completes, maintaining scheduler affinity.
302
303 @param h The coroutine handle of the awaiting coroutine.
304 @param ex The executor to resume through.
305 @param token The stop token for cancellation (currently unused).
306 */
307 template<typename Ex>
308 void
309 await_suspend(std::coroutine_handle<> h, Ex const& ex, std::stop_token = {})
310 {
311 impl_->start([h, &ex]{ ex.dispatch(h).resume(); });
312 }
313
314 /** Complete the await and check for exceptions.
315
316 @throws Any exception that occurred during the operation.
317 */
318 void
319 await_resume()
320 {
321 impl_->get_result();
322 }
323 };
324
325 //-----------------------------------------------------------------------------
326
327 /** Return an async_op from a deferred operation.
328
329 This factory function creates an awaitable async_op that
330 wraps a callback-based asynchronous operation.
331
332 @par Example
333 @code
334 async_op<std::string> async_read()
335 {
336 return make_async_op<std::string>(
337 [](auto handler) {
338 // Simulate async read
339 handler("Hello, World!");
340 });
341 }
342 @endcode
343
344 @tparam T The result type of the asynchronous operation.
345
346 @param op A callable that accepts a completion handler. When invoked,
347 it should initiate the asynchronous operation and call the
348 handler with the result when complete.
349
350 @return An async_op that can be awaited in a coroutine.
351
352 @see async_op
353 */
354 template<class T, class DeferredOp>
355 requires (!std::is_void_v<T>)
356 [[nodiscard]]
357 async_op<T>
358 24 make_async_op(DeferredOp&& op)
359 {
360 using impl_type = detail::async_op_impl<T, std::decay_t<DeferredOp>>;
361 return async_op<T>(
362 24 std::make_unique<impl_type>(std::forward<DeferredOp>(op)));
363 }
364
365 /** Return an async_op<void> from a deferred operation.
366
367 This overload is used for operations that signal completion
368 without producing a value.
369
370 @par Example
371 @code
372 async_op<void> async_wait(int milliseconds)
373 {
374 return make_async_op<void>(
375 [milliseconds](auto on_done) {
376 // Start timer, call on_done() when elapsed
377 start_timer(milliseconds, std::move(on_done));
378 });
379 }
380 @endcode
381
382 @param op A callable that accepts a completion handler taking no
383 arguments. When invoked, it should initiate the operation
384 and call the handler when complete.
385
386 @return An async_op<void> that can be awaited in a coroutine.
387
388 @see async_op
389 */
390 template<class T, class DeferredOp>
391 requires std::is_void_v<T>
392 [[nodiscard]]
393 async_op<void>
394 make_async_op(DeferredOp&& op)
395 {
396 using impl_type = detail::async_op_void_impl<std::decay_t<DeferredOp>>;
397 return async_op<void>(
398 std::make_unique<impl_type>(std::forward<DeferredOp>(op)));
399 }
400
401 } // capy
402 } // boost
403
404 #endif
405