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

Generated by: LCOV version 2.3