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