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-18 18:26:31 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 <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
        

Generated by: LCOV version 2.3