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