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_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 24 : 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 24 : async_op_impl(DeferredOp&& op)
50 24 : : op_(std::forward<DeferredOp>(op))
51 : {
52 24 : }
53 :
54 : void
55 24 : start(std::function<void()> on_done) override
56 : {
57 48 : std::move(op_)(
58 72 : [this, on_done = std::move(on_done)](auto&&... args) mutable
59 : {
60 24 : result_.template emplace<1>(T{std::forward<decltype(args)>(args)...});
61 24 : on_done();
62 : });
63 24 : }
64 :
65 : T
66 24 : get_result() override
67 : {
68 24 : if (result_.index() == 0 && std::get<0>(result_))
69 0 : std::rethrow_exception(std::get<0>(result_));
70 24 : 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 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
|