LCOV - code coverage report
Current view: top level - capy/ex - run_async.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 83.8 % 105 88
Test Date: 2026-02-01 07:03:35 Functions: 81.4 % 1588 1293

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

Generated by: LCOV version 2.3