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 <variant>
21 :
22 : namespace boost {
23 : namespace capy {
24 : namespace detail {
25 :
26 : template<class T>
27 : struct async_op_impl_base
28 : {
29 23 : 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 23 : async_op_impl(DeferredOp&& op)
49 23 : : op_(std::forward<DeferredOp>(op))
50 : {
51 23 : }
52 :
53 : void
54 23 : start(std::function<void()> on_done) override
55 : {
56 46 : std::move(op_)(
57 69 : [this, on_done = std::move(on_done)](auto&&... args) mutable
58 : {
59 23 : result_.template emplace<1>(T{std::forward<decltype(args)>(args)...});
60 23 : on_done();
61 : });
62 23 : }
63 :
64 : T
65 23 : get_result() override
66 : {
67 23 : if (result_.index() == 0 && std::get<0>(result_))
68 0 : std::rethrow_exception(std::get<0>(result_));
69 23 : 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 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
|