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