LCOV - code coverage report
Current view: top level - capy/ex - run.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 99.5 % 186 185
Test Date: 2026-02-12 16:53:28 Functions: 99.2 % 125 124

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3              : //
       4              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6              : //
       7              : // Official repository: https://github.com/cppalliance/capy
       8              : //
       9              : 
      10              : #ifndef BOOST_CAPY_RUN_HPP
      11              : #define BOOST_CAPY_RUN_HPP
      12              : 
      13              : #include <boost/capy/detail/config.hpp>
      14              : #include <boost/capy/detail/run.hpp>
      15              : #include <boost/capy/concept/executor.hpp>
      16              : #include <boost/capy/concept/io_runnable.hpp>
      17              : #include <boost/capy/ex/executor_ref.hpp>
      18              : #include <coroutine>
      19              : #include <boost/capy/ex/frame_allocator.hpp>
      20              : #include <boost/capy/ex/io_env.hpp>
      21              : 
      22              : #include <memory_resource>
      23              : #include <stop_token>
      24              : #include <type_traits>
      25              : #include <utility>
      26              : #include <variant>
      27              : 
      28              : /*
      29              :     Allocator Lifetime Strategy
      30              :     ===========================
      31              : 
      32              :     When using run() with a custom allocator:
      33              : 
      34              :         co_await run(ex, alloc)(my_task());
      35              : 
      36              :     The evaluation order is:
      37              :         1. run(ex, alloc) creates a temporary wrapper
      38              :         2. my_task() allocates its coroutine frame using TLS
      39              :         3. operator() returns an awaitable
      40              :         4. Wrapper temporary is DESTROYED
      41              :         5. co_await suspends caller, resumes task
      42              :         6. Task body executes (wrapper is already dead!)
      43              : 
      44              :     Problem: The wrapper's frame_memory_resource dies before the task
      45              :     body runs. When initial_suspend::await_resume() restores TLS from
      46              :     the saved pointer, it would point to dead memory.
      47              : 
      48              :     Solution: Store a COPY of the allocator in the awaitable (not just
      49              :     the wrapper). The co_await mechanism extends the awaitable's lifetime
      50              :     until the await completes. In await_suspend, we overwrite the promise's
      51              :     saved frame_allocator pointer to point to the awaitable's resource.
      52              : 
      53              :     This works because standard allocator copies are equivalent - memory
      54              :     allocated with one copy can be deallocated with another copy. The
      55              :     task's own frame uses the footer-stored pointer (safe), while nested
      56              :     task creation uses TLS pointing to the awaitable's resource (also safe).
      57              : */
      58              : 
      59              : namespace boost::capy::detail {
      60              : 
      61              : //----------------------------------------------------------
      62              : //
      63              : // dispatch_trampoline - cross-executor dispatch
      64              : //
      65              : //----------------------------------------------------------
      66              : 
      67              : /** Minimal coroutine that dispatches through the caller's executor.
      68              : 
      69              :     Sits between the inner task and the parent when executors
      70              :     diverge. The inner task's `final_suspend` resumes this
      71              :     trampoline via symmetric transfer. The trampoline's own
      72              :     `final_suspend` dispatches the parent through the caller's
      73              :     executor to restore the correct execution context.
      74              : 
      75              :     The trampoline never touches the task's result.
      76              : */
      77              : struct dispatch_trampoline
      78              : {
      79              :     struct promise_type
      80              :     {
      81              :         executor_ref caller_ex_;
      82              :         std::coroutine_handle<> parent_;
      83              : 
      84            9 :         dispatch_trampoline get_return_object() noexcept
      85              :         {
      86              :             return dispatch_trampoline{
      87            9 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
      88              :         }
      89              : 
      90            9 :         std::suspend_always initial_suspend() noexcept { return {}; }
      91              : 
      92            9 :         auto final_suspend() noexcept
      93              :         {
      94              :             struct awaiter
      95              :             {
      96              :                 promise_type* p_;
      97            9 :                 bool await_ready() const noexcept { return false; }
      98              : 
      99            9 :                 std::coroutine_handle<> await_suspend(
     100              :                     std::coroutine_handle<>) noexcept
     101              :                 {
     102            9 :                     return p_->caller_ex_.dispatch(p_->parent_);
     103              :                 }
     104              : 
     105            0 :                 void await_resume() const noexcept {}
     106              :             };
     107            9 :             return awaiter{this};
     108              :         }
     109              : 
     110            9 :         void return_void() noexcept {}
     111              :         void unhandled_exception() noexcept {}
     112              :     };
     113              : 
     114              :     std::coroutine_handle<promise_type> h_{nullptr};
     115              : 
     116            9 :     dispatch_trampoline() noexcept = default;
     117              : 
     118           27 :     ~dispatch_trampoline()
     119              :     {
     120           27 :         if(h_) h_.destroy();
     121           27 :     }
     122              : 
     123              :     dispatch_trampoline(dispatch_trampoline const&) = delete;
     124              :     dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
     125              : 
     126            9 :     dispatch_trampoline(dispatch_trampoline&& o) noexcept
     127            9 :         : h_(std::exchange(o.h_, nullptr)) {}
     128              : 
     129            9 :     dispatch_trampoline& operator=(dispatch_trampoline&& o) noexcept
     130              :     {
     131            9 :         if(this != &o)
     132              :         {
     133            9 :             if(h_) h_.destroy();
     134            9 :             h_ = std::exchange(o.h_, nullptr);
     135              :         }
     136            9 :         return *this;
     137              :     }
     138              : 
     139              : private:
     140            9 :     explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
     141            9 :         : h_(h) {}
     142              : };
     143              : 
     144            9 : inline dispatch_trampoline make_dispatch_trampoline()
     145              : {
     146              :     co_return;
     147           18 : }
     148              : 
     149              : //----------------------------------------------------------
     150              : //
     151              : // run_awaitable_ex - with executor (executor switch)
     152              : //
     153              : //----------------------------------------------------------
     154              : 
     155              : /** Awaitable that binds an IoRunnable to a specific executor.
     156              : 
     157              :     Stores the executor and inner task by value. When co_awaited, the
     158              :     co_await expression's lifetime extension keeps both alive for the
     159              :     duration of the operation.
     160              : 
     161              :     A dispatch trampoline handles the executor switch on completion:
     162              :     the inner task's `final_suspend` resumes the trampoline, which
     163              :     dispatches back through the caller's executor.
     164              : 
     165              :     The `io_env` is owned by this awaitable and is guaranteed to
     166              :     outlive the inner task and all awaitables in its chain. Awaitables
     167              :     may store `io_env const*` without concern for dangling references.
     168              : 
     169              :     @tparam Task The IoRunnable type
     170              :     @tparam Ex The executor type
     171              :     @tparam InheritStopToken If true, inherit caller's stop token
     172              :     @tparam Alloc The allocator type (void for no allocator)
     173              : */
     174              : template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
     175              : struct [[nodiscard]] run_awaitable_ex
     176              : {
     177              :     Ex ex_;
     178              :     frame_memory_resource<Alloc> resource_;
     179              :     std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
     180              :     io_env env_;
     181              :     dispatch_trampoline tr_;
     182              :     Task inner_;  // Last: destroyed first, while env_ is still valid
     183              : 
     184              :     // void allocator, inherit stop token
     185            3 :     run_awaitable_ex(Ex ex, Task inner)
     186              :         requires (InheritStopToken && std::is_void_v<Alloc>)
     187            3 :         : ex_(std::move(ex))
     188            3 :         , inner_(std::move(inner))
     189              :     {
     190            3 :     }
     191              : 
     192              :     // void allocator, explicit stop token
     193            2 :     run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
     194              :         requires (!InheritStopToken && std::is_void_v<Alloc>)
     195            2 :         : ex_(std::move(ex))
     196            2 :         , st_(std::move(st))
     197            2 :         , inner_(std::move(inner))
     198              :     {
     199            2 :     }
     200              : 
     201              :     // with allocator, inherit stop token (use template to avoid void parameter)
     202              :     template<class A>
     203              :         requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
     204            2 :     run_awaitable_ex(Ex ex, A alloc, Task inner)
     205            2 :         : ex_(std::move(ex))
     206            2 :         , resource_(std::move(alloc))
     207            2 :         , inner_(std::move(inner))
     208              :     {
     209            2 :     }
     210              : 
     211              :     // with allocator, explicit stop token (use template to avoid void parameter)
     212              :     template<class A>
     213              :         requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
     214            2 :     run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
     215            2 :         : ex_(std::move(ex))
     216            2 :         , resource_(std::move(alloc))
     217            2 :         , st_(std::move(st))
     218            2 :         , inner_(std::move(inner))
     219              :     {
     220            2 :     }
     221              : 
     222            9 :     bool await_ready() const noexcept
     223              :     {
     224            9 :         return inner_.await_ready();
     225              :     }
     226              : 
     227            9 :     decltype(auto) await_resume()
     228              :     {
     229            9 :         return inner_.await_resume();
     230              :     }
     231              : 
     232            9 :     std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
     233              :     {
     234            9 :         tr_ = make_dispatch_trampoline();
     235            9 :         tr_.h_.promise().caller_ex_ = caller_env->executor;
     236            9 :         tr_.h_.promise().parent_ = cont;
     237              : 
     238            9 :         auto h = inner_.handle();
     239            9 :         auto& p = h.promise();
     240            9 :         p.set_continuation(tr_.h_);
     241              : 
     242            9 :         env_.executor = ex_;
     243              :         if constexpr (InheritStopToken)
     244            5 :             env_.stop_token = caller_env->stop_token;
     245              :         else
     246            4 :             env_.stop_token = st_;
     247              : 
     248              :         if constexpr (!std::is_void_v<Alloc>)
     249            4 :             env_.allocator = resource_.get();
     250              :         else
     251            5 :             env_.allocator = caller_env->allocator;
     252              : 
     253            9 :         p.set_environment(&env_);
     254           18 :         return h;
     255              :     }
     256              : 
     257              :     // Non-copyable
     258              :     run_awaitable_ex(run_awaitable_ex const&) = delete;
     259              :     run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
     260              : 
     261              :     // Movable (no noexcept - Task may throw)
     262            9 :     run_awaitable_ex(run_awaitable_ex&&) = default;
     263              :     run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
     264              : };
     265              : 
     266              : //----------------------------------------------------------
     267              : //
     268              : // run_awaitable - no executor (inherits caller's executor)
     269              : //
     270              : //----------------------------------------------------------
     271              : 
     272              : /** Awaitable that runs a task with optional stop_token override.
     273              : 
     274              :     Does NOT store an executor - the task inherits the caller's executor
     275              :     directly. Executors always match, so no dispatch trampoline is needed.
     276              :     The inner task's `final_suspend` resumes the parent directly via
     277              :     unconditional symmetric transfer.
     278              : 
     279              :     @tparam Task The IoRunnable type
     280              :     @tparam InheritStopToken If true, inherit caller's stop token
     281              :     @tparam Alloc The allocator type (void for no allocator)
     282              : */
     283              : template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
     284              : struct [[nodiscard]] run_awaitable
     285              : {
     286              :     frame_memory_resource<Alloc> resource_;
     287              :     std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
     288              :     io_env env_;
     289              :     Task inner_;  // Last: destroyed first, while env_ is still valid
     290              : 
     291              :     // void allocator, inherit stop token
     292              :     explicit run_awaitable(Task inner)
     293              :         requires (InheritStopToken && std::is_void_v<Alloc>)
     294              :         : inner_(std::move(inner))
     295              :     {
     296              :     }
     297              : 
     298              :     // void allocator, explicit stop token
     299            1 :     run_awaitable(Task inner, std::stop_token st)
     300              :         requires (!InheritStopToken && std::is_void_v<Alloc>)
     301            1 :         : st_(std::move(st))
     302            1 :         , inner_(std::move(inner))
     303              :     {
     304            1 :     }
     305              : 
     306              :     // with allocator, inherit stop token (use template to avoid void parameter)
     307              :     template<class A>
     308              :         requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
     309            3 :     run_awaitable(A alloc, Task inner)
     310            3 :         : resource_(std::move(alloc))
     311            3 :         , inner_(std::move(inner))
     312              :     {
     313            3 :     }
     314              : 
     315              :     // with allocator, explicit stop token (use template to avoid void parameter)
     316              :     template<class A>
     317              :         requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
     318            2 :     run_awaitable(A alloc, Task inner, std::stop_token st)
     319            2 :         : resource_(std::move(alloc))
     320            2 :         , st_(std::move(st))
     321            2 :         , inner_(std::move(inner))
     322              :     {
     323            2 :     }
     324              : 
     325            6 :     bool await_ready() const noexcept
     326              :     {
     327            6 :         return inner_.await_ready();
     328              :     }
     329              : 
     330            6 :     decltype(auto) await_resume()
     331              :     {
     332            6 :         return inner_.await_resume();
     333              :     }
     334              : 
     335            6 :     std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
     336              :     {
     337            6 :         auto h = inner_.handle();
     338            6 :         auto& p = h.promise();
     339            6 :         p.set_continuation(cont);
     340              : 
     341            6 :         env_.executor = caller_env->executor;
     342              :         if constexpr (InheritStopToken)
     343            3 :             env_.stop_token = caller_env->stop_token;
     344              :         else
     345            3 :             env_.stop_token = st_;
     346              : 
     347              :         if constexpr (!std::is_void_v<Alloc>)
     348            5 :             env_.allocator = resource_.get();
     349              :         else
     350            1 :             env_.allocator = caller_env->allocator;
     351              : 
     352            6 :         p.set_environment(&env_);
     353            6 :         return h;
     354              :     }
     355              : 
     356              :     // Non-copyable
     357              :     run_awaitable(run_awaitable const&) = delete;
     358              :     run_awaitable& operator=(run_awaitable const&) = delete;
     359              : 
     360              :     // Movable (no noexcept - Task may throw)
     361            6 :     run_awaitable(run_awaitable&&) = default;
     362              :     run_awaitable& operator=(run_awaitable&&) = default;
     363              : };
     364              : 
     365              : //----------------------------------------------------------
     366              : //
     367              : // run_wrapper_ex - with executor
     368              : //
     369              : //----------------------------------------------------------
     370              : 
     371              : /** Wrapper returned by run(ex, ...) that accepts a task for execution.
     372              : 
     373              :     @tparam Ex The executor type.
     374              :     @tparam InheritStopToken If true, inherit caller's stop token.
     375              :     @tparam Alloc The allocator type (void for no allocator).
     376              : */
     377              : template<Executor Ex, bool InheritStopToken, class Alloc>
     378              : class [[nodiscard]] run_wrapper_ex
     379              : {
     380              :     Ex ex_;
     381              :     std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
     382              :     frame_memory_resource<Alloc> resource_;
     383              :     Alloc alloc_;  // Copy to pass to awaitable
     384              : 
     385              : public:
     386            1 :     run_wrapper_ex(Ex ex, Alloc alloc)
     387              :         requires InheritStopToken
     388            1 :         : ex_(std::move(ex))
     389            1 :         , resource_(alloc)
     390            1 :         , alloc_(std::move(alloc))
     391              :     {
     392            1 :         current_frame_allocator() = &resource_;
     393            1 :     }
     394              : 
     395            1 :     run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
     396              :         requires (!InheritStopToken)
     397            1 :         : ex_(std::move(ex))
     398            1 :         , st_(std::move(st))
     399            1 :         , resource_(alloc)
     400            1 :         , alloc_(std::move(alloc))
     401              :     {
     402            1 :         current_frame_allocator() = &resource_;
     403            1 :     }
     404              : 
     405              :     // Non-copyable, non-movable (must be used immediately)
     406              :     run_wrapper_ex(run_wrapper_ex const&) = delete;
     407              :     run_wrapper_ex(run_wrapper_ex&&) = delete;
     408              :     run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
     409              :     run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
     410              : 
     411              :     template<IoRunnable Task>
     412            2 :     [[nodiscard]] auto operator()(Task t) &&
     413              :     {
     414              :         if constexpr (InheritStopToken)
     415              :             return run_awaitable_ex<Task, Ex, true, Alloc>{
     416            1 :                 std::move(ex_), std::move(alloc_), std::move(t)};
     417              :         else
     418              :             return run_awaitable_ex<Task, Ex, false, Alloc>{
     419            1 :                 std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
     420              :     }
     421              : };
     422              : 
     423              : /// Specialization for memory_resource* - stores pointer directly.
     424              : template<Executor Ex, bool InheritStopToken>
     425              : class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
     426              : {
     427              :     Ex ex_;
     428              :     std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
     429              :     std::pmr::memory_resource* mr_;
     430              : 
     431              : public:
     432            1 :     run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
     433              :         requires InheritStopToken
     434            1 :         : ex_(std::move(ex))
     435            1 :         , mr_(mr)
     436              :     {
     437            1 :         current_frame_allocator() = mr_;
     438            1 :     }
     439              : 
     440            1 :     run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
     441              :         requires (!InheritStopToken)
     442            1 :         : ex_(std::move(ex))
     443            1 :         , st_(std::move(st))
     444            1 :         , mr_(mr)
     445              :     {
     446            1 :         current_frame_allocator() = mr_;
     447            1 :     }
     448              : 
     449              :     // Non-copyable, non-movable (must be used immediately)
     450              :     run_wrapper_ex(run_wrapper_ex const&) = delete;
     451              :     run_wrapper_ex(run_wrapper_ex&&) = delete;
     452              :     run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
     453              :     run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
     454              : 
     455              :     template<IoRunnable Task>
     456            2 :     [[nodiscard]] auto operator()(Task t) &&
     457              :     {
     458              :         if constexpr (InheritStopToken)
     459              :             return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
     460            1 :                 std::move(ex_), mr_, std::move(t)};
     461              :         else
     462              :             return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
     463            1 :                 std::move(ex_), mr_, std::move(t), std::move(st_)};
     464              :     }
     465              : };
     466              : 
     467              : /// Specialization for no allocator (void).
     468              : template<Executor Ex, bool InheritStopToken>
     469              : class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
     470              : {
     471              :     Ex ex_;
     472              :     std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
     473              : 
     474              : public:
     475            3 :     explicit run_wrapper_ex(Ex ex)
     476              :         requires InheritStopToken
     477            3 :         : ex_(std::move(ex))
     478              :     {
     479            3 :     }
     480              : 
     481            2 :     run_wrapper_ex(Ex ex, std::stop_token st)
     482              :         requires (!InheritStopToken)
     483            2 :         : ex_(std::move(ex))
     484            2 :         , st_(std::move(st))
     485              :     {
     486            2 :     }
     487              : 
     488              :     // Non-copyable, non-movable (must be used immediately)
     489              :     run_wrapper_ex(run_wrapper_ex const&) = delete;
     490              :     run_wrapper_ex(run_wrapper_ex&&) = delete;
     491              :     run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
     492              :     run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
     493              : 
     494              :     template<IoRunnable Task>
     495            5 :     [[nodiscard]] auto operator()(Task t) &&
     496              :     {
     497              :         if constexpr (InheritStopToken)
     498              :             return run_awaitable_ex<Task, Ex, true>{
     499            3 :                 std::move(ex_), std::move(t)};
     500              :         else
     501              :             return run_awaitable_ex<Task, Ex, false>{
     502            2 :                 std::move(ex_), std::move(t), std::move(st_)};
     503              :     }
     504              : };
     505              : 
     506              : //----------------------------------------------------------
     507              : //
     508              : // run_wrapper - no executor (inherits caller's executor)
     509              : //
     510              : //----------------------------------------------------------
     511              : 
     512              : /** Wrapper returned by run(st) or run(alloc) that accepts a task.
     513              : 
     514              :     @tparam InheritStopToken If true, inherit caller's stop token.
     515              :     @tparam Alloc The allocator type (void for no allocator).
     516              : */
     517              : template<bool InheritStopToken, class Alloc>
     518              : class [[nodiscard]] run_wrapper
     519              : {
     520              :     std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
     521              :     frame_memory_resource<Alloc> resource_;
     522              :     Alloc alloc_;  // Copy to pass to awaitable
     523              : 
     524              : public:
     525            1 :     explicit run_wrapper(Alloc alloc)
     526              :         requires InheritStopToken
     527            1 :         : resource_(alloc)
     528            1 :         , alloc_(std::move(alloc))
     529              :     {
     530            1 :         current_frame_allocator() = &resource_;
     531            1 :     }
     532              : 
     533            1 :     run_wrapper(std::stop_token st, Alloc alloc)
     534              :         requires (!InheritStopToken)
     535            1 :         : st_(std::move(st))
     536            1 :         , resource_(alloc)
     537            1 :         , alloc_(std::move(alloc))
     538              :     {
     539            1 :         current_frame_allocator() = &resource_;
     540            1 :     }
     541              : 
     542              :     // Non-copyable, non-movable (must be used immediately)
     543              :     run_wrapper(run_wrapper const&) = delete;
     544              :     run_wrapper(run_wrapper&&) = delete;
     545              :     run_wrapper& operator=(run_wrapper const&) = delete;
     546              :     run_wrapper& operator=(run_wrapper&&) = delete;
     547              : 
     548              :     template<IoRunnable Task>
     549            2 :     [[nodiscard]] auto operator()(Task t) &&
     550              :     {
     551              :         if constexpr (InheritStopToken)
     552              :             return run_awaitable<Task, true, Alloc>{
     553            1 :                 std::move(alloc_), std::move(t)};
     554              :         else
     555              :             return run_awaitable<Task, false, Alloc>{
     556            1 :                 std::move(alloc_), std::move(t), std::move(st_)};
     557              :     }
     558              : };
     559              : 
     560              : /// Specialization for memory_resource* - stores pointer directly.
     561              : template<bool InheritStopToken>
     562              : class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
     563              : {
     564              :     std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
     565              :     std::pmr::memory_resource* mr_;
     566              : 
     567              : public:
     568            2 :     explicit run_wrapper(std::pmr::memory_resource* mr)
     569              :         requires InheritStopToken
     570            2 :         : mr_(mr)
     571              :     {
     572            2 :         current_frame_allocator() = mr_;
     573            2 :     }
     574              : 
     575            1 :     run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
     576              :         requires (!InheritStopToken)
     577            1 :         : st_(std::move(st))
     578            1 :         , mr_(mr)
     579              :     {
     580            1 :         current_frame_allocator() = mr_;
     581            1 :     }
     582              : 
     583              :     // Non-copyable, non-movable (must be used immediately)
     584              :     run_wrapper(run_wrapper const&) = delete;
     585              :     run_wrapper(run_wrapper&&) = delete;
     586              :     run_wrapper& operator=(run_wrapper const&) = delete;
     587              :     run_wrapper& operator=(run_wrapper&&) = delete;
     588              : 
     589              :     template<IoRunnable Task>
     590            3 :     [[nodiscard]] auto operator()(Task t) &&
     591              :     {
     592              :         if constexpr (InheritStopToken)
     593              :             return run_awaitable<Task, true, std::pmr::memory_resource*>{
     594            2 :                 mr_, std::move(t)};
     595              :         else
     596              :             return run_awaitable<Task, false, std::pmr::memory_resource*>{
     597            1 :                 mr_, std::move(t), std::move(st_)};
     598              :     }
     599              : };
     600              : 
     601              : /// Specialization for stop_token only (no allocator).
     602              : template<>
     603              : class [[nodiscard]] run_wrapper<false, void>
     604              : {
     605              :     std::stop_token st_;
     606              : 
     607              : public:
     608            1 :     explicit run_wrapper(std::stop_token st)
     609            1 :         : st_(std::move(st))
     610              :     {
     611            1 :     }
     612              : 
     613              :     // Non-copyable, non-movable (must be used immediately)
     614              :     run_wrapper(run_wrapper const&) = delete;
     615              :     run_wrapper(run_wrapper&&) = delete;
     616              :     run_wrapper& operator=(run_wrapper const&) = delete;
     617              :     run_wrapper& operator=(run_wrapper&&) = delete;
     618              : 
     619              :     template<IoRunnable Task>
     620            1 :     [[nodiscard]] auto operator()(Task t) &&
     621              :     {
     622            1 :         return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
     623              :     }
     624              : };
     625              : 
     626              : } // namespace boost::capy::detail
     627              : 
     628              : namespace boost::capy {
     629              : 
     630              : //----------------------------------------------------------
     631              : //
     632              : // run() overloads - with executor
     633              : //
     634              : //----------------------------------------------------------
     635              : 
     636              : /** Bind a task to execute on a specific executor.
     637              : 
     638              :     Returns a wrapper that accepts a task and produces an awaitable.
     639              :     When co_awaited, the task runs on the specified executor.
     640              : 
     641              :     @par Example
     642              :     @code
     643              :     co_await run(other_executor)(my_task());
     644              :     @endcode
     645              : 
     646              :     @param ex The executor on which the task should run.
     647              : 
     648              :     @return A wrapper that accepts a task for execution.
     649              : 
     650              :     @see task
     651              :     @see executor
     652              : */
     653              : template<Executor Ex>
     654              : [[nodiscard]] auto
     655            3 : run(Ex ex)
     656              : {
     657            3 :     return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
     658              : }
     659              : 
     660              : /** Bind a task to an executor with a stop token.
     661              : 
     662              :     @param ex The executor on which the task should run.
     663              :     @param st The stop token for cooperative cancellation.
     664              : 
     665              :     @return A wrapper that accepts a task for execution.
     666              : */
     667              : template<Executor Ex>
     668              : [[nodiscard]] auto
     669            2 : run(Ex ex, std::stop_token st)
     670              : {
     671              :     return detail::run_wrapper_ex<Ex, false, void>{
     672            2 :         std::move(ex), std::move(st)};
     673              : }
     674              : 
     675              : /** Bind a task to an executor with a memory resource.
     676              : 
     677              :     @param ex The executor on which the task should run.
     678              :     @param mr The memory resource for frame allocation.
     679              : 
     680              :     @return A wrapper that accepts a task for execution.
     681              : */
     682              : template<Executor Ex>
     683              : [[nodiscard]] auto
     684            1 : run(Ex ex, std::pmr::memory_resource* mr)
     685              : {
     686              :     return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
     687            1 :         std::move(ex), mr};
     688              : }
     689              : 
     690              : /** Bind a task to an executor with a standard allocator.
     691              : 
     692              :     @param ex The executor on which the task should run.
     693              :     @param alloc The allocator for frame allocation.
     694              : 
     695              :     @return A wrapper that accepts a task for execution.
     696              : */
     697              : template<Executor Ex, detail::Allocator Alloc>
     698              : [[nodiscard]] auto
     699            1 : run(Ex ex, Alloc alloc)
     700              : {
     701              :     return detail::run_wrapper_ex<Ex, true, Alloc>{
     702            1 :         std::move(ex), std::move(alloc)};
     703              : }
     704              : 
     705              : /** Bind a task to an executor with stop token and memory resource.
     706              : 
     707              :     @param ex The executor on which the task should run.
     708              :     @param st The stop token for cooperative cancellation.
     709              :     @param mr The memory resource for frame allocation.
     710              : 
     711              :     @return A wrapper that accepts a task for execution.
     712              : */
     713              : template<Executor Ex>
     714              : [[nodiscard]] auto
     715            1 : run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
     716              : {
     717              :     return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
     718            1 :         std::move(ex), std::move(st), mr};
     719              : }
     720              : 
     721              : /** Bind a task to an executor with stop token and standard allocator.
     722              : 
     723              :     @param ex The executor on which the task should run.
     724              :     @param st The stop token for cooperative cancellation.
     725              :     @param alloc The allocator for frame allocation.
     726              : 
     727              :     @return A wrapper that accepts a task for execution.
     728              : */
     729              : template<Executor Ex, detail::Allocator Alloc>
     730              : [[nodiscard]] auto
     731            1 : run(Ex ex, std::stop_token st, Alloc alloc)
     732              : {
     733              :     return detail::run_wrapper_ex<Ex, false, Alloc>{
     734            1 :         std::move(ex), std::move(st), std::move(alloc)};
     735              : }
     736              : 
     737              : //----------------------------------------------------------
     738              : //
     739              : // run() overloads - no executor (inherits caller's)
     740              : //
     741              : //----------------------------------------------------------
     742              : 
     743              : /** Run a task with a custom stop token.
     744              : 
     745              :     The task inherits the caller's executor. Only the stop token
     746              :     is overridden.
     747              : 
     748              :     @par Example
     749              :     @code
     750              :     std::stop_source source;
     751              :     co_await run(source.get_token())(cancellable_task());
     752              :     @endcode
     753              : 
     754              :     @param st The stop token for cooperative cancellation.
     755              : 
     756              :     @return A wrapper that accepts a task for execution.
     757              : */
     758              : [[nodiscard]] inline auto
     759            1 : run(std::stop_token st)
     760              : {
     761            1 :     return detail::run_wrapper<false, void>{std::move(st)};
     762              : }
     763              : 
     764              : /** Run a task with a custom memory resource.
     765              : 
     766              :     The task inherits the caller's executor. The memory resource
     767              :     is used for nested frame allocations.
     768              : 
     769              :     @param mr The memory resource for frame allocation.
     770              : 
     771              :     @return A wrapper that accepts a task for execution.
     772              : */
     773              : [[nodiscard]] inline auto
     774            2 : run(std::pmr::memory_resource* mr)
     775              : {
     776            2 :     return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
     777              : }
     778              : 
     779              : /** Run a task with a custom standard allocator.
     780              : 
     781              :     The task inherits the caller's executor. The allocator is used
     782              :     for nested frame allocations.
     783              : 
     784              :     @param alloc The allocator for frame allocation.
     785              : 
     786              :     @return A wrapper that accepts a task for execution.
     787              : */
     788              : template<detail::Allocator Alloc>
     789              : [[nodiscard]] auto
     790            1 : run(Alloc alloc)
     791              : {
     792            1 :     return detail::run_wrapper<true, Alloc>{std::move(alloc)};
     793              : }
     794              : 
     795              : /** Run a task with stop token and memory resource.
     796              : 
     797              :     The task inherits the caller's executor.
     798              : 
     799              :     @param st The stop token for cooperative cancellation.
     800              :     @param mr The memory resource for frame allocation.
     801              : 
     802              :     @return A wrapper that accepts a task for execution.
     803              : */
     804              : [[nodiscard]] inline auto
     805            1 : run(std::stop_token st, std::pmr::memory_resource* mr)
     806              : {
     807              :     return detail::run_wrapper<false, std::pmr::memory_resource*>{
     808            1 :         std::move(st), mr};
     809              : }
     810              : 
     811              : /** Run a task with stop token and standard allocator.
     812              : 
     813              :     The task inherits the caller's executor.
     814              : 
     815              :     @param st The stop token for cooperative cancellation.
     816              :     @param alloc The allocator for frame allocation.
     817              : 
     818              :     @return A wrapper that accepts a task for execution.
     819              : */
     820              : template<detail::Allocator Alloc>
     821              : [[nodiscard]] auto
     822            1 : run(std::stop_token st, Alloc alloc)
     823              : {
     824              :     return detail::run_wrapper<false, Alloc>{
     825            1 :         std::move(st), std::move(alloc)};
     826              : }
     827              : 
     828              : } // namespace boost::capy
     829              : 
     830              : #endif
        

Generated by: LCOV version 2.3