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

Generated by: LCOV version 2.3