LCOV - code coverage report
Current view: top level - boost/capy/ex - run_async.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 92.1 % 89 82
Test Date: 2026-01-19 00:56:52 Functions: 83.2 % 976 812

            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_RUN_ASYNC_HPP
      11              : #define BOOST_CAPY_RUN_ASYNC_HPP
      12              : 
      13              : #include <boost/capy/detail/config.hpp>
      14              : #include <boost/capy/concept/executor.hpp>
      15              : #include <boost/capy/concept/frame_allocator.hpp>
      16              : #include <boost/capy/task.hpp>
      17              : 
      18              : #include <concepts>
      19              : #include <coroutine>
      20              : #include <exception>
      21              : #include <stop_token>
      22              : #include <type_traits>
      23              : #include <utility>
      24              : 
      25              : namespace boost {
      26              : namespace capy {
      27              : 
      28              : //----------------------------------------------------------
      29              : //
      30              : // Handler Types
      31              : //
      32              : //----------------------------------------------------------
      33              : 
      34              : /** Default handler for run_async that discards results and rethrows exceptions.
      35              : 
      36              :     This handler type is used when no user-provided handlers are specified.
      37              :     On successful completion it discards the result value. On exception it
      38              :     rethrows the exception from the exception_ptr.
      39              : 
      40              :     @par Thread Safety
      41              :     All member functions are thread-safe.
      42              : 
      43              :     @see run_async
      44              :     @see handler_pair
      45              : */
      46              : struct default_handler
      47              : {
      48              :     /// Discard a non-void result value.
      49              :     template<class T>
      50            1 :     void operator()(T&&) const noexcept
      51              :     {
      52            1 :     }
      53              : 
      54              :     /// Handle void result (no-op).
      55            1 :     void operator()() const noexcept
      56              :     {
      57            1 :     }
      58              : 
      59              :     /// Rethrow the captured exception.
      60            0 :     void operator()(std::exception_ptr ep) const
      61              :     {
      62            0 :         if(ep)
      63            0 :             std::rethrow_exception(ep);
      64            0 :     }
      65              : };
      66              : 
      67              : /** Combines two handlers into one: h1 for success, h2 for exception.
      68              : 
      69              :     This class template wraps a success handler and an error handler,
      70              :     providing a unified callable interface for the trampoline coroutine.
      71              : 
      72              :     @tparam H1 The success handler type. Must be invocable with `T&&` for
      73              :                non-void tasks or with no arguments for void tasks.
      74              :     @tparam H2 The error handler type. Must be invocable with `std::exception_ptr`.
      75              : 
      76              :     @par Thread Safety
      77              :     Thread safety depends on the contained handlers.
      78              : 
      79              :     @see run_async
      80              :     @see default_handler
      81              : */
      82              : template<class H1, class H2>
      83              : struct handler_pair
      84              : {
      85              :     H1 h1_;
      86              :     H2 h2_;
      87              : 
      88              :     /// Invoke success handler with non-void result.
      89              :     template<class T>
      90           24 :     void operator()(T&& v)
      91              :     {
      92           24 :         h1_(std::forward<T>(v));
      93           24 :     }
      94              : 
      95              :     /// Invoke success handler for void result.
      96            2 :     void operator()()
      97              :     {
      98            2 :         h1_();
      99            2 :     }
     100              : 
     101              :     /// Invoke error handler with exception.
     102           12 :     void operator()(std::exception_ptr ep)
     103              :     {
     104           12 :         h2_(ep);
     105           12 :     }
     106              : };
     107              : 
     108              : /** Specialization for single handler that may handle both success and error.
     109              : 
     110              :     When only one handler is provided to `run_async`, this specialization
     111              :     checks at compile time whether the handler can accept `std::exception_ptr`.
     112              :     If so, it routes exceptions to the handler. Otherwise, exceptions are
     113              :     rethrown (the default behavior).
     114              : 
     115              :     @tparam H1 The handler type. If invocable with `std::exception_ptr`,
     116              :                it handles both success and error cases.
     117              : 
     118              :     @par Thread Safety
     119              :     Thread safety depends on the contained handler.
     120              : 
     121              :     @see run_async
     122              :     @see default_handler
     123              : */
     124              : template<class H1>
     125              : struct handler_pair<H1, default_handler>
     126              : {
     127              :     H1 h1_;
     128              : 
     129              :     /// Invoke handler with non-void result.
     130              :     template<class T>
     131           16 :     void operator()(T&& v)
     132              :     {
     133           16 :         h1_(std::forward<T>(v));
     134           16 :     }
     135              : 
     136              :     /// Invoke handler for void result.
     137            1 :     void operator()()
     138              :     {
     139            1 :         h1_();
     140            1 :     }
     141              : 
     142              :     /// Route exception to h1 if it accepts exception_ptr, otherwise rethrow.
     143            1 :     void operator()(std::exception_ptr ep)
     144              :     {
     145              :         if constexpr(std::invocable<H1, std::exception_ptr>)
     146            1 :             h1_(ep);
     147              :         else
     148            0 :             std::rethrow_exception(ep);
     149            1 :     }
     150              : };
     151              : 
     152              : namespace detail {
     153              : 
     154              : //----------------------------------------------------------
     155              : //
     156              : // Trampoline Coroutine
     157              : //
     158              : //----------------------------------------------------------
     159              : 
     160              : /// Awaiter to access the promise from within the coroutine.
     161              : template<class Promise>
     162              : struct get_promise_awaiter
     163              : {
     164              :     Promise* p_ = nullptr;
     165              : 
     166           58 :     bool await_ready() const noexcept { return false; }
     167              : 
     168           58 :     bool await_suspend(std::coroutine_handle<Promise> h) noexcept
     169              :     {
     170           58 :         p_ = &h.promise();
     171           58 :         return false;
     172              :     }
     173              : 
     174           58 :     Promise& await_resume() const noexcept
     175              :     {
     176           58 :         return *p_;
     177              :     }
     178              : };
     179              : 
     180              : /** Internal trampoline coroutine for run_async.
     181              : 
     182              :     The trampoline is allocated BEFORE the task (via C++17 postfix evaluation
     183              :     order) and serves as the task's continuation. When the task final_suspends,
     184              :     control returns to the trampoline which then invokes the appropriate handler.
     185              : 
     186              :     @tparam Ex The executor type.
     187              :     @tparam Handlers The handler type (default_handler or handler_pair).
     188              : */
     189              : template<class Ex, class Handlers>
     190              : struct trampoline
     191              : {
     192              :     using invoke_fn = void(*)(void*, Handlers&);
     193              : 
     194              :     struct promise_type
     195              :     {
     196              :         Ex ex_;
     197              :         Handlers handlers_;
     198              :         invoke_fn invoke_ = nullptr;
     199              :         void* task_promise_ = nullptr;
     200              :         std::coroutine_handle<> task_h_;
     201              : 
     202              :         // Constructor receives coroutine parameters by lvalue reference
     203           58 :         promise_type(Ex ex, Handlers h)
     204           58 :             : ex_(std::move(ex))
     205           58 :             , handlers_(std::move(h))
     206              :         {
     207           58 :         }
     208              : 
     209           58 :         trampoline get_return_object() noexcept
     210              :         {
     211              :             return trampoline{
     212           58 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
     213              :         }
     214              : 
     215           58 :         std::suspend_always initial_suspend() noexcept
     216              :         {
     217           58 :             return {};
     218              :         }
     219              : 
     220              :         // Self-destruct after invoking handlers
     221           58 :         std::suspend_never final_suspend() noexcept
     222              :         {
     223           58 :             return {};
     224              :         }
     225              : 
     226           58 :         void return_void() noexcept
     227              :         {
     228           58 :         }
     229              : 
     230            0 :         void unhandled_exception() noexcept
     231              :         {
     232              :             // Handler threw - this is undefined behavior if no error handler provided
     233            0 :         }
     234              :     };
     235              : 
     236              :     std::coroutine_handle<promise_type> h_;
     237              : 
     238              :     /// Type-erased invoke function instantiated per task<T>.
     239              :     template<class T>
     240           58 :     static void invoke_impl(void* p, Handlers& h)
     241              :     {
     242           58 :         auto& promise = *static_cast<typename task<T>::promise_type*>(p);
     243           58 :         if(promise.ep_)
     244           13 :             h(promise.ep_);
     245              :         else if constexpr(std::is_void_v<T>)
     246            4 :             h();
     247              :         else
     248           41 :             h(std::move(*promise.result_));
     249           58 :     }
     250              : };
     251              : 
     252              : /// Coroutine body for trampoline - invokes handlers then destroys task.
     253              : template<class Ex, class Handlers>
     254              : trampoline<Ex, Handlers>
     255           58 : make_trampoline(Ex ex, Handlers h)
     256              : {
     257              :     // Parameters are passed to promise_type constructor by coroutine machinery
     258              :     (void)ex;
     259              :     (void)h;
     260              :     auto& p = co_await get_promise_awaiter<typename trampoline<Ex, Handlers>::promise_type>{};
     261              :     
     262              :     // Invoke the type-erased handler
     263              :     p.invoke_(p.task_promise_, p.handlers_);
     264              :     
     265              :     // Destroy task (LIFO: task destroyed first, trampoline destroyed after)
     266              :     p.task_h_.destroy();
     267          116 : }
     268              : 
     269              : } // namespace detail
     270              : 
     271              : //----------------------------------------------------------
     272              : //
     273              : // run_async_wrapper
     274              : //
     275              : //----------------------------------------------------------
     276              : 
     277              : /** Wrapper returned by run_async that accepts a task for execution.
     278              : 
     279              :     This wrapper holds the trampoline coroutine, executor, stop token,
     280              :     and handlers. The trampoline is allocated when the wrapper is constructed
     281              :     (before the task due to C++17 postfix evaluation order).
     282              : 
     283              :     The rvalue ref-qualifier on `operator()` ensures the wrapper can only
     284              :     be used as a temporary, preventing misuse that would violate LIFO ordering.
     285              : 
     286              :     @tparam Ex The executor type satisfying the `Executor` concept.
     287              :     @tparam Handlers The handler type (default_handler or handler_pair).
     288              : 
     289              :     @par Thread Safety
     290              :     The wrapper itself should only be used from one thread. The handlers
     291              :     may be invoked from any thread where the executor schedules work.
     292              : 
     293              :     @par Example
     294              :     @code
     295              :     // Correct usage - wrapper is temporary
     296              :     run_async(ex)(my_task());
     297              : 
     298              :     // Compile error - cannot call operator() on lvalue
     299              :     auto w = run_async(ex);
     300              :     w(my_task());  // Error: operator() requires rvalue
     301              :     @endcode
     302              : 
     303              :     @see run_async
     304              : */
     305              : template<Executor Ex, class Handlers>
     306              : class [[nodiscard]] run_async_wrapper
     307              : {
     308              :     detail::trampoline<Ex, Handlers> tr_;
     309              :     std::stop_token st_;
     310              : 
     311              : public:
     312              :     /// Construct wrapper with executor, stop token, and handlers.
     313           58 :     run_async_wrapper(
     314              :         Ex ex,
     315              :         std::stop_token st,
     316              :         Handlers h)
     317           58 :         : tr_(detail::make_trampoline<Ex, Handlers>(
     318           58 :             std::move(ex), std::move(h)))
     319           58 :         , st_(std::move(st))
     320              :     {
     321           58 :     }
     322              : 
     323              :     // Non-copyable, non-movable (must be used immediately)
     324              :     run_async_wrapper(run_async_wrapper const&) = delete;
     325              :     run_async_wrapper(run_async_wrapper&&) = delete;
     326              :     run_async_wrapper& operator=(run_async_wrapper const&) = delete;
     327              :     run_async_wrapper& operator=(run_async_wrapper&&) = delete;
     328              : 
     329              :     /** Launch the task for execution.
     330              : 
     331              :         This operator accepts a task and launches it on the executor.
     332              :         The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
     333              :         correct LIFO destruction order.
     334              : 
     335              :         @tparam T The task's return type.
     336              : 
     337              :         @param t The task to execute. Ownership is transferred to the
     338              :                  trampoline which will destroy it after completion.
     339              :     */
     340              :     template<class T>
     341           58 :     void operator()(task<T> t) &&
     342              :     {
     343           58 :         auto task_h = t.release();
     344           58 :         auto& p = tr_.h_.promise();
     345              : 
     346              :         // Inject T-specific invoke function
     347           58 :         p.invoke_ = detail::trampoline<Ex, Handlers>::template invoke_impl<T>;
     348           58 :         p.task_promise_ = &task_h.promise();
     349           58 :         p.task_h_ = task_h;
     350              : 
     351              :         // Setup task's continuation to return to trampoline
     352              :         // Executor lives in trampoline's promise, so reference is valid for task's lifetime
     353           58 :         task_h.promise().continuation_ = tr_.h_;
     354           58 :         task_h.promise().caller_ex_ = p.ex_;
     355           58 :         task_h.promise().ex_ = p.ex_;
     356           58 :         task_h.promise().set_stop_token(st_);
     357              : 
     358              :         // Resume task through executor
     359              :         // The executor returns a handle for symmetric transfer;
     360              :         // from non-coroutine code we must explicitly resume it
     361           58 :         p.ex_.dispatch(task_h).resume();
     362           58 :     }
     363              : };
     364              : 
     365              : //----------------------------------------------------------
     366              : //
     367              : // run_async Overloads
     368              : //
     369              : //----------------------------------------------------------
     370              : 
     371              : // Executor only
     372              : 
     373              : /** Asynchronously launch a lazy task on the given executor.
     374              : 
     375              :     Use this to start execution of a `task<T>` that was created lazily.
     376              :     The returned wrapper must be immediately invoked with the task;
     377              :     storing the wrapper and calling it later violates LIFO ordering.
     378              : 
     379              :     With no handlers, the result is discarded and exceptions are rethrown.
     380              : 
     381              :     @par Thread Safety
     382              :     The wrapper and handlers may be called from any thread where the
     383              :     executor schedules work.
     384              : 
     385              :     @par Example
     386              :     @code
     387              :     run_async(ioc.get_executor())(my_task());
     388              :     @endcode
     389              : 
     390              :     @param ex The executor to execute the task on.
     391              : 
     392              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     393              : 
     394              :     @see task
     395              :     @see executor
     396              : */
     397              : template<Executor Ex>
     398              : [[nodiscard]] auto
     399            2 : run_async(Ex ex)
     400              : {
     401              :     return run_async_wrapper<Ex, default_handler>(
     402            2 :         std::move(ex),
     403            4 :         std::stop_token{},
     404            4 :         default_handler{});
     405              : }
     406              : 
     407              : /** Asynchronously launch a lazy task with a result handler.
     408              : 
     409              :     The handler `h1` is called with the task's result on success. If `h1`
     410              :     is also invocable with `std::exception_ptr`, it handles exceptions too.
     411              :     Otherwise, exceptions are rethrown.
     412              : 
     413              :     @par Thread Safety
     414              :     The handler may be called from any thread where the executor
     415              :     schedules work.
     416              : 
     417              :     @par Example
     418              :     @code
     419              :     // Handler for result only (exceptions rethrown)
     420              :     run_async(ex, [](int result) {
     421              :         std::cout << "Got: " << result << "\n";
     422              :     })(compute_value());
     423              : 
     424              :     // Overloaded handler for both result and exception
     425              :     run_async(ex, overloaded{
     426              :         [](int result) { std::cout << "Got: " << result << "\n"; },
     427              :         [](std::exception_ptr) { std::cout << "Failed\n"; }
     428              :     })(compute_value());
     429              :     @endcode
     430              : 
     431              :     @param ex The executor to execute the task on.
     432              :     @param h1 The handler to invoke with the result (and optionally exception).
     433              : 
     434              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     435              : 
     436              :     @see task
     437              :     @see executor
     438              : */
     439              : template<Executor Ex, class H1>
     440              : [[nodiscard]] auto
     441           15 : run_async(Ex ex, H1 h1)
     442              : {
     443              :     return run_async_wrapper<Ex, handler_pair<H1, default_handler>>(
     444           15 :         std::move(ex),
     445           15 :         std::stop_token{},
     446           45 :         handler_pair<H1, default_handler>{std::move(h1)});
     447              : }
     448              : 
     449              : /** Asynchronously launch a lazy task with separate result and error handlers.
     450              : 
     451              :     The handler `h1` is called with the task's result on success.
     452              :     The handler `h2` is called with the exception_ptr on failure.
     453              : 
     454              :     @par Thread Safety
     455              :     The handlers may be called from any thread where the executor
     456              :     schedules work.
     457              : 
     458              :     @par Example
     459              :     @code
     460              :     run_async(ex,
     461              :         [](int result) { std::cout << "Got: " << result << "\n"; },
     462              :         [](std::exception_ptr ep) {
     463              :             try { std::rethrow_exception(ep); }
     464              :             catch (std::exception const& e) {
     465              :                 std::cout << "Error: " << e.what() << "\n";
     466              :             }
     467              :         }
     468              :     )(compute_value());
     469              :     @endcode
     470              : 
     471              :     @param ex The executor to execute the task on.
     472              :     @param h1 The handler to invoke with the result on success.
     473              :     @param h2 The handler to invoke with the exception on failure.
     474              : 
     475              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     476              : 
     477              :     @see task
     478              :     @see executor
     479              : */
     480              : template<Executor Ex, class H1, class H2>
     481              : [[nodiscard]] auto
     482           38 : run_async(Ex ex, H1 h1, H2 h2)
     483              : {
     484              :     return run_async_wrapper<Ex, handler_pair<H1, H2>>(
     485           38 :         std::move(ex),
     486           38 :         std::stop_token{},
     487          114 :         handler_pair<H1, H2>{std::move(h1), std::move(h2)});
     488              : }
     489              : 
     490              : // Ex + stop_token
     491              : 
     492              : /** Asynchronously launch a lazy task with stop token support.
     493              : 
     494              :     The stop token is propagated to the task, enabling cooperative
     495              :     cancellation. With no handlers, the result is discarded and
     496              :     exceptions are rethrown.
     497              : 
     498              :     @par Thread Safety
     499              :     The wrapper may be called from any thread where the executor
     500              :     schedules work.
     501              : 
     502              :     @par Example
     503              :     @code
     504              :     std::stop_source source;
     505              :     run_async(ex, source.get_token())(cancellable_task());
     506              :     // Later: source.request_stop();
     507              :     @endcode
     508              : 
     509              :     @param ex The executor to execute the task on.
     510              :     @param st The stop token for cooperative cancellation.
     511              : 
     512              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     513              : 
     514              :     @see task
     515              :     @see executor
     516              : */
     517              : template<Executor Ex>
     518              : [[nodiscard]] auto
     519              : run_async(Ex ex, std::stop_token st)
     520              : {
     521              :     return run_async_wrapper<Ex, default_handler>(
     522              :         std::move(ex),
     523              :         std::move(st),
     524              :         default_handler{});
     525              : }
     526              : 
     527              : /** Asynchronously launch a lazy task with stop token and result handler.
     528              : 
     529              :     The stop token is propagated to the task for cooperative cancellation.
     530              :     The handler `h1` is called with the result on success, and optionally
     531              :     with exception_ptr if it accepts that type.
     532              : 
     533              :     @param ex The executor to execute the task on.
     534              :     @param st The stop token for cooperative cancellation.
     535              :     @param h1 The handler to invoke with the result (and optionally exception).
     536              : 
     537              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     538              : 
     539              :     @see task
     540              :     @see executor
     541              : */
     542              : template<Executor Ex, class H1>
     543              : [[nodiscard]] auto
     544            3 : run_async(Ex ex, std::stop_token st, H1 h1)
     545              : {
     546              :     return run_async_wrapper<Ex, handler_pair<H1, default_handler>>(
     547            3 :         std::move(ex),
     548            3 :         std::move(st),
     549            6 :         handler_pair<H1, default_handler>{std::move(h1)});
     550              : }
     551              : 
     552              : /** Asynchronously launch a lazy task with stop token and separate handlers.
     553              : 
     554              :     The stop token is propagated to the task for cooperative cancellation.
     555              :     The handler `h1` is called on success, `h2` on failure.
     556              : 
     557              :     @param ex The executor to execute the task on.
     558              :     @param st The stop token for cooperative cancellation.
     559              :     @param h1 The handler to invoke with the result on success.
     560              :     @param h2 The handler to invoke with the exception on failure.
     561              : 
     562              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     563              : 
     564              :     @see task
     565              :     @see executor
     566              : */
     567              : template<Executor Ex, class H1, class H2>
     568              : [[nodiscard]] auto
     569              : run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
     570              : {
     571              :     return run_async_wrapper<Ex, handler_pair<H1, H2>>(
     572              :         std::move(ex),
     573              :         std::move(st),
     574              :         handler_pair<H1, H2>{std::move(h1), std::move(h2)});
     575              : }
     576              : 
     577              : // Executor + stop_token + allocator
     578              : 
     579              : /** Asynchronously launch a lazy task with stop token and allocator.
     580              : 
     581              :     The stop token is propagated to the task for cooperative cancellation.
     582              :     The allocator parameter is reserved for future use and currently ignored.
     583              : 
     584              :     @param ex The executor to execute the task on.
     585              :     @param st The stop token for cooperative cancellation.
     586              :     @param alloc The frame allocator (currently ignored).
     587              : 
     588              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     589              : 
     590              :     @see task
     591              :     @see executor
     592              :     @see frame_allocator
     593              : */
     594              : template<Executor Ex, FrameAllocator FA>
     595              : [[nodiscard]] auto
     596              : run_async(Ex ex, std::stop_token st, FA alloc)
     597              : {
     598              :     (void)alloc; // Currently ignored
     599              :     return run_async_wrapper<Ex, default_handler>(
     600              :         std::move(ex),
     601              :         std::move(st),
     602              :         default_handler{});
     603              : }
     604              : 
     605              : /** Asynchronously launch a lazy task with stop token, allocator, and handler.
     606              : 
     607              :     The stop token is propagated to the task for cooperative cancellation.
     608              :     The allocator parameter is reserved for future use and currently ignored.
     609              : 
     610              :     @param ex The executor to execute the task on.
     611              :     @param st The stop token for cooperative cancellation.
     612              :     @param alloc The frame allocator (currently ignored).
     613              :     @param h1 The handler to invoke with the result (and optionally exception).
     614              : 
     615              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     616              : 
     617              :     @see task
     618              :     @see executor
     619              :     @see frame_allocator
     620              : */
     621              : template<Executor Ex, FrameAllocator FA, class H1>
     622              : [[nodiscard]] auto
     623              : run_async(Ex ex, std::stop_token st, FA alloc, H1 h1)
     624              : {
     625              :     (void)alloc; // Currently ignored
     626              :     return run_async_wrapper<Ex, handler_pair<H1, default_handler>>(
     627              :         std::move(ex),
     628              :         std::move(st),
     629              :         handler_pair<H1, default_handler>{std::move(h1)});
     630              : }
     631              : 
     632              : /** Asynchronously launch a lazy task with stop token, allocator, and handlers.
     633              : 
     634              :     The stop token is propagated to the task for cooperative cancellation.
     635              :     The allocator parameter is reserved for future use and currently ignored.
     636              : 
     637              :     @param ex The executor to execute the task on.
     638              :     @param st The stop token for cooperative cancellation.
     639              :     @param alloc The frame allocator (currently ignored).
     640              :     @param h1 The handler to invoke with the result on success.
     641              :     @param h2 The handler to invoke with the exception on failure.
     642              : 
     643              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     644              : 
     645              :     @see task
     646              :     @see executor
     647              :     @see frame_allocator
     648              : */
     649              : template<Executor Ex, FrameAllocator FA, class H1, class H2>
     650              : [[nodiscard]] auto
     651              : run_async(Ex ex, std::stop_token st, FA alloc, H1 h1, H2 h2)
     652              : {
     653              :     (void)alloc; // Currently ignored
     654              :     return run_async_wrapper<Ex, handler_pair<H1, H2>>(
     655              :         std::move(ex),
     656              :         std::move(st),
     657              :         handler_pair<H1, H2>{std::move(h1), std::move(h2)});
     658              : }
     659              : 
     660              : } // namespace capy
     661              : } // namespace boost
     662              : 
     663              : #endif
        

Generated by: LCOV version 2.3