LCOV - code coverage report
Current view: top level - capy/ex - run_async.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 85.2 % 115 98
Test Date: 2026-02-12 22:58:59 Functions: 92.6 % 3119 2887

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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/detail/run.hpp>
      15              : #include <boost/capy/detail/run_callbacks.hpp>
      16              : #include <boost/capy/concept/executor.hpp>
      17              : #include <boost/capy/concept/io_runnable.hpp>
      18              : #include <boost/capy/ex/execution_context.hpp>
      19              : #include <boost/capy/ex/frame_allocator.hpp>
      20              : #include <boost/capy/ex/io_env.hpp>
      21              : #include <boost/capy/ex/recycling_memory_resource.hpp>
      22              : #include <boost/capy/ex/work_guard.hpp>
      23              : 
      24              : #include <coroutine>
      25              : #include <cstring>
      26              : #include <memory_resource>
      27              : #include <new>
      28              : #include <stop_token>
      29              : #include <type_traits>
      30              : 
      31              : namespace boost {
      32              : namespace capy {
      33              : namespace detail {
      34              : 
      35              : /// Function pointer type for type-erased frame deallocation.
      36              : using dealloc_fn = void(*)(void*, std::size_t);
      37              : 
      38              : /// Type-erased deallocator implementation for trampoline frames.
      39              : template<class Alloc>
      40              : void dealloc_impl(void* raw, std::size_t total)
      41              : {
      42              :     static_assert(std::is_same_v<typename Alloc::value_type, std::byte>);
      43              :     auto* a = std::launder(reinterpret_cast<Alloc*>(
      44              :         static_cast<char*>(raw) + total - sizeof(Alloc)));
      45              :     Alloc ba(std::move(*a));
      46              :     a->~Alloc();
      47              :     ba.deallocate(static_cast<std::byte*>(raw), total);
      48              : }
      49              : 
      50              : /// Awaiter to access the promise from within the coroutine.
      51              : template<class Promise>
      52              : struct get_promise_awaiter
      53              : {
      54              :     Promise* p_ = nullptr;
      55              : 
      56         2013 :     bool await_ready() const noexcept { return false; }
      57              : 
      58         2013 :     bool await_suspend(std::coroutine_handle<Promise> h) noexcept
      59              :     {
      60         2013 :         p_ = &h.promise();
      61         2013 :         return false;
      62              :     }
      63              : 
      64         2013 :     Promise& await_resume() const noexcept
      65              :     {
      66         2013 :         return *p_;
      67              :     }
      68              : };
      69              : 
      70              : /** Internal run_async_trampoline coroutine for run_async.
      71              : 
      72              :     The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation
      73              :     order) and serves as the task's continuation. When the task final_suspends,
      74              :     control returns to the run_async_trampoline which then invokes the appropriate handler.
      75              : 
      76              :     For value-type allocators, the run_async_trampoline stores a frame_memory_resource
      77              :     that wraps the allocator. For memory_resource*, it stores the pointer directly.
      78              : 
      79              :     @tparam Ex The executor type.
      80              :     @tparam Handlers The handler type (default_handler or handler_pair).
      81              :     @tparam Alloc The allocator type (value type or memory_resource*).
      82              : */
      83              : template<class Ex, class Handlers, class Alloc>
      84              : struct run_async_trampoline
      85              : {
      86              :     using invoke_fn = void(*)(void*, Handlers&);
      87              : 
      88              :     struct promise_type
      89              :     {
      90              :         work_guard<Ex> wg_;
      91              :         Handlers handlers_;
      92              :         frame_memory_resource<Alloc> resource_;
      93              :         io_env env_;
      94              :         invoke_fn invoke_ = nullptr;
      95              :         void* task_promise_ = nullptr;
      96              :         std::coroutine_handle<> task_h_;
      97              : 
      98              :         promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
      99              :             : wg_(std::move(ex))
     100              :             , handlers_(std::move(h))
     101              :             , resource_(std::move(a))
     102              :         {
     103              :         }
     104              : 
     105              :         static void* operator new(
     106              :             std::size_t size, Ex const&, Handlers const&, Alloc a)
     107              :         {
     108              :             using byte_alloc = typename std::allocator_traits<Alloc>
     109              :                 ::template rebind_alloc<std::byte>;
     110              : 
     111              :             constexpr auto footer_align =
     112              :                 (std::max)(alignof(dealloc_fn), alignof(Alloc));
     113              :             auto padded = (size + footer_align - 1) & ~(footer_align - 1);
     114              :             auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
     115              : 
     116              :             byte_alloc ba(std::move(a));
     117              :             void* raw = ba.allocate(total);
     118              : 
     119              :             auto* fn_loc = reinterpret_cast<dealloc_fn*>(
     120              :                 static_cast<char*>(raw) + padded);
     121              :             *fn_loc = &dealloc_impl<byte_alloc>;
     122              : 
     123              :             new (fn_loc + 1) byte_alloc(std::move(ba));
     124              : 
     125              :             return raw;
     126              :         }
     127              : 
     128            0 :         static void operator delete(void* ptr, std::size_t size)
     129              :         {
     130            0 :             constexpr auto footer_align =
     131              :                 (std::max)(alignof(dealloc_fn), alignof(Alloc));
     132            0 :             auto padded = (size + footer_align - 1) & ~(footer_align - 1);
     133            0 :             auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
     134              : 
     135            0 :             auto* fn = reinterpret_cast<dealloc_fn*>(
     136              :                 static_cast<char*>(ptr) + padded);
     137            0 :             (*fn)(ptr, total);
     138            0 :         }
     139              : 
     140              :         std::pmr::memory_resource* get_resource() noexcept
     141              :         {
     142              :             return &resource_;
     143              :         }
     144              : 
     145              :         run_async_trampoline get_return_object() noexcept
     146              :         {
     147              :             return run_async_trampoline{
     148              :                 std::coroutine_handle<promise_type>::from_promise(*this)};
     149              :         }
     150              : 
     151            0 :         std::suspend_always initial_suspend() noexcept
     152              :         {
     153            0 :             return {};
     154              :         }
     155              : 
     156            0 :         std::suspend_never final_suspend() noexcept
     157              :         {
     158            0 :             return {};
     159              :         }
     160              : 
     161            0 :         void return_void() noexcept
     162              :         {
     163            0 :         }
     164              : 
     165            0 :         void unhandled_exception() noexcept
     166              :         {
     167            0 :         }
     168              :     };
     169              : 
     170              :     std::coroutine_handle<promise_type> h_;
     171              : 
     172              :     template<IoRunnable Task>
     173              :     static void invoke_impl(void* p, Handlers& h)
     174              :     {
     175              :         using R = decltype(std::declval<Task&>().await_resume());
     176              :         auto& promise = *static_cast<typename Task::promise_type*>(p);
     177              :         if(promise.exception())
     178              :             h(promise.exception());
     179              :         else if constexpr(std::is_void_v<R>)
     180              :             h();
     181              :         else
     182              :             h(std::move(promise.result()));
     183              :     }
     184              : };
     185              : 
     186              : /** Specialization for memory_resource* - stores pointer directly.
     187              : 
     188              :     This avoids double indirection when the user passes a memory_resource*.
     189              : */
     190              : template<class Ex, class Handlers>
     191              : struct run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
     192              : {
     193              :     using invoke_fn = void(*)(void*, Handlers&);
     194              : 
     195              :     struct promise_type
     196              :     {
     197              :         work_guard<Ex> wg_;
     198              :         Handlers handlers_;
     199              :         std::pmr::memory_resource* mr_;
     200              :         io_env env_;
     201              :         invoke_fn invoke_ = nullptr;
     202              :         void* task_promise_ = nullptr;
     203              :         std::coroutine_handle<> task_h_;
     204              : 
     205         2014 :         promise_type(
     206              :             Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
     207         2014 :             : wg_(std::move(ex))
     208         2014 :             , handlers_(std::move(h))
     209         2014 :             , mr_(mr)
     210              :         {
     211         2014 :         }
     212              : 
     213         2014 :         static void* operator new(
     214              :             std::size_t size, Ex const&, Handlers const&,
     215              :             std::pmr::memory_resource* mr)
     216              :         {
     217         2014 :             auto total = size + sizeof(mr);
     218         2014 :             void* raw = mr->allocate(total, alignof(std::max_align_t));
     219         2014 :             std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
     220         2014 :             return raw;
     221              :         }
     222              : 
     223         2014 :         static void operator delete(void* ptr, std::size_t size)
     224              :         {
     225              :             std::pmr::memory_resource* mr;
     226         2014 :             std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
     227         2014 :             auto total = size + sizeof(mr);
     228         2014 :             mr->deallocate(ptr, total, alignof(std::max_align_t));
     229         2014 :         }
     230              : 
     231         4028 :         std::pmr::memory_resource* get_resource() noexcept
     232              :         {
     233         4028 :             return mr_;
     234              :         }
     235              : 
     236         2014 :         run_async_trampoline get_return_object() noexcept
     237              :         {
     238              :             return run_async_trampoline{
     239         2014 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
     240              :         }
     241              : 
     242         2014 :         std::suspend_always initial_suspend() noexcept
     243              :         {
     244         2014 :             return {};
     245              :         }
     246              : 
     247         2013 :         std::suspend_never final_suspend() noexcept
     248              :         {
     249         2013 :             return {};
     250              :         }
     251              : 
     252         2013 :         void return_void() noexcept
     253              :         {
     254         2013 :         }
     255              : 
     256            0 :         void unhandled_exception() noexcept
     257              :         {
     258            0 :         }
     259              :     };
     260              : 
     261              :     std::coroutine_handle<promise_type> h_;
     262              : 
     263              :     template<IoRunnable Task>
     264         2013 :     static void invoke_impl(void* p, Handlers& h)
     265              :     {
     266              :         using R = decltype(std::declval<Task&>().await_resume());
     267         2013 :         auto& promise = *static_cast<typename Task::promise_type*>(p);
     268         2013 :         if(promise.exception())
     269          720 :             h(promise.exception());
     270              :         else if constexpr(std::is_void_v<R>)
     271         1164 :             h();
     272              :         else
     273          129 :             h(std::move(promise.result()));
     274         2013 :     }
     275              : };
     276              : 
     277              : /// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
     278              : template<class Ex, class Handlers, class Alloc>
     279              : run_async_trampoline<Ex, Handlers, Alloc>
     280         2014 : make_trampoline(Ex, Handlers, Alloc)
     281              : {
     282              :     // promise_type ctor steals the parameters
     283              :     auto& p = co_await get_promise_awaiter<
     284              :         typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
     285              :     
     286              :     p.invoke_(p.task_promise_, p.handlers_);
     287              :     p.task_h_.destroy();
     288         4028 : }
     289              : 
     290              : } // namespace detail
     291              : 
     292              : //----------------------------------------------------------
     293              : //
     294              : // run_async_wrapper
     295              : //
     296              : //----------------------------------------------------------
     297              : 
     298              : /** Wrapper returned by run_async that accepts a task for execution.
     299              : 
     300              :     This wrapper holds the run_async_trampoline coroutine, executor, stop token,
     301              :     and handlers. The run_async_trampoline is allocated when the wrapper is constructed
     302              :     (before the task due to C++17 postfix evaluation order).
     303              : 
     304              :     The rvalue ref-qualifier on `operator()` ensures the wrapper can only
     305              :     be used as a temporary, preventing misuse that would violate LIFO ordering.
     306              : 
     307              :     @tparam Ex The executor type satisfying the `Executor` concept.
     308              :     @tparam Handlers The handler type (default_handler or handler_pair).
     309              :     @tparam Alloc The allocator type (value type or memory_resource*).
     310              : 
     311              :     @par Thread Safety
     312              :     The wrapper itself should only be used from one thread. The handlers
     313              :     may be invoked from any thread where the executor schedules work.
     314              : 
     315              :     @par Example
     316              :     @code
     317              :     // Correct usage - wrapper is temporary
     318              :     run_async(ex)(my_task());
     319              : 
     320              :     // Compile error - cannot call operator() on lvalue
     321              :     auto w = run_async(ex);
     322              :     w(my_task());  // Error: operator() requires rvalue
     323              :     @endcode
     324              : 
     325              :     @see run_async
     326              : */
     327              : template<Executor Ex, class Handlers, class Alloc>
     328              : class [[nodiscard]] run_async_wrapper
     329              : {
     330              :     detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
     331              :     std::stop_token st_;
     332              :     std::pmr::memory_resource* saved_tls_;
     333              : 
     334              : public:
     335              :     /// Construct wrapper with executor, stop token, handlers, and allocator.
     336         2014 :     run_async_wrapper(
     337              :         Ex ex,
     338              :         std::stop_token st,
     339              :         Handlers h,
     340              :         Alloc a) noexcept
     341         2015 :         : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
     342         2015 :             std::move(ex), std::move(h), std::move(a)))
     343         2014 :         , st_(std::move(st))
     344         2014 :         , saved_tls_(current_frame_allocator())
     345              :     {
     346              :         if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
     347              :         {
     348              :             static_assert(
     349              :                 std::is_nothrow_move_constructible_v<Alloc>,
     350              :                 "Allocator must be nothrow move constructible");
     351              :         }
     352              :         // Set TLS before task argument is evaluated
     353         2014 :         current_frame_allocator() = tr_.h_.promise().get_resource();
     354         2014 :     }
     355              : 
     356         2014 :     ~run_async_wrapper()
     357              :     {
     358              :         // Restore TLS so stale pointer doesn't outlive
     359              :         // the execution context that owns the resource.
     360         2014 :         current_frame_allocator() = saved_tls_;
     361         2014 :     }
     362              : 
     363              :     // Non-copyable, non-movable (must be used immediately)
     364              :     run_async_wrapper(run_async_wrapper const&) = delete;
     365              :     run_async_wrapper(run_async_wrapper&&) = delete;
     366              :     run_async_wrapper& operator=(run_async_wrapper const&) = delete;
     367              :     run_async_wrapper& operator=(run_async_wrapper&&) = delete;
     368              : 
     369              :     /** Launch the task for execution.
     370              : 
     371              :         This operator accepts a task and launches it on the executor.
     372              :         The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
     373              :         correct LIFO destruction order.
     374              : 
     375              :         The `io_env` constructed for the task is owned by the trampoline
     376              :         coroutine and is guaranteed to outlive the task and all awaitables
     377              :         in its chain. Awaitables may store `io_env const*` without concern
     378              :         for dangling references.
     379              : 
     380              :         @tparam Task The IoRunnable type.
     381              : 
     382              :         @param t The task to execute. Ownership is transferred to the
     383              :                  run_async_trampoline which will destroy it after completion.
     384              :     */
     385              :     template<IoRunnable Task>
     386         2014 :     void operator()(Task t) &&
     387              :     {
     388         2014 :         auto task_h = t.handle();
     389         2014 :         auto& task_promise = task_h.promise();
     390         2014 :         t.release();
     391              : 
     392         2014 :         auto& p = tr_.h_.promise();
     393              : 
     394              :         // Inject Task-specific invoke function
     395         2014 :         p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
     396         2014 :         p.task_promise_ = &task_promise;
     397         2014 :         p.task_h_ = task_h;
     398              : 
     399              :         // Setup task's continuation to return to run_async_trampoline
     400         2014 :         task_promise.set_continuation(tr_.h_);
     401         4028 :         p.env_ = {p.wg_.executor(), st_, p.get_resource()};
     402         2014 :         task_promise.set_environment(&p.env_);
     403              : 
     404              :         // Start task through executor
     405         2014 :         p.wg_.executor().dispatch(task_h).resume();
     406         4028 :     }
     407              : };
     408              : 
     409              : //----------------------------------------------------------
     410              : //
     411              : // run_async Overloads
     412              : //
     413              : //----------------------------------------------------------
     414              : 
     415              : // Executor only (uses default recycling allocator)
     416              : 
     417              : /** Asynchronously launch a lazy task on the given executor.
     418              : 
     419              :     Use this to start execution of a `task<T>` that was created lazily.
     420              :     The returned wrapper must be immediately invoked with the task;
     421              :     storing the wrapper and calling it later violates LIFO ordering.
     422              : 
     423              :     Uses the default recycling frame allocator for coroutine frames.
     424              :     With no handlers, the result is discarded and exceptions are rethrown.
     425              : 
     426              :     @par Thread Safety
     427              :     The wrapper and handlers may be called from any thread where the
     428              :     executor schedules work.
     429              : 
     430              :     @par Example
     431              :     @code
     432              :     run_async(ioc.get_executor())(my_task());
     433              :     @endcode
     434              : 
     435              :     @param ex The executor to execute the task on.
     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>
     443              : [[nodiscard]] auto
     444            2 : run_async(Ex ex)
     445              : {
     446            2 :     auto* mr = ex.context().get_frame_allocator();
     447              :     return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
     448            2 :         std::move(ex),
     449            4 :         std::stop_token{},
     450              :         detail::default_handler{},
     451            2 :         mr);
     452              : }
     453              : 
     454              : /** Asynchronously launch a lazy task with a result handler.
     455              : 
     456              :     The handler `h1` is called with the task's result on success. If `h1`
     457              :     is also invocable with `std::exception_ptr`, it handles exceptions too.
     458              :     Otherwise, exceptions are rethrown.
     459              : 
     460              :     @par Thread Safety
     461              :     The handler may be called from any thread where the executor
     462              :     schedules work.
     463              : 
     464              :     @par Example
     465              :     @code
     466              :     // Handler for result only (exceptions rethrown)
     467              :     run_async(ex, [](int result) {
     468              :         std::cout << "Got: " << result << "\n";
     469              :     })(compute_value());
     470              : 
     471              :     // Overloaded handler for both result and exception
     472              :     run_async(ex, overloaded{
     473              :         [](int result) { std::cout << "Got: " << result << "\n"; },
     474              :         [](std::exception_ptr) { std::cout << "Failed\n"; }
     475              :     })(compute_value());
     476              :     @endcode
     477              : 
     478              :     @param ex The executor to execute the task on.
     479              :     @param h1 The handler to invoke with the result (and optionally exception).
     480              : 
     481              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     482              : 
     483              :     @see task
     484              :     @see executor
     485              : */
     486              : template<Executor Ex, class H1>
     487              : [[nodiscard]] auto
     488           34 : run_async(Ex ex, H1 h1)
     489              : {
     490           34 :     auto* mr = ex.context().get_frame_allocator();
     491              :     return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
     492           34 :         std::move(ex),
     493           34 :         std::stop_token{},
     494           34 :         detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
     495           68 :         mr);
     496              : }
     497              : 
     498              : /** Asynchronously launch a lazy task with separate result and error handlers.
     499              : 
     500              :     The handler `h1` is called with the task's result on success.
     501              :     The handler `h2` is called with the exception_ptr on failure.
     502              : 
     503              :     @par Thread Safety
     504              :     The handlers may be called from any thread where the executor
     505              :     schedules work.
     506              : 
     507              :     @par Example
     508              :     @code
     509              :     run_async(ex,
     510              :         [](int result) { std::cout << "Got: " << result << "\n"; },
     511              :         [](std::exception_ptr ep) {
     512              :             try { std::rethrow_exception(ep); }
     513              :             catch (std::exception const& e) {
     514              :                 std::cout << "Error: " << e.what() << "\n";
     515              :             }
     516              :         }
     517              :     )(compute_value());
     518              :     @endcode
     519              : 
     520              :     @param ex The executor to execute the task on.
     521              :     @param h1 The handler to invoke with the result on success.
     522              :     @param h2 The handler to invoke with the exception on failure.
     523              : 
     524              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     525              : 
     526              :     @see task
     527              :     @see executor
     528              : */
     529              : template<Executor Ex, class H1, class H2>
     530              : [[nodiscard]] auto
     531           97 : run_async(Ex ex, H1 h1, H2 h2)
     532              : {
     533           97 :     auto* mr = ex.context().get_frame_allocator();
     534              :     return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
     535           97 :         std::move(ex),
     536           97 :         std::stop_token{},
     537           97 :         detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
     538          194 :         mr);
     539            1 : }
     540              : 
     541              : // Ex + stop_token
     542              : 
     543              : /** Asynchronously launch a lazy task with stop token support.
     544              : 
     545              :     The stop token is propagated to the task, enabling cooperative
     546              :     cancellation. With no handlers, the result is discarded and
     547              :     exceptions are rethrown.
     548              : 
     549              :     @par Thread Safety
     550              :     The wrapper may be called from any thread where the executor
     551              :     schedules work.
     552              : 
     553              :     @par Example
     554              :     @code
     555              :     std::stop_source source;
     556              :     run_async(ex, source.get_token())(cancellable_task());
     557              :     // Later: source.request_stop();
     558              :     @endcode
     559              : 
     560              :     @param ex The executor to execute the task on.
     561              :     @param st The stop token for cooperative cancellation.
     562              : 
     563              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     564              : 
     565              :     @see task
     566              :     @see executor
     567              : */
     568              : template<Executor Ex>
     569              : [[nodiscard]] auto
     570            1 : run_async(Ex ex, std::stop_token st)
     571              : {
     572            1 :     auto* mr = ex.context().get_frame_allocator();
     573              :     return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
     574            1 :         std::move(ex),
     575            1 :         std::move(st),
     576              :         detail::default_handler{},
     577            2 :         mr);
     578              : }
     579              : 
     580              : /** Asynchronously launch a lazy task with stop token and result handler.
     581              : 
     582              :     The stop token is propagated to the task for cooperative cancellation.
     583              :     The handler `h1` is called with the result on success, and optionally
     584              :     with exception_ptr if it accepts that type.
     585              : 
     586              :     @param ex The executor to execute the task on.
     587              :     @param st The stop token for cooperative cancellation.
     588              :     @param h1 The handler to invoke with the result (and optionally exception).
     589              : 
     590              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     591              : 
     592              :     @see task
     593              :     @see executor
     594              : */
     595              : template<Executor Ex, class H1>
     596              : [[nodiscard]] auto
     597         1871 : run_async(Ex ex, std::stop_token st, H1 h1)
     598              : {
     599         1871 :     auto* mr = ex.context().get_frame_allocator();
     600              :     return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
     601         1871 :         std::move(ex),
     602         1871 :         std::move(st),
     603         1871 :         detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
     604         3742 :         mr);
     605              : }
     606              : 
     607              : /** Asynchronously launch a lazy task with stop token and separate handlers.
     608              : 
     609              :     The stop token is propagated to the task for cooperative cancellation.
     610              :     The handler `h1` is called on success, `h2` on failure.
     611              : 
     612              :     @param ex The executor to execute the task on.
     613              :     @param st The stop token for cooperative cancellation.
     614              :     @param h1 The handler to invoke with the result on success.
     615              :     @param h2 The handler to invoke with the exception on failure.
     616              : 
     617              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     618              : 
     619              :     @see task
     620              :     @see executor
     621              : */
     622              : template<Executor Ex, class H1, class H2>
     623              : [[nodiscard]] auto
     624            9 : run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
     625              : {
     626            9 :     auto* mr = ex.context().get_frame_allocator();
     627              :     return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
     628            9 :         std::move(ex),
     629            9 :         std::move(st),
     630            9 :         detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
     631           18 :         mr);
     632              : }
     633              : 
     634              : // Ex + memory_resource*
     635              : 
     636              : /** Asynchronously launch a lazy task with custom memory resource.
     637              : 
     638              :     The memory resource is used for coroutine frame allocation. The caller
     639              :     is responsible for ensuring the memory resource outlives all tasks.
     640              : 
     641              :     @param ex The executor to execute the task on.
     642              :     @param mr The memory resource for frame allocation.
     643              : 
     644              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     645              : 
     646              :     @see task
     647              :     @see executor
     648              : */
     649              : template<Executor Ex>
     650              : [[nodiscard]] auto
     651              : run_async(Ex ex, std::pmr::memory_resource* mr)
     652              : {
     653              :     return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
     654              :         std::move(ex),
     655              :         std::stop_token{},
     656              :         detail::default_handler{},
     657              :         mr);
     658              : }
     659              : 
     660              : /** Asynchronously launch a lazy task with memory resource and handler.
     661              : 
     662              :     @param ex The executor to execute the task on.
     663              :     @param mr The memory resource for frame allocation.
     664              :     @param h1 The handler to invoke with the result (and optionally exception).
     665              : 
     666              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     667              : 
     668              :     @see task
     669              :     @see executor
     670              : */
     671              : template<Executor Ex, class H1>
     672              : [[nodiscard]] auto
     673              : run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
     674              : {
     675              :     return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
     676              :         std::move(ex),
     677              :         std::stop_token{},
     678              :         detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
     679              :         mr);
     680              : }
     681              : 
     682              : /** Asynchronously launch a lazy task with memory resource and handlers.
     683              : 
     684              :     @param ex The executor to execute the task on.
     685              :     @param mr The memory resource for frame allocation.
     686              :     @param h1 The handler to invoke with the result on success.
     687              :     @param h2 The handler to invoke with the exception on failure.
     688              : 
     689              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     690              : 
     691              :     @see task
     692              :     @see executor
     693              : */
     694              : template<Executor Ex, class H1, class H2>
     695              : [[nodiscard]] auto
     696              : run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
     697              : {
     698              :     return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
     699              :         std::move(ex),
     700              :         std::stop_token{},
     701              :         detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
     702              :         mr);
     703              : }
     704              : 
     705              : // Ex + stop_token + memory_resource*
     706              : 
     707              : /** Asynchronously launch a lazy task with stop token and memory resource.
     708              : 
     709              :     @param ex The executor to execute the task on.
     710              :     @param st The stop token for cooperative cancellation.
     711              :     @param mr The memory resource for frame allocation.
     712              : 
     713              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     714              : 
     715              :     @see task
     716              :     @see executor
     717              : */
     718              : template<Executor Ex>
     719              : [[nodiscard]] auto
     720              : run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
     721              : {
     722              :     return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
     723              :         std::move(ex),
     724              :         std::move(st),
     725              :         detail::default_handler{},
     726              :         mr);
     727              : }
     728              : 
     729              : /** Asynchronously launch a lazy task with stop token, memory resource, and handler.
     730              : 
     731              :     @param ex The executor to execute the task on.
     732              :     @param st The stop token for cooperative cancellation.
     733              :     @param mr The memory resource for frame allocation.
     734              :     @param h1 The handler to invoke with the result (and optionally exception).
     735              : 
     736              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     737              : 
     738              :     @see task
     739              :     @see executor
     740              : */
     741              : template<Executor Ex, class H1>
     742              : [[nodiscard]] auto
     743              : run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
     744              : {
     745              :     return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
     746              :         std::move(ex),
     747              :         std::move(st),
     748              :         detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
     749              :         mr);
     750              : }
     751              : 
     752              : /** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
     753              : 
     754              :     @param ex The executor to execute the task on.
     755              :     @param st The stop token for cooperative cancellation.
     756              :     @param mr The memory resource for frame allocation.
     757              :     @param h1 The handler to invoke with the result on success.
     758              :     @param h2 The handler to invoke with the exception on failure.
     759              : 
     760              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     761              : 
     762              :     @see task
     763              :     @see executor
     764              : */
     765              : template<Executor Ex, class H1, class H2>
     766              : [[nodiscard]] auto
     767              : run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
     768              : {
     769              :     return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
     770              :         std::move(ex),
     771              :         std::move(st),
     772              :         detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
     773              :         mr);
     774              : }
     775              : 
     776              : // Ex + standard Allocator (value type)
     777              : 
     778              : /** Asynchronously launch a lazy task with custom allocator.
     779              : 
     780              :     The allocator is wrapped in a frame_memory_resource and stored in the
     781              :     run_async_trampoline, ensuring it outlives all coroutine frames.
     782              : 
     783              :     @param ex The executor to execute the task on.
     784              :     @param alloc The allocator for frame allocation (copied and stored).
     785              : 
     786              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     787              : 
     788              :     @see task
     789              :     @see executor
     790              : */
     791              : template<Executor Ex, detail::Allocator Alloc>
     792              : [[nodiscard]] auto
     793              : run_async(Ex ex, Alloc alloc)
     794              : {
     795              :     return run_async_wrapper<Ex, detail::default_handler, Alloc>(
     796              :         std::move(ex),
     797              :         std::stop_token{},
     798              :         detail::default_handler{},
     799              :         std::move(alloc));
     800              : }
     801              : 
     802              : /** Asynchronously launch a lazy task with allocator and handler.
     803              : 
     804              :     @param ex The executor to execute the task on.
     805              :     @param alloc The allocator for frame allocation (copied and stored).
     806              :     @param h1 The handler to invoke with the result (and optionally exception).
     807              : 
     808              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     809              : 
     810              :     @see task
     811              :     @see executor
     812              : */
     813              : template<Executor Ex, detail::Allocator Alloc, class H1>
     814              : [[nodiscard]] auto
     815              : run_async(Ex ex, Alloc alloc, H1 h1)
     816              : {
     817              :     return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
     818              :         std::move(ex),
     819              :         std::stop_token{},
     820              :         detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
     821              :         std::move(alloc));
     822              : }
     823              : 
     824              : /** Asynchronously launch a lazy task with allocator and handlers.
     825              : 
     826              :     @param ex The executor to execute the task on.
     827              :     @param alloc The allocator for frame allocation (copied and stored).
     828              :     @param h1 The handler to invoke with the result on success.
     829              :     @param h2 The handler to invoke with the exception on failure.
     830              : 
     831              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     832              : 
     833              :     @see task
     834              :     @see executor
     835              : */
     836              : template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
     837              : [[nodiscard]] auto
     838              : run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
     839              : {
     840              :     return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
     841              :         std::move(ex),
     842              :         std::stop_token{},
     843              :         detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
     844              :         std::move(alloc));
     845              : }
     846              : 
     847              : // Ex + stop_token + standard Allocator
     848              : 
     849              : /** Asynchronously launch a lazy task with stop token and allocator.
     850              : 
     851              :     @param ex The executor to execute the task on.
     852              :     @param st The stop token for cooperative cancellation.
     853              :     @param alloc The allocator for frame allocation (copied and stored).
     854              : 
     855              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     856              : 
     857              :     @see task
     858              :     @see executor
     859              : */
     860              : template<Executor Ex, detail::Allocator Alloc>
     861              : [[nodiscard]] auto
     862              : run_async(Ex ex, std::stop_token st, Alloc alloc)
     863              : {
     864              :     return run_async_wrapper<Ex, detail::default_handler, Alloc>(
     865              :         std::move(ex),
     866              :         std::move(st),
     867              :         detail::default_handler{},
     868              :         std::move(alloc));
     869              : }
     870              : 
     871              : /** Asynchronously launch a lazy task with stop token, allocator, and handler.
     872              : 
     873              :     @param ex The executor to execute the task on.
     874              :     @param st The stop token for cooperative cancellation.
     875              :     @param alloc The allocator for frame allocation (copied and stored).
     876              :     @param h1 The handler to invoke with the result (and optionally exception).
     877              : 
     878              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     879              : 
     880              :     @see task
     881              :     @see executor
     882              : */
     883              : template<Executor Ex, detail::Allocator Alloc, class H1>
     884              : [[nodiscard]] auto
     885              : run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
     886              : {
     887              :     return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
     888              :         std::move(ex),
     889              :         std::move(st),
     890              :         detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
     891              :         std::move(alloc));
     892              : }
     893              : 
     894              : /** Asynchronously launch a lazy task with stop token, allocator, and handlers.
     895              : 
     896              :     @param ex The executor to execute the task on.
     897              :     @param st The stop token for cooperative cancellation.
     898              :     @param alloc The allocator for frame allocation (copied and stored).
     899              :     @param h1 The handler to invoke with the result on success.
     900              :     @param h2 The handler to invoke with the exception on failure.
     901              : 
     902              :     @return A wrapper that accepts a `task<T>` for immediate execution.
     903              : 
     904              :     @see task
     905              :     @see executor
     906              : */
     907              : template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
     908              : [[nodiscard]] auto
     909              : run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
     910              : {
     911              :     return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
     912              :         std::move(ex),
     913              :         std::move(st),
     914              :         detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
     915              :         std::move(alloc));
     916              : }
     917              : 
     918              : } // namespace capy
     919              : } // namespace boost
     920              : 
     921              : #endif
        

Generated by: LCOV version 2.3