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

Generated by: LCOV version 2.3