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

Generated by: LCOV version 2.3