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

Generated by: LCOV version 2.3