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