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