1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_TEST_RUN_BLOCKING_HPP
10  
#ifndef BOOST_CAPY_TEST_RUN_BLOCKING_HPP
11  
#define BOOST_CAPY_TEST_RUN_BLOCKING_HPP
11  
#define BOOST_CAPY_TEST_RUN_BLOCKING_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/concept/execution_context.hpp>
14  
#include <boost/capy/concept/execution_context.hpp>
15  
#include <boost/capy/concept/executor.hpp>
15  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/ex/run_async.hpp>
16  
#include <boost/capy/ex/run_async.hpp>
17  

17  

18  
#include <coroutine>
18  
#include <coroutine>
19  
#include <exception>
19  
#include <exception>
20  
#include <stop_token>
20  
#include <stop_token>
21  
#include <type_traits>
21  
#include <type_traits>
22  
#include <utility>
22  
#include <utility>
23  

23  

24  
namespace boost {
24  
namespace boost {
25  
namespace capy {
25  
namespace capy {
26  
namespace test {
26  
namespace test {
27  

27  

28  
class blocking_context;
28  
class blocking_context;
29  

29  

30  
/** Single-threaded executor for blocking synchronous tests.
30  
/** Single-threaded executor for blocking synchronous tests.
31  

31  

32  
    This executor is used internally by @ref run_blocking to
32  
    This executor is used internally by @ref run_blocking to
33  
    execute coroutine tasks on the calling thread. Work submitted
33  
    execute coroutine tasks on the calling thread. Work submitted
34  
    via `dispatch()` is returned for symmetric transfer. Work
34  
    via `dispatch()` is returned for symmetric transfer. Work
35  
    submitted via `post()` is enqueued and processed by the
35  
    submitted via `post()` is enqueued and processed by the
36  
    @ref blocking_context event loop.
36  
    @ref blocking_context event loop.
37  

37  

38  
    Users do not construct this type directly. It is obtained
38  
    Users do not construct this type directly. It is obtained
39  
    from @ref blocking_context::get_executor.
39  
    from @ref blocking_context::get_executor.
40  

40  

41  
    @par Thread Safety
41  
    @par Thread Safety
42  
    All member functions are safe to call from any thread.
42  
    All member functions are safe to call from any thread.
43  

43  

44  
    @see blocking_context, run_blocking
44  
    @see blocking_context, run_blocking
45  
*/
45  
*/
46  
struct BOOST_CAPY_DECL blocking_executor
46  
struct BOOST_CAPY_DECL blocking_executor
47  
{
47  
{
48  
    /// Construct from a context pointer.
48  
    /// Construct from a context pointer.
49  
    explicit blocking_executor(
49  
    explicit blocking_executor(
50  
        blocking_context* ctx) noexcept
50  
        blocking_context* ctx) noexcept
51  
        : ctx_(ctx)
51  
        : ctx_(ctx)
52  
    {
52  
    {
53  
    }
53  
    }
54  

54  

55  
    /** Compare two blocking executors for equality.
55  
    /** Compare two blocking executors for equality.
56  

56  

57  
        Two executors are equal if they share the same context.
57  
        Two executors are equal if they share the same context.
58  
    */
58  
    */
59  
    bool
59  
    bool
60  
    operator==(blocking_executor const& other) const noexcept;
60  
    operator==(blocking_executor const& other) const noexcept;
61  

61  

62  
    /** Return the associated execution context.
62  
    /** Return the associated execution context.
63  

63  

64  
        @return A reference to the owning `blocking_context`.
64  
        @return A reference to the owning `blocking_context`.
65  
    */
65  
    */
66  
    blocking_context&
66  
    blocking_context&
67  
    context() const noexcept;
67  
    context() const noexcept;
68  

68  

69  
    /// Called when work is submitted (no-op).
69  
    /// Called when work is submitted (no-op).
70  
    void on_work_started() const noexcept;
70  
    void on_work_started() const noexcept;
71  

71  

72  
    /// Called when work completes (no-op).
72  
    /// Called when work completes (no-op).
73  
    void on_work_finished() const noexcept;
73  
    void on_work_finished() const noexcept;
74  

74  

75  
    /** Dispatch work for immediate inline execution.
75  
    /** Dispatch work for immediate inline execution.
76  

76  

77  
        Returns the handle for symmetric transfer. The caller
77  
        Returns the handle for symmetric transfer. The caller
78  
        resumes the coroutine via the returned handle.
78  
        resumes the coroutine via the returned handle.
79  

79  

80  
        @param h The coroutine handle to execute.
80  
        @param h The coroutine handle to execute.
81  

81  

82  
        @return `h` for symmetric transfer.
82  
        @return `h` for symmetric transfer.
83  
    */
83  
    */
84  
    std::coroutine_handle<>
84  
    std::coroutine_handle<>
85  
    dispatch(std::coroutine_handle<> h) const;
85  
    dispatch(std::coroutine_handle<> h) const;
86  

86  

87  
    /** Post work for deferred execution.
87  
    /** Post work for deferred execution.
88  

88  

89  
        Enqueues the coroutine handle into the context's work
89  
        Enqueues the coroutine handle into the context's work
90  
        queue. The handle is resumed when the blocking event
90  
        queue. The handle is resumed when the blocking event
91  
        loop processes it.
91  
        loop processes it.
92  

92  

93  
        @param h The coroutine handle to enqueue.
93  
        @param h The coroutine handle to enqueue.
94  
    */
94  
    */
95  
    void
95  
    void
96  
    post(std::coroutine_handle<> h) const;
96  
    post(std::coroutine_handle<> h) const;
97  

97  

98  
private:
98  
private:
99  
    blocking_context* ctx_;
99  
    blocking_context* ctx_;
100  
};
100  
};
101  

101  

102  
/** Single-threaded execution context for blocking tests.
102  
/** Single-threaded execution context for blocking tests.
103  

103  

104  
    Provides a work queue and event loop that runs on the
104  
    Provides a work queue and event loop that runs on the
105  
    calling thread. Coroutines dispatched through the
105  
    calling thread. Coroutines dispatched through the
106  
    associated @ref blocking_executor have their `post()`
106  
    associated @ref blocking_executor have their `post()`
107  
    calls enqueued and processed by @ref run, which blocks
107  
    calls enqueued and processed by @ref run, which blocks
108  
    until @ref signal_done is called.
108  
    until @ref signal_done is called.
109  

109  

110  
    This context is created internally by @ref run_blocking.
110  
    This context is created internally by @ref run_blocking.
111  
    Users do not interact with it directly.
111  
    Users do not interact with it directly.
112  

112  

113  
    @par Thread Safety
113  
    @par Thread Safety
114  
    The event loop runs on the thread that calls `run()`.
114  
    The event loop runs on the thread that calls `run()`.
115  
    `signal_done()` and `enqueue()` are safe to call from
115  
    `signal_done()` and `enqueue()` are safe to call from
116  
    any thread.
116  
    any thread.
117  

117  

118  
    @see blocking_executor, run_blocking
118  
    @see blocking_executor, run_blocking
119  
*/
119  
*/
120  
class BOOST_CAPY_DECL blocking_context
120  
class BOOST_CAPY_DECL blocking_context
121  
    : public execution_context
121  
    : public execution_context
122  
{
122  
{
123  
    struct impl;
123  
    struct impl;
124  
    impl* impl_;
124  
    impl* impl_;
125  

125  

126  
public:
126  
public:
127  
    using executor_type = blocking_executor;
127  
    using executor_type = blocking_executor;
128  

128  

129  
    /** Construct a blocking context.
129  
    /** Construct a blocking context.
130  

130  

131  
        Allocates the internal work queue and
131  
        Allocates the internal work queue and
132  
        synchronization state.
132  
        synchronization state.
133  
    */
133  
    */
134  
    blocking_context();
134  
    blocking_context();
135  

135  

136  
    /** Destroy the blocking context. */
136  
    /** Destroy the blocking context. */
137  
    ~blocking_context();
137  
    ~blocking_context();
138  

138  

139  
    /** Return an executor bound to this context.
139  
    /** Return an executor bound to this context.
140  

140  

141  
        @return A `blocking_executor` that enqueues work
141  
        @return A `blocking_executor` that enqueues work
142  
            into this context's queue.
142  
            into this context's queue.
143  
    */
143  
    */
144  
    blocking_executor
144  
    blocking_executor
145  
    get_executor() noexcept;
145  
    get_executor() noexcept;
146  

146  

147  
    /** Signal that the task has completed.
147  
    /** Signal that the task has completed.
148  

148  

149  
        Wakes the event loop so that @ref run returns.
149  
        Wakes the event loop so that @ref run returns.
150  
    */
150  
    */
151  
    void
151  
    void
152  
    signal_done() noexcept;
152  
    signal_done() noexcept;
153  

153  

154  
    /** Signal that the task has completed with an error.
154  
    /** Signal that the task has completed with an error.
155  

155  

156  
        Stores the exception and wakes the event loop
156  
        Stores the exception and wakes the event loop
157  
        so that @ref run rethrows it.
157  
        so that @ref run rethrows it.
158  

158  

159  
        @param ep The exception to propagate.
159  
        @param ep The exception to propagate.
160  
    */
160  
    */
161  
    void
161  
    void
162  
    signal_done(std::exception_ptr ep) noexcept;
162  
    signal_done(std::exception_ptr ep) noexcept;
163  

163  

164  
    /** Run the event loop until done.
164  
    /** Run the event loop until done.
165  

165  

166  
        Blocks the calling thread, processing posted
166  
        Blocks the calling thread, processing posted
167  
        coroutine handles until @ref signal_done is called.
167  
        coroutine handles until @ref signal_done is called.
168  
        After draining remaining work, rethrows any stored
168  
        After draining remaining work, rethrows any stored
169  
        exception.
169  
        exception.
170  

170  

171  
        @par Exception Safety
171  
        @par Exception Safety
172  
        Basic guarantee. If the completed task stored an
172  
        Basic guarantee. If the completed task stored an
173  
        exception via `signal_done(ep)`, it is rethrown.
173  
        exception via `signal_done(ep)`, it is rethrown.
174  
    */
174  
    */
175  
    void
175  
    void
176  
    run();
176  
    run();
177  

177  

178  
    /** Enqueue a coroutine handle for processing.
178  
    /** Enqueue a coroutine handle for processing.
179  

179  

180  
        @param h The coroutine handle to enqueue.
180  
        @param h The coroutine handle to enqueue.
181  
    */
181  
    */
182  
    void
182  
    void
183  
    enqueue(std::coroutine_handle<> h);
183  
    enqueue(std::coroutine_handle<> h);
184  
};
184  
};
185  

185  

186  
/** Wrapper that signals completion after invoking the handler.
186  
/** Wrapper that signals completion after invoking the handler.
187  

187  

188  
    Forwards invocations to the contained handler_pair, then
188  
    Forwards invocations to the contained handler_pair, then
189  
    signals the `blocking_context` so that its event loop
189  
    signals the `blocking_context` so that its event loop
190  
    unblocks. Exceptions thrown by the handler are captured
190  
    unblocks. Exceptions thrown by the handler are captured
191  
    and stored for later rethrow.
191  
    and stored for later rethrow.
192  

192  

193  
    @tparam H1 The success handler type.
193  
    @tparam H1 The success handler type.
194  
    @tparam H2 The error handler type.
194  
    @tparam H2 The error handler type.
195  

195  

196  
    @par Thread Safety
196  
    @par Thread Safety
197  
    Safe to invoke from any thread.
197  
    Safe to invoke from any thread.
198  

198  

199  
    @see run_blocking, blocking_context
199  
    @see run_blocking, blocking_context
200  
*/
200  
*/
201  
template<class H1, class H2>
201  
template<class H1, class H2>
202  
struct blocking_handler_wrapper
202  
struct blocking_handler_wrapper
203  
{
203  
{
204  
    blocking_context* ctx_;
204  
    blocking_context* ctx_;
205  
    detail::handler_pair<H1, H2> handlers_;
205  
    detail::handler_pair<H1, H2> handlers_;
206  

206  

207  
    /** Invoke the handler with a non-void result. */
207  
    /** Invoke the handler with a non-void result. */
208  
    template<class T>
208  
    template<class T>
209  
    void operator()(T&& v)
209  
    void operator()(T&& v)
210  
    {
210  
    {
211  
        try
211  
        try
212  
        {
212  
        {
213  
            handlers_(std::forward<T>(v));
213  
            handlers_(std::forward<T>(v));
214  
        }
214  
        }
215  
        catch(...)
215  
        catch(...)
216  
        {
216  
        {
217  
            ctx_->signal_done(std::current_exception());
217  
            ctx_->signal_done(std::current_exception());
218  
            return;
218  
            return;
219  
        }
219  
        }
220  
        ctx_->signal_done();
220  
        ctx_->signal_done();
221  
    }
221  
    }
222  

222  

223  
    /** Invoke the handler for a void result. */
223  
    /** Invoke the handler for a void result. */
224  
    void operator()()
224  
    void operator()()
225  
    {
225  
    {
226  
        try
226  
        try
227  
        {
227  
        {
228  
            handlers_();
228  
            handlers_();
229  
        }
229  
        }
230  
        catch(...)
230  
        catch(...)
231  
        {
231  
        {
232  
            ctx_->signal_done(std::current_exception());
232  
            ctx_->signal_done(std::current_exception());
233  
            return;
233  
            return;
234  
        }
234  
        }
235  
        ctx_->signal_done();
235  
        ctx_->signal_done();
236  
    }
236  
    }
237  

237  

238  
    /** Invoke the handler with an exception. */
238  
    /** Invoke the handler with an exception. */
239  
    void operator()(std::exception_ptr ep)
239  
    void operator()(std::exception_ptr ep)
240  
    {
240  
    {
241  
        try
241  
        try
242  
        {
242  
        {
243  
            handlers_(ep);
243  
            handlers_(ep);
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  
        }
250  
        ctx_->signal_done();
250  
        ctx_->signal_done();
251  
    }
251  
    }
252  
};
252  
};
253  

253  

254  
/** Wrapper returned by run_blocking that accepts a task.
254  
/** Wrapper returned by run_blocking that accepts a task.
255  

255  

256  
    Holds the handlers and optional stop token. When invoked
256  
    Holds the handlers and optional stop token. When invoked
257  
    with a task, creates a @ref blocking_context, launches
257  
    with a task, creates a @ref blocking_context, launches
258  
    the task via `run_async`, and pumps the event loop until
258  
    the task via `run_async`, and pumps the event loop until
259  
    the task completes.
259  
    the task completes.
260  

260  

261  
    The rvalue ref-qualifier on `operator()` ensures the
261  
    The rvalue ref-qualifier on `operator()` ensures the
262  
    wrapper can only be used as a temporary.
262  
    wrapper can only be used as a temporary.
263  

263  

264  
    @tparam H1 The success handler type.
264  
    @tparam H1 The success handler type.
265  
    @tparam H2 The error handler type.
265  
    @tparam H2 The error handler type.
266  

266  

267  
    @par Thread Safety
267  
    @par Thread Safety
268  
    The wrapper itself should only be used from one thread.
268  
    The wrapper itself should only be used from one thread.
269  
    The calling thread blocks until the task completes.
269  
    The calling thread blocks until the task completes.
270  

270  

271  
    @par Example
271  
    @par Example
272  
    @code
272  
    @code
273  
    int result = 0;
273  
    int result = 0;
274  
    run_blocking([&](int v) { result = v; })(my_task());
274  
    run_blocking([&](int v) { result = v; })(my_task());
275  
    @endcode
275  
    @endcode
276  

276  

277  
    @see run_blocking, run_async
277  
    @see run_blocking, run_async
278  
*/
278  
*/
279  
template<class H1, class H2>
279  
template<class H1, class H2>
280  
class [[nodiscard]] run_blocking_wrapper
280  
class [[nodiscard]] run_blocking_wrapper
281  
{
281  
{
282  
    std::stop_token st_;
282  
    std::stop_token st_;
283  
    H1 h1_;
283  
    H1 h1_;
284  
    H2 h2_;
284  
    H2 h2_;
285  

285  

286  
public:
286  
public:
287  
    /** Construct wrapper with stop token and handlers.
287  
    /** Construct wrapper with stop token and handlers.
288  

288  

289  
        @param st The stop token for cooperative cancellation.
289  
        @param st The stop token for cooperative cancellation.
290  
        @param h1 The success handler.
290  
        @param h1 The success handler.
291  
        @param h2 The error handler.
291  
        @param h2 The error handler.
292  
    */
292  
    */
293  
    run_blocking_wrapper(
293  
    run_blocking_wrapper(
294  
        std::stop_token st,
294  
        std::stop_token st,
295  
        H1 h1,
295  
        H1 h1,
296  
        H2 h2)
296  
        H2 h2)
297  
        : st_(std::move(st))
297  
        : st_(std::move(st))
298  
        , h1_(std::move(h1))
298  
        , h1_(std::move(h1))
299  
        , h2_(std::move(h2))
299  
        , h2_(std::move(h2))
300  
    {
300  
    {
301  
    }
301  
    }
302  

302  

303  
    run_blocking_wrapper(run_blocking_wrapper const&) = delete;
303  
    run_blocking_wrapper(run_blocking_wrapper const&) = delete;
304  
    run_blocking_wrapper(run_blocking_wrapper&&) = delete;
304  
    run_blocking_wrapper(run_blocking_wrapper&&) = delete;
305  
    run_blocking_wrapper& operator=(run_blocking_wrapper const&) = delete;
305  
    run_blocking_wrapper& operator=(run_blocking_wrapper const&) = delete;
306  
    run_blocking_wrapper& operator=(run_blocking_wrapper&&) = delete;
306  
    run_blocking_wrapper& operator=(run_blocking_wrapper&&) = delete;
307  

307  

308  
    /** Launch the task and block until completion.
308  
    /** Launch the task and block until completion.
309  

309  

310  
        Creates a blocking_context with a single-threaded
310  
        Creates a blocking_context with a single-threaded
311  
        event loop, launches the task via `run_async`, then
311  
        event loop, launches the task via `run_async`, then
312  
        pumps the loop until the task completes or throws.
312  
        pumps the loop until the task completes or throws.
313  

313  

314  
        @tparam Task The IoRunnable type.
314  
        @tparam Task The IoRunnable type.
315  

315  

316  
        @param t The task to execute.
316  
        @param t The task to execute.
317  
    */
317  
    */
318  
    template<IoRunnable Task>
318  
    template<IoRunnable Task>
319  
    void
319  
    void
320  
    operator()(Task t) &&
320  
    operator()(Task t) &&
321  
    {
321  
    {
322  
        blocking_context ctx;
322  
        blocking_context ctx;
323  

323  

324  
        auto make_handlers = [&]() {
324  
        auto make_handlers = [&]() {
325  
            if constexpr(
325  
            if constexpr(
326  
                std::is_same_v<H2, detail::default_handler>)
326  
                std::is_same_v<H2, detail::default_handler>)
327  
                return detail::handler_pair<H1, H2>{
327  
                return detail::handler_pair<H1, H2>{
328  
                    std::move(h1_)};
328  
                    std::move(h1_)};
329  
            else
329  
            else
330  
                return detail::handler_pair<H1, H2>{
330  
                return detail::handler_pair<H1, H2>{
331  
                    std::move(h1_), std::move(h2_)};
331  
                    std::move(h1_), std::move(h2_)};
332  
        };
332  
        };
333  

333  

334  
        run_async(
334  
        run_async(
335  
            ctx.get_executor(),
335  
            ctx.get_executor(),
336  
            st_,
336  
            st_,
337  
            blocking_handler_wrapper<H1, H2>{
337  
            blocking_handler_wrapper<H1, H2>{
338  
                &ctx, make_handlers()}
338  
                &ctx, make_handlers()}
339  
        )(std::move(t));
339  
        )(std::move(t));
340  

340  

341  
        ctx.run();
341  
        ctx.run();
342  
    }
342  
    }
343  
};
343  
};
344  

344  

345  
/** Block until task completes and discard result.
345  
/** Block until task completes and discard result.
346  

346  

347  
    Executes a lazy task on a single-threaded event loop
347  
    Executes a lazy task on a single-threaded event loop
348  
    and blocks the calling thread until the task completes
348  
    and blocks the calling thread until the task completes
349  
    or throws.
349  
    or throws.
350  

350  

351  
    @par Exception Safety
351  
    @par Exception Safety
352  
    Basic guarantee. If the task throws, the exception is
352  
    Basic guarantee. If the task throws, the exception is
353  
    rethrown to the caller.
353  
    rethrown to the caller.
354  

354  

355  
    @par Example
355  
    @par Example
356  
    @code
356  
    @code
357  
    run_blocking()(my_void_task());
357  
    run_blocking()(my_void_task());
358  
    @endcode
358  
    @endcode
359  

359  

360  
    @return A wrapper that accepts a task for blocking execution.
360  
    @return A wrapper that accepts a task for blocking execution.
361  

361  

362  
    @see run_async
362  
    @see run_async
363  
*/
363  
*/
364  
[[nodiscard]] inline auto
364  
[[nodiscard]] inline auto
365  
run_blocking()
365  
run_blocking()
366  
{
366  
{
367  
    return run_blocking_wrapper<
367  
    return run_blocking_wrapper<
368  
        detail::default_handler,
368  
        detail::default_handler,
369  
        detail::default_handler>(
369  
        detail::default_handler>(
370  
            std::stop_token{},
370  
            std::stop_token{},
371  
            detail::default_handler{},
371  
            detail::default_handler{},
372  
            detail::default_handler{});
372  
            detail::default_handler{});
373  
}
373  
}
374  

374  

375  
/** Block until task completes and invoke handler with result.
375  
/** Block until task completes and invoke handler with result.
376  

376  

377  
    Executes a lazy task on a single-threaded event loop
377  
    Executes a lazy task on a single-threaded event loop
378  
    and blocks until completion. The handler `h1` is called
378  
    and blocks until completion. The handler `h1` is called
379  
    with the result on success. If `h1` is also invocable
379  
    with the result on success. If `h1` is also invocable
380  
    with `std::exception_ptr`, it handles exceptions too.
380  
    with `std::exception_ptr`, it handles exceptions too.
381  
    Otherwise, exceptions are rethrown.
381  
    Otherwise, exceptions are rethrown.
382  

382  

383  
    @par Exception Safety
383  
    @par Exception Safety
384  
    Basic guarantee. Exceptions from the task are passed
384  
    Basic guarantee. Exceptions from the task are passed
385  
    to `h1` if it accepts `std::exception_ptr`, otherwise
385  
    to `h1` if it accepts `std::exception_ptr`, otherwise
386  
    rethrown.
386  
    rethrown.
387  

387  

388  
    @par Example
388  
    @par Example
389  
    @code
389  
    @code
390  
    int result = 0;
390  
    int result = 0;
391  
    run_blocking([&](int v) { result = v; })(compute());
391  
    run_blocking([&](int v) { result = v; })(compute());
392  
    @endcode
392  
    @endcode
393  

393  

394  
    @param h1 Handler invoked with the result on success,
394  
    @param h1 Handler invoked with the result on success,
395  
        and optionally with `std::exception_ptr` on failure.
395  
        and optionally with `std::exception_ptr` on failure.
396  

396  

397  
    @return A wrapper that accepts a task for blocking execution.
397  
    @return A wrapper that accepts a task for blocking execution.
398  

398  

399  
    @see run_async
399  
    @see run_async
400  
*/
400  
*/
401  
template<class H1>
401  
template<class H1>
402  
[[nodiscard]] auto
402  
[[nodiscard]] auto
403  
run_blocking(H1 h1)
403  
run_blocking(H1 h1)
404  
{
404  
{
405  
    return run_blocking_wrapper<
405  
    return run_blocking_wrapper<
406  
        H1,
406  
        H1,
407  
        detail::default_handler>(
407  
        detail::default_handler>(
408  
            std::stop_token{},
408  
            std::stop_token{},
409  
            std::move(h1),
409  
            std::move(h1),
410  
            detail::default_handler{});
410  
            detail::default_handler{});
411  
}
411  
}
412  

412  

413  
/** Block until task completes with separate handlers.
413  
/** Block until task completes with separate handlers.
414  

414  

415  
    Executes a lazy task on a single-threaded event loop
415  
    Executes a lazy task on a single-threaded event loop
416  
    and blocks until completion. The handler `h1` is called
416  
    and blocks until completion. The handler `h1` is called
417  
    on success, `h2` on failure.
417  
    on success, `h2` on failure.
418  

418  

419  
    @par Exception Safety
419  
    @par Exception Safety
420  
    Basic guarantee. Exceptions from the task are passed
420  
    Basic guarantee. Exceptions from the task are passed
421  
    to `h2`.
421  
    to `h2`.
422  

422  

423  
    @par Example
423  
    @par Example
424  
    @code
424  
    @code
425  
    int result = 0;
425  
    int result = 0;
426  
    run_blocking(
426  
    run_blocking(
427  
        [&](int v) { result = v; },
427  
        [&](int v) { result = v; },
428  
        [](std::exception_ptr ep) {
428  
        [](std::exception_ptr ep) {
429  
            std::rethrow_exception(ep);
429  
            std::rethrow_exception(ep);
430  
        }
430  
        }
431  
    )(compute());
431  
    )(compute());
432  
    @endcode
432  
    @endcode
433  

433  

434  
    @param h1 Handler invoked with the result on success.
434  
    @param h1 Handler invoked with the result on success.
435  
    @param h2 Handler invoked with the exception on failure.
435  
    @param h2 Handler invoked with the exception on failure.
436  

436  

437  
    @return A wrapper that accepts a task for blocking execution.
437  
    @return A wrapper that accepts a task for blocking execution.
438  

438  

439  
    @see run_async
439  
    @see run_async
440  
*/
440  
*/
441  
template<class H1, class H2>
441  
template<class H1, class H2>
442  
[[nodiscard]] auto
442  
[[nodiscard]] auto
443  
run_blocking(H1 h1, H2 h2)
443  
run_blocking(H1 h1, H2 h2)
444  
{
444  
{
445  
    return run_blocking_wrapper<
445  
    return run_blocking_wrapper<
446  
        H1,
446  
        H1,
447  
        H2>(
447  
        H2>(
448  
            std::stop_token{},
448  
            std::stop_token{},
449  
            std::move(h1),
449  
            std::move(h1),
450  
            std::move(h2));
450  
            std::move(h2));
451  
}
451  
}
452  

452  

453  
/** Block until task completes with stop token support.
453  
/** Block until task completes with stop token support.
454  

454  

455  
    Executes a lazy task on a single-threaded event loop
455  
    Executes a lazy task on a single-threaded event loop
456  
    with the given stop token and blocks until completion.
456  
    with the given stop token and blocks until completion.
457  

457  

458  
    @par Exception Safety
458  
    @par Exception Safety
459  
    Basic guarantee. If the task throws, the exception is
459  
    Basic guarantee. If the task throws, the exception is
460  
    rethrown to the caller.
460  
    rethrown to the caller.
461  

461  

462  
    @param st The stop token for cooperative cancellation.
462  
    @param st The stop token for cooperative cancellation.
463  

463  

464  
    @return A wrapper that accepts a task for blocking execution.
464  
    @return A wrapper that accepts a task for blocking execution.
465  

465  

466  
    @see run_async
466  
    @see run_async
467  
*/
467  
*/
468  
[[nodiscard]] inline auto
468  
[[nodiscard]] inline auto
469  
run_blocking(std::stop_token st)
469  
run_blocking(std::stop_token st)
470  
{
470  
{
471  
    return run_blocking_wrapper<
471  
    return run_blocking_wrapper<
472  
        detail::default_handler,
472  
        detail::default_handler,
473  
        detail::default_handler>(
473  
        detail::default_handler>(
474  
            std::move(st),
474  
            std::move(st),
475  
            detail::default_handler{},
475  
            detail::default_handler{},
476  
            detail::default_handler{});
476  
            detail::default_handler{});
477  
}
477  
}
478  

478  

479  
/** Block until task completes with stop token and handler.
479  
/** Block until task completes with stop token and handler.
480  

480  

481  
    @param st The stop token for cooperative cancellation.
481  
    @param st The stop token for cooperative cancellation.
482  
    @param h1 Handler invoked with the result on success.
482  
    @param h1 Handler invoked with the result on success.
483  

483  

484  
    @return A wrapper that accepts a task for blocking execution.
484  
    @return A wrapper that accepts a task for blocking execution.
485  

485  

486  
    @see run_async
486  
    @see run_async
487  
*/
487  
*/
488  
template<class H1>
488  
template<class H1>
489  
[[nodiscard]] auto
489  
[[nodiscard]] auto
490  
run_blocking(std::stop_token st, H1 h1)
490  
run_blocking(std::stop_token st, H1 h1)
491  
{
491  
{
492  
    return run_blocking_wrapper<
492  
    return run_blocking_wrapper<
493  
        H1,
493  
        H1,
494  
        detail::default_handler>(
494  
        detail::default_handler>(
495  
            std::move(st),
495  
            std::move(st),
496  
            std::move(h1),
496  
            std::move(h1),
497  
            detail::default_handler{});
497  
            detail::default_handler{});
498  
}
498  
}
499  

499  

500  
/** Block until task completes with stop token and handlers.
500  
/** Block until task completes with stop token and handlers.
501  

501  

502  
    @param st The stop token for cooperative cancellation.
502  
    @param st The stop token for cooperative cancellation.
503  
    @param h1 Handler invoked with the result on success.
503  
    @param h1 Handler invoked with the result on success.
504  
    @param h2 Handler invoked with the exception on failure.
504  
    @param h2 Handler invoked with the exception on failure.
505  

505  

506  
    @return A wrapper that accepts a task for blocking execution.
506  
    @return A wrapper that accepts a task for blocking execution.
507  

507  

508  
    @see run_async
508  
    @see run_async
509  
*/
509  
*/
510  
template<class H1, class H2>
510  
template<class H1, class H2>
511  
[[nodiscard]] auto
511  
[[nodiscard]] auto
512  
run_blocking(std::stop_token st, H1 h1, H2 h2)
512  
run_blocking(std::stop_token st, H1 h1, H2 h2)
513  
{
513  
{
514  
    return run_blocking_wrapper<
514  
    return run_blocking_wrapper<
515  
        H1,
515  
        H1,
516  
        H2>(
516  
        H2>(
517  
            std::move(st),
517  
            std::move(st),
518  
            std::move(h1),
518  
            std::move(h1),
519  
            std::move(h2));
519  
            std::move(h2));
520  
}
520  
}
521  

521  

522  
} // namespace test
522  
} // namespace test
523  
} // namespace capy
523  
} // namespace capy
524  
} // namespace boost
524  
} // namespace boost
525  

525  

526  
#endif
526  
#endif