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

Generated by: LCOV version 2.3