GCC Code Coverage Report


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