LCOV - code coverage report
Current view: top level - boost/capy/ex - run_async.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 83.8 % 105 88
Test Date: 2026-01-30 23:43:15 Functions: 79.6 % 1384 1101

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

Generated by: LCOV version 2.3