LCOV - code coverage report
Current view: top level - boost/capy/ex - run_async.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 95.6 % 90 86
Test Date: 2026-01-23 22:12:31 Functions: 76.5 % 1579 1208

            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/io_awaitable.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            2 :     void operator()(T&&) const noexcept
      51              :     {
      52            2 :     }
      53              : 
      54              :     /// Handle void result (no-op).
      55            8 :     void operator()() const noexcept
      56              :     {
      57            8 :     }
      58              : 
      59              :     /// Rethrow the captured exception.
      60            5 :     void operator()(std::exception_ptr ep) const
      61              :     {
      62            5 :         if(ep)
      63            5 :             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           14 :     void operator()(T&& v)
      91              :     {
      92           14 :         h1_(std::forward<T>(v));
      93           14 :     }
      94              : 
      95              :     /// Invoke success handler for void result.
      96           17 :     void operator()()
      97              :     {
      98           17 :         h1_();
      99           17 :     }
     100              : 
     101              :     /// Invoke error handler with exception.
     102            7 :     void operator()(std::exception_ptr ep)
     103              :     {
     104            7 :         h2_(ep);
     105            7 :     }
     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           59 :     void operator()(T&& v)
     132              :     {
     133           59 :         h1_(std::forward<T>(v));
     134           59 :     }
     135              : 
     136              :     /// Invoke handler for void result.
     137           19 :     void operator()()
     138              :     {
     139           19 :         h1_();
     140           19 :     }
     141              : 
     142              :     /// Route exception to h1 if it accepts exception_ptr, otherwise rethrow.
     143           15 :     void operator()(std::exception_ptr ep)
     144              :     {
     145              :         if constexpr(std::invocable<H1, std::exception_ptr>)
     146           15 :             h1_(ep);
     147              :         else
     148            0 :             std::rethrow_exception(ep);
     149           10 :     }
     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           96 :     bool await_ready() const noexcept { return false; }
     167              : 
     168           96 :     bool await_suspend(std::coroutine_handle<Promise> h) noexcept
     169              :     {
     170           96 :         p_ = &h.promise();
     171           96 :         return false;
     172              :     }
     173              : 
     174           96 :     Promise& await_resume() const noexcept
     175              :     {
     176           96 :         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           96 :         promise_type(Ex ex, Handlers h)
     204           96 :             : ex_(std::move(ex))
     205           96 :             , handlers_(std::move(h))
     206              :         {
     207           96 :         }
     208              : 
     209           96 :         trampoline get_return_object() noexcept
     210              :         {
     211              :             return trampoline{
     212           96 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
     213              :         }
     214              : 
     215           96 :         std::suspend_always initial_suspend() noexcept
     216              :         {
     217           96 :             return {};
     218              :         }
     219              : 
     220              :         // Self-destruct after invoking handlers
     221           96 :         std::suspend_never final_suspend() noexcept
     222              :         {
     223           96 :             return {};
     224              :         }
     225              : 
     226           96 :         void return_void() noexcept
     227              :         {
     228           96 :         }
     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 IoLaunchableTask.
     239              :     template<IoLaunchableTask Task>
     240           96 :     static void invoke_impl(void* p, Handlers& h)
     241              :     {
     242              :         using R = decltype(std::declval<Task&>().await_resume());
     243           96 :         auto& promise = *static_cast<typename Task::promise_type*>(p);
     244           96 :         if(promise.exception())
     245           14 :             h(promise.exception());
     246              :         else if constexpr(std::is_void_v<R>)
     247           29 :             h();
     248              :         else
     249           53 :             h(std::move(promise.result()));
     250           96 :     }
     251              : };
     252              : 
     253              : /// Coroutine body for trampoline - invokes handlers then destroys task.
     254              : template<class Ex, class Handlers>
     255              : trampoline<Ex, Handlers>
     256           96 : make_trampoline(Ex ex, Handlers h)
     257              : {
     258              :     // Parameters are passed to promise_type constructor by coroutine machinery
     259              :     (void)ex;
     260              :     (void)h;
     261              :     auto& p = co_await get_promise_awaiter<typename trampoline<Ex, Handlers>::promise_type>{};
     262              :     
     263              :     // Invoke the type-erased handler
     264              :     p.invoke_(p.task_promise_, p.handlers_);
     265              :     
     266              :     // Destroy task (LIFO: task destroyed first, trampoline destroyed after)
     267              :     p.task_h_.destroy();
     268          192 : }
     269              : 
     270              : } // namespace detail
     271              : 
     272              : //----------------------------------------------------------
     273              : //
     274              : // run_async_wrapper
     275              : //
     276              : //----------------------------------------------------------
     277              : 
     278              : /** Wrapper returned by run_async that accepts a task for execution.
     279              : 
     280              :     This wrapper holds the trampoline coroutine, executor, stop token,
     281              :     and handlers. The trampoline is allocated when the wrapper is constructed
     282              :     (before the task due to C++17 postfix evaluation order).
     283              : 
     284              :     The rvalue ref-qualifier on `operator()` ensures the wrapper can only
     285              :     be used as a temporary, preventing misuse that would violate LIFO ordering.
     286              : 
     287              :     @tparam Ex The executor type satisfying the `Executor` concept.
     288              :     @tparam Handlers The handler type (default_handler or handler_pair).
     289              : 
     290              :     @par Thread Safety
     291              :     The wrapper itself should only be used from one thread. The handlers
     292              :     may be invoked from any thread where the executor schedules work.
     293              : 
     294              :     @par Example
     295              :     @code
     296              :     // Correct usage - wrapper is temporary
     297              :     run_async(ex)(my_task());
     298              : 
     299              :     // Compile error - cannot call operator() on lvalue
     300              :     auto w = run_async(ex);
     301              :     w(my_task());  // Error: operator() requires rvalue
     302              :     @endcode
     303              : 
     304              :     @see run_async
     305              : */
     306              : template<Executor Ex, class Handlers>
     307              : class [[nodiscard]] run_async_wrapper
     308              : {
     309              :     detail::trampoline<Ex, Handlers> tr_;
     310              :     std::stop_token st_;
     311              : 
     312              : public:
     313              :     /// Construct wrapper with executor, stop token, and handlers.
     314           96 :     run_async_wrapper(
     315              :         Ex ex,
     316              :         std::stop_token st,
     317              :         Handlers h)
     318           96 :         : tr_(detail::make_trampoline<Ex, Handlers>(
     319           96 :             std::move(ex), std::move(h)))
     320           96 :         , st_(std::move(st))
     321              :     {
     322           96 :     }
     323              : 
     324              :     // Non-copyable, non-movable (must be used immediately)
     325              :     run_async_wrapper(run_async_wrapper const&) = delete;
     326              :     run_async_wrapper(run_async_wrapper&&) = delete;
     327              :     run_async_wrapper& operator=(run_async_wrapper const&) = delete;
     328              :     run_async_wrapper& operator=(run_async_wrapper&&) = delete;
     329              : 
     330              :     /** Launch the task for execution.
     331              : 
     332              :         This operator accepts a task and launches it on the executor.
     333              :         The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
     334              :         correct LIFO destruction order.
     335              : 
     336              :         @tparam Task The IoLaunchableTask type.
     337              : 
     338              :         @param t The task to execute. Ownership is transferred to the
     339              :                  trampoline which will destroy it after completion.
     340              :     */
     341              :     template<IoLaunchableTask Task>
     342           96 :     void operator()(Task t) &&
     343              :     {
     344           96 :         auto task_h = t.handle();
     345           96 :         auto& task_promise = task_h.promise();
     346           96 :         t.release();
     347              : 
     348           96 :         auto& p = tr_.h_.promise();
     349              : 
     350              :         // Inject Task-specific invoke function
     351           96 :         p.invoke_ = detail::trampoline<Ex, Handlers>::template invoke_impl<Task>;
     352           96 :         p.task_promise_ = &task_promise;
     353           96 :         p.task_h_ = task_h;
     354              : 
     355              :         // Setup task's continuation to return to trampoline
     356              :         // Executor lives in trampoline's promise, so reference is valid for task's lifetime
     357           96 :         task_promise.set_continuation(tr_.h_, p.ex_);
     358           96 :         task_promise.set_executor(p.ex_);
     359           96 :         task_promise.set_stop_token(st_);
     360              : 
     361              :         // Resume task through executor
     362              :         // The executor returns a handle for symmetric transfer;
     363              :         // from non-coroutine code we must explicitly resume it
     364           96 :         p.ex_.dispatch(task_h).resume();
     365           96 :     }
     366              : };
     367              : 
     368              : //----------------------------------------------------------
     369              : //
     370              : // run_async Overloads
     371              : //
     372              : //----------------------------------------------------------
     373              : 
     374              : // Executor only
     375              : 
     376              : /** Asynchronously launch a lazy task on the given executor.
     377              : 
     378              :     Use this to start execution of a `task<T>` that was created lazily.
     379              :     The returned wrapper must be immediately invoked with the task;
     380              :     storing the wrapper and calling it later violates LIFO ordering.
     381              : 
     382              :     With no handlers, the result is discarded and exceptions are rethrown.
     383              : 
     384              :     @par Thread Safety
     385              :     The wrapper and handlers may be called from any thread where the
     386              :     executor schedules work.
     387              : 
     388              :     @par Example
     389              :     @code
     390              :     run_async(ioc.get_executor())(my_task());
     391              :     @endcode
     392              : 
     393              :     @param ex The executor to execute the task on.
     394              : 
     395              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     396              : 
     397              :     @see task
     398              :     @see executor
     399              : */
     400              : template<Executor Ex>
     401              : [[nodiscard]] auto
     402            2 : run_async(Ex ex)
     403              : {
     404              :     return run_async_wrapper<Ex, default_handler>(
     405            2 :         std::move(ex),
     406            4 :         std::stop_token{},
     407            4 :         default_handler{});
     408              : }
     409              : 
     410              : /** Asynchronously launch a lazy task with a result handler.
     411              : 
     412              :     The handler `h1` is called with the task's result on success. If `h1`
     413              :     is also invocable with `std::exception_ptr`, it handles exceptions too.
     414              :     Otherwise, exceptions are rethrown.
     415              : 
     416              :     @par Thread Safety
     417              :     The handler may be called from any thread where the executor
     418              :     schedules work.
     419              : 
     420              :     @par Example
     421              :     @code
     422              :     // Handler for result only (exceptions rethrown)
     423              :     run_async(ex, [](int result) {
     424              :         std::cout << "Got: " << result << "\n";
     425              :     })(compute_value());
     426              : 
     427              :     // Overloaded handler for both result and exception
     428              :     run_async(ex, overloaded{
     429              :         [](int result) { std::cout << "Got: " << result << "\n"; },
     430              :         [](std::exception_ptr) { std::cout << "Failed\n"; }
     431              :     })(compute_value());
     432              :     @endcode
     433              : 
     434              :     @param ex The executor to execute the task on.
     435              :     @param h1 The handler to invoke with the result (and optionally exception).
     436              : 
     437              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     438              : 
     439              :     @see task
     440              :     @see executor
     441              : */
     442              : template<Executor Ex, class H1>
     443              : [[nodiscard]] auto
     444           19 : run_async(Ex ex, H1 h1)
     445              : {
     446              :     return run_async_wrapper<Ex, handler_pair<H1, default_handler>>(
     447           19 :         std::move(ex),
     448           19 :         std::stop_token{},
     449           57 :         handler_pair<H1, default_handler>{std::move(h1)});
     450              : }
     451              : 
     452              : /** Asynchronously launch a lazy task with separate result and error handlers.
     453              : 
     454              :     The handler `h1` is called with the task's result on success.
     455              :     The handler `h2` is called with the exception_ptr on failure.
     456              : 
     457              :     @par Thread Safety
     458              :     The handlers may be called from any thread where the executor
     459              :     schedules work.
     460              : 
     461              :     @par Example
     462              :     @code
     463              :     run_async(ex,
     464              :         [](int result) { std::cout << "Got: " << result << "\n"; },
     465              :         [](std::exception_ptr ep) {
     466              :             try { std::rethrow_exception(ep); }
     467              :             catch (std::exception const& e) {
     468              :                 std::cout << "Error: " << e.what() << "\n";
     469              :             }
     470              :         }
     471              :     )(compute_value());
     472              :     @endcode
     473              : 
     474              :     @param ex The executor to execute the task on.
     475              :     @param h1 The handler to invoke with the result on success.
     476              :     @param h2 The handler to invoke with the exception on failure.
     477              : 
     478              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     479              : 
     480              :     @see task
     481              :     @see executor
     482              : */
     483              : template<Executor Ex, class H1, class H2>
     484              : [[nodiscard]] auto
     485           35 : run_async(Ex ex, H1 h1, H2 h2)
     486              : {
     487              :     return run_async_wrapper<Ex, handler_pair<H1, H2>>(
     488           35 :         std::move(ex),
     489           35 :         std::stop_token{},
     490          105 :         handler_pair<H1, H2>{std::move(h1), std::move(h2)});
     491              : }
     492              : 
     493              : // Ex + stop_token
     494              : 
     495              : /** Asynchronously launch a lazy task with stop token support.
     496              : 
     497              :     The stop token is propagated to the task, enabling cooperative
     498              :     cancellation. With no handlers, the result is discarded and
     499              :     exceptions are rethrown.
     500              : 
     501              :     @par Thread Safety
     502              :     The wrapper may be called from any thread where the executor
     503              :     schedules work.
     504              : 
     505              :     @par Example
     506              :     @code
     507              :     std::stop_source source;
     508              :     run_async(ex, source.get_token())(cancellable_task());
     509              :     // Later: source.request_stop();
     510              :     @endcode
     511              : 
     512              :     @param ex The executor to execute the task on.
     513              :     @param st The stop token for cooperative cancellation.
     514              : 
     515              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     516              : 
     517              :     @see task
     518              :     @see executor
     519              : */
     520              : template<Executor Ex>
     521              : [[nodiscard]] auto
     522              : run_async(Ex ex, std::stop_token st)
     523              : {
     524              :     return run_async_wrapper<Ex, default_handler>(
     525              :         std::move(ex),
     526              :         std::move(st),
     527              :         default_handler{});
     528              : }
     529              : 
     530              : /** Asynchronously launch a lazy task with stop token and result handler.
     531              : 
     532              :     The stop token is propagated to the task for cooperative cancellation.
     533              :     The handler `h1` is called with the result on success, and optionally
     534              :     with exception_ptr if it accepts that type.
     535              : 
     536              :     @param ex The executor to execute the task on.
     537              :     @param st The stop token for cooperative cancellation.
     538              :     @param h1 The handler to invoke with the result (and optionally exception).
     539              : 
     540              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     541              : 
     542              :     @see task
     543              :     @see executor
     544              : */
     545              : template<Executor Ex, class H1>
     546              : [[nodiscard]] auto
     547           40 : run_async(Ex ex, std::stop_token st, H1 h1)
     548              : {
     549              :     return run_async_wrapper<Ex, handler_pair<H1, default_handler>>(
     550           40 :         std::move(ex),
     551           40 :         std::move(st),
     552           80 :         handler_pair<H1, default_handler>{std::move(h1)});
     553              : }
     554              : 
     555              : /** Asynchronously launch a lazy task with stop token and separate handlers.
     556              : 
     557              :     The stop token is propagated to the task for cooperative cancellation.
     558              :     The handler `h1` is called on success, `h2` on failure.
     559              : 
     560              :     @param ex The executor to execute the task on.
     561              :     @param st The stop token for cooperative cancellation.
     562              :     @param h1 The handler to invoke with the result on success.
     563              :     @param h2 The handler to invoke with the exception on failure.
     564              : 
     565              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     566              : 
     567              :     @see task
     568              :     @see executor
     569              : */
     570              : template<Executor Ex, class H1, class H2>
     571              : [[nodiscard]] auto
     572              : run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
     573              : {
     574              :     return run_async_wrapper<Ex, handler_pair<H1, H2>>(
     575              :         std::move(ex),
     576              :         std::move(st),
     577              :         handler_pair<H1, H2>{std::move(h1), std::move(h2)});
     578              : }
     579              : 
     580              : // Executor + stop_token + allocator
     581              : 
     582              : /** Asynchronously launch a lazy task with stop token and allocator.
     583              : 
     584              :     The stop token is propagated to the task for cooperative cancellation.
     585              :     The allocator parameter is reserved for future use and currently ignored.
     586              : 
     587              :     @param ex The executor to execute the task on.
     588              :     @param st The stop token for cooperative cancellation.
     589              :     @param alloc The frame allocator (currently ignored).
     590              : 
     591              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     592              : 
     593              :     @see task
     594              :     @see executor
     595              :     @see frame_allocator
     596              : */
     597              : template<Executor Ex, FrameAllocator FA>
     598              : [[nodiscard]] auto
     599              : run_async(Ex ex, std::stop_token st, FA alloc)
     600              : {
     601              :     (void)alloc; // Currently ignored
     602              :     return run_async_wrapper<Ex, default_handler>(
     603              :         std::move(ex),
     604              :         std::move(st),
     605              :         default_handler{});
     606              : }
     607              : 
     608              : /** Asynchronously launch a lazy task with stop token, allocator, and handler.
     609              : 
     610              :     The stop token is propagated to the task for cooperative cancellation.
     611              :     The allocator parameter is reserved for future use and currently ignored.
     612              : 
     613              :     @param ex The executor to execute the task on.
     614              :     @param st The stop token for cooperative cancellation.
     615              :     @param alloc The frame allocator (currently ignored).
     616              :     @param h1 The handler to invoke with the result (and optionally exception).
     617              : 
     618              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     619              : 
     620              :     @see task
     621              :     @see executor
     622              :     @see frame_allocator
     623              : */
     624              : template<Executor Ex, FrameAllocator FA, class H1>
     625              : [[nodiscard]] auto
     626              : run_async(Ex ex, std::stop_token st, FA alloc, H1 h1)
     627              : {
     628              :     (void)alloc; // Currently ignored
     629              :     return run_async_wrapper<Ex, handler_pair<H1, default_handler>>(
     630              :         std::move(ex),
     631              :         std::move(st),
     632              :         handler_pair<H1, default_handler>{std::move(h1)});
     633              : }
     634              : 
     635              : /** Asynchronously launch a lazy task with stop token, allocator, and handlers.
     636              : 
     637              :     The stop token is propagated to the task for cooperative cancellation.
     638              :     The allocator parameter is reserved for future use and currently ignored.
     639              : 
     640              :     @param ex The executor to execute the task on.
     641              :     @param st The stop token for cooperative cancellation.
     642              :     @param alloc The frame allocator (currently ignored).
     643              :     @param h1 The handler to invoke with the result on success.
     644              :     @param h2 The handler to invoke with the exception on failure.
     645              : 
     646              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     647              : 
     648              :     @see task
     649              :     @see executor
     650              :     @see frame_allocator
     651              : */
     652              : template<Executor Ex, FrameAllocator FA, class H1, class H2>
     653              : [[nodiscard]] auto
     654              : run_async(Ex ex, std::stop_token st, FA alloc, H1 h1, H2 h2)
     655              : {
     656              :     (void)alloc; // Currently ignored
     657              :     return run_async_wrapper<Ex, handler_pair<H1, H2>>(
     658              :         std::move(ex),
     659              :         std::move(st),
     660              :         handler_pair<H1, H2>{std::move(h1), std::move(h2)});
     661              : }
     662              : 
     663              : } // namespace capy
     664              : } // namespace boost
     665              : 
     666              : #endif
        

Generated by: LCOV version 2.3