LCOV - code coverage report
Current view: top level - capy/ex - run.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 99.5 % 187 186 1
Test Date: 2026-03-09 22:47:34 Functions: 99.2 % 131 130 1

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

Generated by: LCOV version 2.3