LCOV - code coverage report
Current view: top level - capy/test - run_blocking.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 45 45
Test Date: 2026-02-12 16:53:28 Functions: 84.4 % 154 130

            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_TEST_RUN_BLOCKING_HPP
      11              : #define BOOST_CAPY_TEST_RUN_BLOCKING_HPP
      12              : 
      13              : #include <boost/capy/detail/config.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              : 
      18              : #include <coroutine>
      19              : #include <exception>
      20              : #include <stop_token>
      21              : #include <type_traits>
      22              : #include <utility>
      23              : 
      24              : namespace boost {
      25              : namespace capy {
      26              : namespace test {
      27              : 
      28              : class blocking_context;
      29              : 
      30              : /** Single-threaded executor for blocking synchronous tests.
      31              : 
      32              :     This executor is used internally by @ref run_blocking to
      33              :     execute coroutine tasks on the calling thread. Work submitted
      34              :     via `dispatch()` is returned for symmetric transfer. Work
      35              :     submitted via `post()` is enqueued and processed by the
      36              :     @ref blocking_context event loop.
      37              : 
      38              :     Users do not construct this type directly. It is obtained
      39              :     from @ref blocking_context::get_executor.
      40              : 
      41              :     @par Thread Safety
      42              :     All member functions are safe to call from any thread.
      43              : 
      44              :     @see blocking_context, run_blocking
      45              : */
      46              : struct BOOST_CAPY_DECL blocking_executor
      47              : {
      48              :     /// Construct from a context pointer.
      49         1876 :     explicit blocking_executor(
      50              :         blocking_context* ctx) noexcept
      51         1876 :         : ctx_(ctx)
      52              :     {
      53         1876 :     }
      54              : 
      55              :     /** Compare two blocking executors for equality.
      56              : 
      57              :         Two executors are equal if they share the same context.
      58              :     */
      59              :     bool
      60              :     operator==(blocking_executor const& other) const noexcept;
      61              : 
      62              :     /** Return the associated execution context.
      63              : 
      64              :         @return A reference to the owning `blocking_context`.
      65              :     */
      66              :     blocking_context&
      67              :     context() const noexcept;
      68              : 
      69              :     /// Called when work is submitted (no-op).
      70              :     void on_work_started() const noexcept;
      71              : 
      72              :     /// Called when work completes (no-op).
      73              :     void on_work_finished() const noexcept;
      74              : 
      75              :     /** Dispatch work for immediate inline execution.
      76              : 
      77              :         Returns the handle for symmetric transfer. The caller
      78              :         resumes the coroutine via the returned handle.
      79              : 
      80              :         @param h The coroutine handle to execute.
      81              : 
      82              :         @return `h` for symmetric transfer.
      83              :     */
      84              :     std::coroutine_handle<>
      85              :     dispatch(std::coroutine_handle<> h) const;
      86              : 
      87              :     /** Post work for deferred execution.
      88              : 
      89              :         Enqueues the coroutine handle into the context's work
      90              :         queue. The handle is resumed when the blocking event
      91              :         loop processes it.
      92              : 
      93              :         @param h The coroutine handle to enqueue.
      94              :     */
      95              :     void
      96              :     post(std::coroutine_handle<> h) const;
      97              : 
      98              : private:
      99              :     blocking_context* ctx_;
     100              : };
     101              : 
     102              : /** Single-threaded execution context for blocking tests.
     103              : 
     104              :     Provides a work queue and event loop that runs on the
     105              :     calling thread. Coroutines dispatched through the
     106              :     associated @ref blocking_executor have their `post()`
     107              :     calls enqueued and processed by @ref run, which blocks
     108              :     until @ref signal_done is called.
     109              : 
     110              :     This context is created internally by @ref run_blocking.
     111              :     Users do not interact with it directly.
     112              : 
     113              :     @par Thread Safety
     114              :     The event loop runs on the thread that calls `run()`.
     115              :     `signal_done()` and `enqueue()` are safe to call from
     116              :     any thread.
     117              : 
     118              :     @see blocking_executor, run_blocking
     119              : */
     120              : class BOOST_CAPY_DECL blocking_context
     121              :     : public execution_context
     122              : {
     123              :     struct impl;
     124              :     impl* impl_;
     125              : 
     126              : public:
     127              :     using executor_type = blocking_executor;
     128              : 
     129              :     /** Construct a blocking context.
     130              : 
     131              :         Allocates the internal work queue and
     132              :         synchronization state.
     133              :     */
     134              :     blocking_context();
     135              : 
     136              :     /** Destroy the blocking context. */
     137              :     ~blocking_context();
     138              : 
     139              :     /** Return an executor bound to this context.
     140              : 
     141              :         @return A `blocking_executor` that enqueues work
     142              :             into this context's queue.
     143              :     */
     144              :     blocking_executor
     145              :     get_executor() noexcept;
     146              : 
     147              :     /** Signal that the task has completed.
     148              : 
     149              :         Wakes the event loop so that @ref run returns.
     150              :     */
     151              :     void
     152              :     signal_done() noexcept;
     153              : 
     154              :     /** Signal that the task has completed with an error.
     155              : 
     156              :         Stores the exception and wakes the event loop
     157              :         so that @ref run rethrows it.
     158              : 
     159              :         @param ep The exception to propagate.
     160              :     */
     161              :     void
     162              :     signal_done(std::exception_ptr ep) noexcept;
     163              : 
     164              :     /** Run the event loop until done.
     165              : 
     166              :         Blocks the calling thread, processing posted
     167              :         coroutine handles until @ref signal_done is called.
     168              :         After draining remaining work, rethrows any stored
     169              :         exception.
     170              : 
     171              :         @par Exception Safety
     172              :         Basic guarantee. If the completed task stored an
     173              :         exception via `signal_done(ep)`, it is rethrown.
     174              :     */
     175              :     void
     176              :     run();
     177              : 
     178              :     /** Enqueue a coroutine handle for processing.
     179              : 
     180              :         @param h The coroutine handle to enqueue.
     181              :     */
     182              :     void
     183              :     enqueue(std::coroutine_handle<> h);
     184              : };
     185              : 
     186              : /** Wrapper that signals completion after invoking the handler.
     187              : 
     188              :     Forwards invocations to the contained handler_pair, then
     189              :     signals the `blocking_context` so that its event loop
     190              :     unblocks. Exceptions thrown by the handler are captured
     191              :     and stored for later rethrow.
     192              : 
     193              :     @tparam H1 The success handler type.
     194              :     @tparam H2 The error handler type.
     195              : 
     196              :     @par Thread Safety
     197              :     Safe to invoke from any thread.
     198              : 
     199              :     @see run_blocking, blocking_context
     200              : */
     201              : template<class H1, class H2>
     202              : struct blocking_handler_wrapper
     203              : {
     204              :     blocking_context* ctx_;
     205              :     detail::handler_pair<H1, H2> handlers_;
     206              : 
     207              :     /** Invoke the handler with a non-void result. */
     208              :     template<class T>
     209           21 :     void operator()(T&& v)
     210              :     {
     211              :         try
     212              :         {
     213           21 :             handlers_(std::forward<T>(v));
     214              :         }
     215              :         catch(...)
     216              :         {
     217              :             ctx_->signal_done(std::current_exception());
     218              :             return;
     219              :         }
     220           21 :         ctx_->signal_done();
     221              :     }
     222              : 
     223              :     /** Invoke the handler for a void result. */
     224         1152 :     void operator()()
     225              :     {
     226              :         try
     227              :         {
     228         1152 :             handlers_();
     229              :         }
     230              :         catch(...)
     231              :         {
     232              :             ctx_->signal_done(std::current_exception());
     233              :             return;
     234              :         }
     235         1152 :         ctx_->signal_done();
     236              :     }
     237              : 
     238              :     /** Invoke the handler with an exception. */
     239          695 :     void operator()(std::exception_ptr ep)
     240              :     {
     241              :         try
     242              :         {
     243         1387 :             handlers_(ep);
     244              :         }
     245          692 :         catch(...)
     246              :         {
     247          692 :             ctx_->signal_done(std::current_exception());
     248          692 :             return;
     249              :         }
     250            3 :         ctx_->signal_done();
     251              :     }
     252              : };
     253              : 
     254              : /** Wrapper returned by run_blocking that accepts a task.
     255              : 
     256              :     Holds the handlers and optional stop token. When invoked
     257              :     with a task, creates a @ref blocking_context, launches
     258              :     the task via `run_async`, and pumps the event loop until
     259              :     the task completes.
     260              : 
     261              :     The rvalue ref-qualifier on `operator()` ensures the
     262              :     wrapper can only be used as a temporary.
     263              : 
     264              :     @tparam H1 The success handler type.
     265              :     @tparam H2 The error handler type.
     266              : 
     267              :     @par Thread Safety
     268              :     The wrapper itself should only be used from one thread.
     269              :     The calling thread blocks until the task completes.
     270              : 
     271              :     @par Example
     272              :     @code
     273              :     int result = 0;
     274              :     run_blocking([&](int v) { result = v; })(my_task());
     275              :     @endcode
     276              : 
     277              :     @see run_blocking, run_async
     278              : */
     279              : template<class H1, class H2>
     280              : class [[nodiscard]] run_blocking_wrapper
     281              : {
     282              :     std::stop_token st_;
     283              :     H1 h1_;
     284              :     H2 h2_;
     285              : 
     286              : public:
     287              :     /** Construct wrapper with stop token and handlers.
     288              : 
     289              :         @param st The stop token for cooperative cancellation.
     290              :         @param h1 The success handler.
     291              :         @param h2 The error handler.
     292              :     */
     293         1868 :     run_blocking_wrapper(
     294              :         std::stop_token st,
     295              :         H1 h1,
     296              :         H2 h2)
     297         1868 :         : st_(std::move(st))
     298         1868 :         , h1_(std::move(h1))
     299         1868 :         , h2_(std::move(h2))
     300              :     {
     301         1868 :     }
     302              : 
     303              :     run_blocking_wrapper(run_blocking_wrapper const&) = delete;
     304              :     run_blocking_wrapper(run_blocking_wrapper&&) = delete;
     305              :     run_blocking_wrapper& operator=(run_blocking_wrapper const&) = delete;
     306              :     run_blocking_wrapper& operator=(run_blocking_wrapper&&) = delete;
     307              : 
     308              :     /** Launch the task and block until completion.
     309              : 
     310              :         Creates a blocking_context with a single-threaded
     311              :         event loop, launches the task via `run_async`, then
     312              :         pumps the loop until the task completes or throws.
     313              : 
     314              :         @tparam Task The IoRunnable type.
     315              : 
     316              :         @param t The task to execute.
     317              :     */
     318              :     template<IoRunnable Task>
     319              :     void
     320         1868 :     operator()(Task t) &&
     321              :     {
     322         1868 :         blocking_context ctx;
     323              : 
     324         3736 :         auto make_handlers = [&]() {
     325              :             if constexpr(
     326              :                 std::is_same_v<H2, detail::default_handler>)
     327              :                 return detail::handler_pair<H1, H2>{
     328         1865 :                     std::move(h1_)};
     329              :             else
     330              :                 return detail::handler_pair<H1, H2>{
     331            3 :                     std::move(h1_), std::move(h2_)};
     332              :         };
     333              : 
     334              :         run_async(
     335              :             ctx.get_executor(),
     336         1868 :             st_,
     337              :             blocking_handler_wrapper<H1, H2>{
     338         1868 :                 &ctx, make_handlers()}
     339         1868 :         )(std::move(t));
     340              : 
     341         1868 :         ctx.run();
     342         1868 :     }
     343              : };
     344              : 
     345              : /** Block until task completes and discard result.
     346              : 
     347              :     Executes a lazy task on a single-threaded event loop
     348              :     and blocks the calling thread until the task completes
     349              :     or throws.
     350              : 
     351              :     @par Exception Safety
     352              :     Basic guarantee. If the task throws, the exception is
     353              :     rethrown to the caller.
     354              : 
     355              :     @par Example
     356              :     @code
     357              :     run_blocking()(my_void_task());
     358              :     @endcode
     359              : 
     360              :     @return A wrapper that accepts a task for blocking execution.
     361              : 
     362              :     @see run_async
     363              : */
     364              : [[nodiscard]] inline auto
     365         1844 : run_blocking()
     366              : {
     367              :     return run_blocking_wrapper<
     368              :         detail::default_handler,
     369              :         detail::default_handler>(
     370         3688 :             std::stop_token{},
     371              :             detail::default_handler{},
     372         1844 :             detail::default_handler{});
     373              : }
     374              : 
     375              : /** Block until task completes and invoke handler with result.
     376              : 
     377              :     Executes a lazy task on a single-threaded event loop
     378              :     and blocks until completion. The handler `h1` is called
     379              :     with the result on success. If `h1` is also invocable
     380              :     with `std::exception_ptr`, it handles exceptions too.
     381              :     Otherwise, exceptions are rethrown.
     382              : 
     383              :     @par Exception Safety
     384              :     Basic guarantee. Exceptions from the task are passed
     385              :     to `h1` if it accepts `std::exception_ptr`, otherwise
     386              :     rethrown.
     387              : 
     388              :     @par Example
     389              :     @code
     390              :     int result = 0;
     391              :     run_blocking([&](int v) { result = v; })(compute());
     392              :     @endcode
     393              : 
     394              :     @param h1 Handler invoked with the result on success,
     395              :         and optionally with `std::exception_ptr` on failure.
     396              : 
     397              :     @return A wrapper that accepts a task for blocking execution.
     398              : 
     399              :     @see run_async
     400              : */
     401              : template<class H1>
     402              : [[nodiscard]] auto
     403           19 : run_blocking(H1 h1)
     404              : {
     405              :     return run_blocking_wrapper<
     406              :         H1,
     407              :         detail::default_handler>(
     408           38 :             std::stop_token{},
     409           19 :             std::move(h1),
     410           19 :             detail::default_handler{});
     411              : }
     412              : 
     413              : /** Block until task completes with separate handlers.
     414              : 
     415              :     Executes a lazy task on a single-threaded event loop
     416              :     and blocks until completion. The handler `h1` is called
     417              :     on success, `h2` on failure.
     418              : 
     419              :     @par Exception Safety
     420              :     Basic guarantee. Exceptions from the task are passed
     421              :     to `h2`.
     422              : 
     423              :     @par Example
     424              :     @code
     425              :     int result = 0;
     426              :     run_blocking(
     427              :         [&](int v) { result = v; },
     428              :         [](std::exception_ptr ep) {
     429              :             std::rethrow_exception(ep);
     430              :         }
     431              :     )(compute());
     432              :     @endcode
     433              : 
     434              :     @param h1 Handler invoked with the result on success.
     435              :     @param h2 Handler invoked with the exception on failure.
     436              : 
     437              :     @return A wrapper that accepts a task for blocking execution.
     438              : 
     439              :     @see run_async
     440              : */
     441              : template<class H1, class H2>
     442              : [[nodiscard]] auto
     443            3 : run_blocking(H1 h1, H2 h2)
     444              : {
     445              :     return run_blocking_wrapper<
     446              :         H1,
     447              :         H2>(
     448            6 :             std::stop_token{},
     449            3 :             std::move(h1),
     450            6 :             std::move(h2));
     451              : }
     452              : 
     453              : /** Block until task completes with stop token support.
     454              : 
     455              :     Executes a lazy task on a single-threaded event loop
     456              :     with the given stop token and blocks until completion.
     457              : 
     458              :     @par Exception Safety
     459              :     Basic guarantee. If the task throws, the exception is
     460              :     rethrown to the caller.
     461              : 
     462              :     @param st The stop token for cooperative cancellation.
     463              : 
     464              :     @return A wrapper that accepts a task for blocking execution.
     465              : 
     466              :     @see run_async
     467              : */
     468              : [[nodiscard]] inline auto
     469              : run_blocking(std::stop_token st)
     470              : {
     471              :     return run_blocking_wrapper<
     472              :         detail::default_handler,
     473              :         detail::default_handler>(
     474              :             std::move(st),
     475              :             detail::default_handler{},
     476              :             detail::default_handler{});
     477              : }
     478              : 
     479              : /** Block until task completes with stop token and handler.
     480              : 
     481              :     @param st The stop token for cooperative cancellation.
     482              :     @param h1 Handler invoked with the result on success.
     483              : 
     484              :     @return A wrapper that accepts a task for blocking execution.
     485              : 
     486              :     @see run_async
     487              : */
     488              : template<class H1>
     489              : [[nodiscard]] auto
     490            2 : run_blocking(std::stop_token st, H1 h1)
     491              : {
     492              :     return run_blocking_wrapper<
     493              :         H1,
     494              :         detail::default_handler>(
     495            2 :             std::move(st),
     496            2 :             std::move(h1),
     497            2 :             detail::default_handler{});
     498              : }
     499              : 
     500              : /** Block until task completes with stop token and handlers.
     501              : 
     502              :     @param st The stop token for cooperative cancellation.
     503              :     @param h1 Handler invoked with the result on success.
     504              :     @param h2 Handler invoked with the exception on failure.
     505              : 
     506              :     @return A wrapper that accepts a task for blocking execution.
     507              : 
     508              :     @see run_async
     509              : */
     510              : template<class H1, class H2>
     511              : [[nodiscard]] auto
     512              : run_blocking(std::stop_token st, H1 h1, H2 h2)
     513              : {
     514              :     return run_blocking_wrapper<
     515              :         H1,
     516              :         H2>(
     517              :             std::move(st),
     518              :             std::move(h1),
     519              :             std::move(h2));
     520              : }
     521              : 
     522              : } // namespace test
     523              : } // namespace capy
     524              : } // namespace boost
     525              : 
     526              : #endif
        

Generated by: LCOV version 2.3