LCOV - code coverage report
Current view: top level - capy/test - run_blocking.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.4 % 76 74
Test Date: 2026-02-01 07:03:35 Functions: 86.0 % 186 160

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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_TEST_RUN_BLOCKING_HPP
      11              : #define BOOST_CAPY_TEST_RUN_BLOCKING_HPP
      12              : 
      13              : #include <boost/capy/coro.hpp>
      14              : #include <boost/capy/concept/execution_context.hpp>
      15              : #include <boost/capy/concept/executor.hpp>
      16              : #include <boost/capy/ex/run_async.hpp>
      17              : #include <boost/capy/ex/system_context.hpp>
      18              : 
      19              : #include <condition_variable>
      20              : #include <exception>
      21              : #include <mutex>
      22              : #include <stdexcept>
      23              : #include <stop_token>
      24              : #include <type_traits>
      25              : #include <utility>
      26              : 
      27              : namespace boost {
      28              : namespace capy {
      29              : namespace test {
      30              : 
      31              : struct inline_executor;
      32              : 
      33              : /** Execution context for inline blocking execution.
      34              : 
      35              :     This execution context is used with inline_executor for
      36              :     blocking synchronous execution. It satisfies the
      37              :     ExecutionContext concept requirements.
      38              : 
      39              :     @see inline_executor
      40              :     @see run_blocking
      41              : */
      42              : class inline_context : public execution_context
      43              : {
      44              : public:
      45              :     using executor_type = inline_executor;
      46              : 
      47           19 :     inline_context() = default;
      48              : 
      49              :     executor_type
      50              :     get_executor() noexcept;
      51              : };
      52              : 
      53              : /** Synchronous executor that executes inline and disallows posting.
      54              : 
      55              :     This executor executes work synchronously on the calling thread
      56              :     via `dispatch()`. Calling `post()` throws `std::logic_error`
      57              :     because posting implies deferred execution which is incompatible
      58              :     with blocking synchronous semantics.
      59              : 
      60              :     @par Thread Safety
      61              :     All member functions are thread-safe.
      62              : 
      63              :     @see run_blocking
      64              : */
      65              : struct inline_executor
      66              : {
      67              :     /// Compare two inline executors for equality.
      68              :     bool
      69            1 :     operator==(inline_executor const&) const noexcept
      70              :     {
      71            1 :         return true;
      72              :     }
      73              : 
      74              :     /** Return the associated execution context.
      75              : 
      76              :         @return A reference to a function-local static `inline_context`.
      77              :     */
      78              :     inline_context&
      79         1541 :     context() const noexcept
      80              :     {
      81         1541 :         static inline_context ctx;
      82         1541 :         return ctx;
      83              :     }
      84              : 
      85              :     /// Called when work is submitted (no-op).
      86            0 :     void on_work_started() const noexcept {}
      87              : 
      88              :     /// Called when work completes (no-op).
      89            0 :     void on_work_finished() const noexcept {}
      90              : 
      91              :     /** Dispatch work for immediate inline execution.
      92              : 
      93              :         @param h The coroutine handle to execute.
      94              : 
      95              :         @return The same handle for symmetric transfer.
      96              :     */
      97              :     coro
      98         1542 :     dispatch(coro h) const
      99              :     {
     100         1542 :         return h;
     101              :     }
     102              : 
     103              :     /** Post work for deferred execution.
     104              : 
     105              :         @par Exception Safety
     106              :         Always throws.
     107              : 
     108              :         @throws std::logic_error Always, because posting is not
     109              :             supported in a blocking context.
     110              : 
     111              :         @param h The coroutine handle (unused).
     112              :     */
     113              :     [[noreturn]] void
     114            1 :     post(coro) const
     115              :     {
     116              :         throw std::logic_error(
     117            1 :             "post not supported in blocking context");
     118              :     }
     119              : };
     120              : 
     121              : inline inline_context::executor_type
     122              : inline_context::get_executor() noexcept
     123              : {
     124              :     return inline_executor{};
     125              : }
     126              : 
     127              : static_assert(Executor<inline_executor>);
     128              : static_assert(ExecutionContext<inline_context>);
     129              : 
     130              : //----------------------------------------------------------
     131              : //
     132              : // blocking_state
     133              : //
     134              : //----------------------------------------------------------
     135              : 
     136              : /** Synchronization state for blocking execution.
     137              : 
     138              :     Holds the mutex, condition variable, and completion flag
     139              :     used to block the caller until the task completes.
     140              : 
     141              :     @par Thread Safety
     142              :     Thread-safe when accessed under the mutex.
     143              : 
     144              :     @see run_blocking
     145              :     @see blocking_handler_wrapper
     146              : */
     147              : struct blocking_state
     148              : {
     149              :     std::mutex mtx;
     150              :     std::condition_variable cv;
     151              :     bool done = false;
     152              :     std::exception_ptr ep;
     153              : };
     154              : 
     155              : //----------------------------------------------------------
     156              : //
     157              : // blocking_handler_wrapper
     158              : //
     159              : //----------------------------------------------------------
     160              : 
     161              : /** Wrapper that signals completion after invoking the underlying handler_pair.
     162              : 
     163              :     This wrapper forwards invocations to the contained handler_pair,
     164              :     then signals the `blocking_state` condition variable so
     165              :     that `run_blocking` can unblock. Any exceptions thrown by the
     166              :     handler are captured and stored for later rethrow.
     167              : 
     168              :     @tparam H1 The success handler type.
     169              :     @tparam H2 The error handler type.
     170              : 
     171              :     @par Thread Safety
     172              :     Safe to invoke from any thread. Signals the condition
     173              :     variable after calling the handler.
     174              : 
     175              :     @see run_blocking
     176              :     @see blocking_state
     177              : */
     178              : template<class H1, class H2>
     179              : struct blocking_handler_wrapper
     180              : {
     181              :     blocking_state* state_;
     182              :     detail::handler_pair<H1, H2> handlers_;
     183              : 
     184              :     /** Invoke the handler with non-void result and signal completion. */
     185              :     template<class T>
     186           21 :     void operator()(T&& v)
     187              :     {
     188              :         try
     189              :         {
     190           21 :             handlers_(std::forward<T>(v));
     191              :         }
     192              :         catch(...)
     193              :         {
     194              :             std::lock_guard<std::mutex> lock(state_->mtx);
     195              :             state_->ep = std::current_exception();
     196              :             state_->done = true;
     197              :             state_->cv.notify_one();
     198              :             return;
     199              :         }
     200              :         {
     201           21 :             std::lock_guard<std::mutex> lock(state_->mtx);
     202           21 :             state_->done = true;
     203           21 :         }
     204           21 :         state_->cv.notify_one();
     205              :     }
     206              : 
     207              :     /** Invoke the handler for void result and signal completion. */
     208          919 :     void operator()()
     209              :     {
     210              :         try
     211              :         {
     212          919 :             handlers_();
     213              :         }
     214              :         catch(...)
     215              :         {
     216              :             std::lock_guard<std::mutex> lock(state_->mtx);
     217              :             state_->ep = std::current_exception();
     218              :             state_->done = true;
     219              :             state_->cv.notify_one();
     220              :             return;
     221              :         }
     222              :         {
     223          919 :             std::lock_guard<std::mutex> lock(state_->mtx);
     224          919 :             state_->done = true;
     225          919 :         }
     226          919 :         state_->cv.notify_one();
     227              :     }
     228              : 
     229              :     /** Invoke the handler with exception and signal completion. */
     230          602 :     void operator()(std::exception_ptr ep)
     231              :     {
     232              :         try
     233              :         {
     234         1201 :             handlers_(ep);
     235              :         }
     236         1198 :         catch(...)
     237              :         {
     238          599 :             std::lock_guard<std::mutex> lock(state_->mtx);
     239          599 :             state_->ep = std::current_exception();
     240          599 :             state_->done = true;
     241          599 :             state_->cv.notify_one();
     242          599 :             return;
     243          599 :         }
     244              :         {
     245            3 :             std::lock_guard<std::mutex> lock(state_->mtx);
     246            3 :             state_->done = true;
     247            3 :         }
     248            3 :         state_->cv.notify_one();
     249              :     }
     250              : };
     251              : 
     252              : //----------------------------------------------------------
     253              : //
     254              : // run_blocking_wrapper
     255              : //
     256              : //----------------------------------------------------------
     257              : 
     258              : /** Wrapper returned by run_blocking that accepts a task for execution.
     259              : 
     260              :     This wrapper holds the blocking state and handlers. When invoked
     261              :     with a task, it launches the task via `run_async` and blocks
     262              :     until the task completes.
     263              : 
     264              :     The rvalue ref-qualifier on `operator()` ensures the wrapper
     265              :     can only be used as a temporary.
     266              : 
     267              :     @tparam Ex The executor type satisfying the `Executor` concept.
     268              :     @tparam H1 The success handler type.
     269              :     @tparam H2 The error handler type.
     270              : 
     271              :     @par Thread Safety
     272              :     The wrapper itself should only be used from one thread.
     273              :     The calling thread blocks until the task completes.
     274              : 
     275              :     @par Example
     276              :     @code
     277              :     // Block until task completes
     278              :     int result = 0;
     279              :     run_blocking([&](int v) { result = v; })(my_task());
     280              :     @endcode
     281              : 
     282              :     @see run_blocking
     283              :     @see run_async
     284              : */
     285              : template<Executor Ex, class H1, class H2>
     286              : class [[nodiscard]] run_blocking_wrapper
     287              : {
     288              :     Ex ex_;
     289              :     std::stop_token st_;
     290              :     H1 h1_;
     291              :     H2 h2_;
     292              : 
     293              : public:
     294              :     /** Construct wrapper with executor, stop token, and handlers.
     295              : 
     296              :         @param ex The executor to execute the task on.
     297              :         @param st The stop token for cooperative cancellation.
     298              :         @param h1 The success handler.
     299              :         @param h2 The error handler.
     300              :     */
     301         1542 :     run_blocking_wrapper(
     302              :         Ex ex,
     303              :         std::stop_token st,
     304              :         H1 h1,
     305              :         H2 h2)
     306         1542 :         : ex_(std::move(ex))
     307         1542 :         , st_(std::move(st))
     308         1542 :         , h1_(std::move(h1))
     309         1542 :         , h2_(std::move(h2))
     310              :     {
     311         1542 :     }
     312              : 
     313              :     run_blocking_wrapper(run_blocking_wrapper const&) = delete;
     314              :     run_blocking_wrapper(run_blocking_wrapper&&) = delete;
     315              :     run_blocking_wrapper& operator=(run_blocking_wrapper const&) = delete;
     316              :     run_blocking_wrapper& operator=(run_blocking_wrapper&&) = delete;
     317              : 
     318              :     /** Launch the task and block until completion.
     319              : 
     320              :         This operator accepts a task, launches it via `run_async`
     321              :         with wrapped handlers, and blocks until the task completes.
     322              : 
     323              :         @tparam Task The IoLaunchableTask type.
     324              : 
     325              :         @param t The task to execute.
     326              :     */
     327              :     template<IoLaunchableTask Task>
     328              :     void
     329         1542 :     operator()(Task t) &&
     330              :     {
     331         1542 :         blocking_state state;
     332              : 
     333         3084 :         auto make_handlers = [&]() {
     334              :             if constexpr(std::is_same_v<H2, detail::default_handler>)
     335         1539 :                 return detail::handler_pair<H1, H2>{std::move(h1_)};
     336              :             else
     337            3 :                 return detail::handler_pair<H1, H2>{std::move(h1_), std::move(h2_)};
     338              :         };
     339              : 
     340              :         run_async(
     341              :             ex_,
     342         1542 :             st_,
     343         1542 :             blocking_handler_wrapper<H1, H2>{&state, make_handlers()}
     344         1542 :         )(std::move(t));
     345              : 
     346         1542 :         std::unique_lock<std::mutex> lock(state.mtx);
     347         3084 :         state.cv.wait(lock, [&] { return state.done; });
     348         1542 :         if(state.ep)
     349         1198 :             std::rethrow_exception(state.ep);
     350         2141 :     }
     351              : };
     352              : 
     353              : //----------------------------------------------------------
     354              : //
     355              : // run_blocking Overloads
     356              : //
     357              : //----------------------------------------------------------
     358              : 
     359              : // With inline_executor (default)
     360              : 
     361              : /** Block until task completes and discard result.
     362              : 
     363              :     Executes a lazy task using the inline executor and blocks the
     364              :     calling thread until the task completes or throws.
     365              : 
     366              :     @par Thread Safety
     367              :     The calling thread is blocked. The task executes inline
     368              :     on the calling thread.
     369              : 
     370              :     @par Exception Safety
     371              :     Basic guarantee. If the task throws, the exception is
     372              :     rethrown to the caller.
     373              : 
     374              :     @par Example
     375              :     @code
     376              :     run_blocking()(my_void_task());
     377              :     @endcode
     378              : 
     379              :     @return A wrapper that accepts a task for blocking execution.
     380              : 
     381              :     @see run_async
     382              : */
     383              : [[nodiscard]] inline auto
     384         1518 : run_blocking()
     385              : {
     386              :     return run_blocking_wrapper<
     387              :         inline_executor,
     388              :         detail::default_handler,
     389              :         detail::default_handler>(
     390              :             inline_executor{},
     391         3036 :             std::stop_token{},
     392              :             detail::default_handler{},
     393         1518 :             detail::default_handler{});
     394              : }
     395              : 
     396              : /** Block until task completes and invoke handler with result.
     397              : 
     398              :     Executes a lazy task using the inline executor and blocks until
     399              :     completion. The handler `h1` is called with the result on success.
     400              :     If `h1` is also invocable with `std::exception_ptr`, it handles
     401              :     exceptions too. Otherwise, exceptions are rethrown.
     402              : 
     403              :     @par Thread Safety
     404              :     The calling thread is blocked. The task and handler execute
     405              :     inline on the calling thread.
     406              : 
     407              :     @par Exception Safety
     408              :     Basic guarantee. Exceptions from the task are passed to `h1`
     409              :     if it accepts `std::exception_ptr`, otherwise rethrown.
     410              : 
     411              :     @par Example
     412              :     @code
     413              :     int result = 0;
     414              :     run_blocking([&](int v) { result = v; })(compute_value());
     415              :     @endcode
     416              : 
     417              :     @param h1 Handler invoked with the result on success, and
     418              :         optionally with `std::exception_ptr` on failure.
     419              : 
     420              :     @return A wrapper that accepts a task for blocking execution.
     421              : 
     422              :     @see run_async
     423              : */
     424              : template<class H1>
     425              : [[nodiscard]] auto
     426           18 : run_blocking(H1 h1)
     427              : {
     428              :     return run_blocking_wrapper<
     429              :         inline_executor,
     430              :         H1,
     431              :         detail::default_handler>(
     432              :             inline_executor{},
     433           36 :             std::stop_token{},
     434           18 :             std::move(h1),
     435           18 :             detail::default_handler{});
     436              : }
     437              : 
     438              : /** Block until task completes with separate handlers.
     439              : 
     440              :     Executes a lazy task using the inline executor and blocks until
     441              :     completion. The handler `h1` is called on success, `h2` on failure.
     442              : 
     443              :     @par Thread Safety
     444              :     The calling thread is blocked. The task and handlers execute
     445              :     inline on the calling thread.
     446              : 
     447              :     @par Exception Safety
     448              :     Basic guarantee. Exceptions from the task are passed to `h2`.
     449              : 
     450              :     @par Example
     451              :     @code
     452              :     int result = 0;
     453              :     run_blocking(
     454              :         [&](int v) { result = v; },
     455              :         [](std::exception_ptr ep) {
     456              :             std::rethrow_exception(ep);
     457              :         }
     458              :     )(compute_value());
     459              :     @endcode
     460              : 
     461              :     @param h1 Handler invoked with the result on success.
     462              :     @param h2 Handler invoked with the exception on failure.
     463              : 
     464              :     @return A wrapper that accepts a task for blocking execution.
     465              : 
     466              :     @see run_async
     467              : */
     468              : template<class H1, class H2>
     469              : [[nodiscard]] auto
     470            3 : run_blocking(H1 h1, H2 h2)
     471              : {
     472              :     return run_blocking_wrapper<
     473              :         inline_executor,
     474              :         H1,
     475              :         H2>(
     476              :             inline_executor{},
     477            6 :             std::stop_token{},
     478            3 :             std::move(h1),
     479            6 :             std::move(h2));
     480              : }
     481              : 
     482              : // With explicit executor
     483              : 
     484              : /** Block until task completes on the given executor.
     485              : 
     486              :     Executes a lazy task on the specified executor and blocks the
     487              :     calling thread until the task completes.
     488              : 
     489              :     @par Thread Safety
     490              :     The calling thread is blocked. The task may execute on
     491              :     a different thread depending on the executor.
     492              : 
     493              :     @par Exception Safety
     494              :     Basic guarantee. If the task throws, the exception is
     495              :     rethrown to the caller.
     496              : 
     497              :     @par Example
     498              :     @code
     499              :     run_blocking(my_executor)(my_void_task());
     500              :     @endcode
     501              : 
     502              :     @param ex The executor to execute the task on.
     503              : 
     504              :     @return A wrapper that accepts a task for blocking execution.
     505              : 
     506              :     @see run_async
     507              : */
     508              : template<Executor Ex>
     509              : [[nodiscard]] auto
     510              : run_blocking(Ex ex)
     511              : {
     512              :     return run_blocking_wrapper<
     513              :         Ex,
     514              :         detail::default_handler,
     515              :         detail::default_handler>(
     516              :             std::move(ex),
     517              :             std::stop_token{},
     518              :             detail::default_handler{},
     519              :             detail::default_handler{});
     520              : }
     521              : 
     522              : /** Block until task completes on executor with handler.
     523              : 
     524              :     Executes a lazy task on the specified executor and blocks until
     525              :     completion. The handler `h1` is called with the result.
     526              : 
     527              :     @par Thread Safety
     528              :     The calling thread is blocked. The task and handler may
     529              :     execute on a different thread depending on the executor.
     530              : 
     531              :     @par Exception Safety
     532              :     Basic guarantee. Exceptions from the task are passed to `h1`
     533              :     if it accepts `std::exception_ptr`, otherwise rethrown.
     534              : 
     535              :     @param ex The executor to execute the task on.
     536              :     @param h1 Handler invoked with the result on success.
     537              : 
     538              :     @return A wrapper that accepts a task for blocking execution.
     539              : 
     540              :     @see run_async
     541              : */
     542              : template<Executor Ex, class H1>
     543              : [[nodiscard]] auto
     544            1 : run_blocking(Ex ex, H1 h1)
     545              : {
     546              :     return run_blocking_wrapper<
     547              :         Ex,
     548              :         H1,
     549              :         detail::default_handler>(
     550            1 :             std::move(ex),
     551            2 :             std::stop_token{},
     552            1 :             std::move(h1),
     553            1 :             detail::default_handler{});
     554              : }
     555              : 
     556              : /** Block until task completes on executor with separate handlers.
     557              : 
     558              :     Executes a lazy task on the specified executor and blocks until
     559              :     completion. The handler `h1` is called on success, `h2` on failure.
     560              : 
     561              :     @par Thread Safety
     562              :     The calling thread is blocked. The task and handlers may
     563              :     execute on a different thread depending on the executor.
     564              : 
     565              :     @par Exception Safety
     566              :     Basic guarantee. Exceptions from the task are passed to `h2`.
     567              : 
     568              :     @param ex The executor to execute the task on.
     569              :     @param h1 Handler invoked with the result on success.
     570              :     @param h2 Handler invoked with the exception on failure.
     571              : 
     572              :     @return A wrapper that accepts a task for blocking execution.
     573              : 
     574              :     @see run_async
     575              : */
     576              : template<Executor Ex, class H1, class H2>
     577              : [[nodiscard]] auto
     578              : run_blocking(Ex ex, H1 h1, H2 h2)
     579              : {
     580              :     return run_blocking_wrapper<
     581              :         Ex,
     582              :         H1,
     583              :         H2>(
     584              :             std::move(ex),
     585              :             std::stop_token{},
     586              :             std::move(h1),
     587              :             std::move(h2));
     588              : }
     589              : 
     590              : // With stop_token
     591              : 
     592              : /** Block until task completes with stop token support.
     593              : 
     594              :     Executes a lazy task using the inline executor with the given
     595              :     stop token and blocks until completion.
     596              : 
     597              :     @par Thread Safety
     598              :     The calling thread is blocked. The task executes inline
     599              :     on the calling thread.
     600              : 
     601              :     @par Exception Safety
     602              :     Basic guarantee. If the task throws, the exception is
     603              :     rethrown to the caller.
     604              : 
     605              :     @param st The stop token for cooperative cancellation.
     606              : 
     607              :     @return A wrapper that accepts a task for blocking execution.
     608              : 
     609              :     @see run_async
     610              : */
     611              : [[nodiscard]] inline auto
     612              : run_blocking(std::stop_token st)
     613              : {
     614              :     return run_blocking_wrapper<
     615              :         inline_executor,
     616              :         detail::default_handler,
     617              :         detail::default_handler>(
     618              :             inline_executor{},
     619              :             std::move(st),
     620              :             detail::default_handler{},
     621              :             detail::default_handler{});
     622              : }
     623              : 
     624              : /** Block until task completes with stop token and handler.
     625              : 
     626              :     @param st The stop token for cooperative cancellation.
     627              :     @param h1 Handler invoked with the result on success.
     628              : 
     629              :     @return A wrapper that accepts a task for blocking execution.
     630              : 
     631              :     @see run_async
     632              : */
     633              : template<class H1>
     634              : [[nodiscard]] auto
     635            2 : run_blocking(std::stop_token st, H1 h1)
     636              : {
     637              :     return run_blocking_wrapper<
     638              :         inline_executor,
     639              :         H1,
     640              :         detail::default_handler>(
     641              :             inline_executor{},
     642            2 :             std::move(st),
     643            2 :             std::move(h1),
     644            2 :             detail::default_handler{});
     645              : }
     646              : 
     647              : /** Block until task completes with stop token and separate handlers.
     648              : 
     649              :     @param st The stop token for cooperative cancellation.
     650              :     @param h1 Handler invoked with the result on success.
     651              :     @param h2 Handler invoked with the exception on failure.
     652              : 
     653              :     @return A wrapper that accepts a task for blocking execution.
     654              : 
     655              :     @see run_async
     656              : */
     657              : template<class H1, class H2>
     658              : [[nodiscard]] auto
     659              : run_blocking(std::stop_token st, H1 h1, H2 h2)
     660              : {
     661              :     return run_blocking_wrapper<
     662              :         inline_executor,
     663              :         H1,
     664              :         H2>(
     665              :             inline_executor{},
     666              :             std::move(st),
     667              :             std::move(h1),
     668              :             std::move(h2));
     669              : }
     670              : 
     671              : // Executor + stop_token
     672              : 
     673              : /** Block until task completes on executor with stop token.
     674              : 
     675              :     @param ex The executor to execute the task on.
     676              :     @param st The stop token for cooperative cancellation.
     677              : 
     678              :     @return A wrapper that accepts a task for blocking execution.
     679              : 
     680              :     @see run_async
     681              : */
     682              : template<Executor Ex>
     683              : [[nodiscard]] auto
     684              : run_blocking(Ex ex, std::stop_token st)
     685              : {
     686              :     return run_blocking_wrapper<
     687              :         Ex,
     688              :         detail::default_handler,
     689              :         detail::default_handler>(
     690              :             std::move(ex),
     691              :             std::move(st),
     692              :             detail::default_handler{},
     693              :             detail::default_handler{});
     694              : }
     695              : 
     696              : /** Block until task completes on executor with stop token and handler.
     697              : 
     698              :     @param ex The executor to execute the task on.
     699              :     @param st The stop token for cooperative cancellation.
     700              :     @param h1 Handler invoked with the result on success.
     701              : 
     702              :     @return A wrapper that accepts a task for blocking execution.
     703              : 
     704              :     @see run_async
     705              : */
     706              : template<Executor Ex, class H1>
     707              : [[nodiscard]] auto
     708              : run_blocking(Ex ex, std::stop_token st, H1 h1)
     709              : {
     710              :     return run_blocking_wrapper<
     711              :         Ex,
     712              :         H1,
     713              :         detail::default_handler>(
     714              :             std::move(ex),
     715              :             std::move(st),
     716              :             std::move(h1),
     717              :             detail::default_handler{});
     718              : }
     719              : 
     720              : /** Block until task completes on executor with stop token and handlers.
     721              : 
     722              :     @param ex The executor to execute the task on.
     723              :     @param st The stop token for cooperative cancellation.
     724              :     @param h1 Handler invoked with the result on success.
     725              :     @param h2 Handler invoked with the exception on failure.
     726              : 
     727              :     @return A wrapper that accepts a task for blocking execution.
     728              : 
     729              :     @see run_async
     730              : */
     731              : template<Executor Ex, class H1, class H2>
     732              : [[nodiscard]] auto
     733              : run_blocking(Ex ex, std::stop_token st, H1 h1, H2 h2)
     734              : {
     735              :     return run_blocking_wrapper<
     736              :         Ex,
     737              :         H1,
     738              :         H2>(
     739              :             std::move(ex),
     740              :             std::move(st),
     741              :             std::move(h1),
     742              :             std::move(h2));
     743              : }
     744              : 
     745              : } // namespace test
     746              : } // namespace capy
     747              : } // namespace boost
     748              : 
     749              : #endif
        

Generated by: LCOV version 2.3