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

Generated by: LCOV version 2.3