LCOV - code coverage report
Current view: top level - boost/capy/ex - async_op.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 96.2 % 26 25
Test Date: 2026-01-15 18:27:21 Functions: 72.7 % 22 16

            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
        

Generated by: LCOV version 2.3