1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/coro.hpp>
13  
#include <boost/capy/coro.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  
#include <boost/capy/ex/system_context.hpp>
17  
#include <boost/capy/ex/system_context.hpp>
18  

18  

19  
#include <condition_variable>
19  
#include <condition_variable>
20  
#include <exception>
20  
#include <exception>
21  
#include <mutex>
21  
#include <mutex>
22  
#include <stdexcept>
22  
#include <stdexcept>
23  
#include <stop_token>
23  
#include <stop_token>
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  
struct inline_executor;
31  
struct inline_executor;
32  

32  

33  
/** Execution context for inline blocking execution.
33  
/** Execution context for inline blocking execution.
34  

34  

35  
    This execution context is used with inline_executor for
35  
    This execution context is used with inline_executor for
36  
    blocking synchronous execution. It satisfies the
36  
    blocking synchronous execution. It satisfies the
37  
    ExecutionContext concept requirements.
37  
    ExecutionContext concept requirements.
38  

38  

39  
    @see inline_executor
39  
    @see inline_executor
40  
    @see run_blocking
40  
    @see run_blocking
41  
*/
41  
*/
42  
class inline_context : public execution_context
42  
class inline_context : public execution_context
43  
{
43  
{
44  
public:
44  
public:
45  
    using executor_type = inline_executor;
45  
    using executor_type = inline_executor;
46  

46  

47  
    inline_context() = default;
47  
    inline_context() = default;
48  

48  

49  
    executor_type
49  
    executor_type
50  
    get_executor() noexcept;
50  
    get_executor() noexcept;
51  
};
51  
};
52  

52  

53  
/** Synchronous executor that executes inline and disallows posting.
53  
/** Synchronous executor that executes inline and disallows posting.
54  

54  

55  
    This executor executes work synchronously on the calling thread
55  
    This executor executes work synchronously on the calling thread
56  
    via `dispatch()`. Calling `post()` throws `std::logic_error`
56  
    via `dispatch()`. Calling `post()` throws `std::logic_error`
57  
    because posting implies deferred execution which is incompatible
57  
    because posting implies deferred execution which is incompatible
58  
    with blocking synchronous semantics.
58  
    with blocking synchronous semantics.
59  

59  

60  
    @par Thread Safety
60  
    @par Thread Safety
61  
    All member functions are thread-safe.
61  
    All member functions are thread-safe.
62  

62  

63  
    @see run_blocking
63  
    @see run_blocking
64  
*/
64  
*/
65  
struct inline_executor
65  
struct inline_executor
66  
{
66  
{
67  
    /// Compare two inline executors for equality.
67  
    /// Compare two inline executors for equality.
68  
    bool
68  
    bool
69  
    operator==(inline_executor const&) const noexcept
69  
    operator==(inline_executor const&) const noexcept
70  
    {
70  
    {
71  
        return true;
71  
        return true;
72  
    }
72  
    }
73  

73  

74  
    /** Return the associated execution context.
74  
    /** Return the associated execution context.
75  

75  

76  
        @return A reference to a function-local static `inline_context`.
76  
        @return A reference to a function-local static `inline_context`.
77  
    */
77  
    */
78  
    inline_context&
78  
    inline_context&
79  
    context() const noexcept
79  
    context() const noexcept
80  
    {
80  
    {
81  
        static inline_context ctx;
81  
        static inline_context ctx;
82  
        return ctx;
82  
        return ctx;
83  
    }
83  
    }
84  

84  

85  
    /// Called when work is submitted (no-op).
85  
    /// Called when work is submitted (no-op).
86  
    void on_work_started() const noexcept {}
86  
    void on_work_started() const noexcept {}
87  

87  

88  
    /// Called when work completes (no-op).
88  
    /// Called when work completes (no-op).
89  
    void on_work_finished() const noexcept {}
89  
    void on_work_finished() const noexcept {}
90  

90  

91  
    /** Dispatch work for immediate inline execution.
91  
    /** Dispatch work for immediate inline execution.
92  

92  

93  
        @param h The coroutine handle to execute.
93  
        @param h The coroutine handle to execute.
94  
    */
94  
    */
95  
    void
95  
    void
96  
    dispatch(coro h) const
96  
    dispatch(coro h) const
97  
    {
97  
    {
98  
        h.resume();
98  
        h.resume();
99  
    }
99  
    }
100  

100  

101  
    /** Post work for deferred execution.
101  
    /** Post work for deferred execution.
102  

102  

103  
        @par Exception Safety
103  
        @par Exception Safety
104  
        Always throws.
104  
        Always throws.
105  

105  

106  
        @throws std::logic_error Always, because posting is not
106  
        @throws std::logic_error Always, because posting is not
107  
            supported in a blocking context.
107  
            supported in a blocking context.
108  

108  

109  
        @param h The coroutine handle (unused).
109  
        @param h The coroutine handle (unused).
110  
    */
110  
    */
111  
    [[noreturn]] void
111  
    [[noreturn]] void
112  
    post(coro) const
112  
    post(coro) const
113  
    {
113  
    {
114  
        throw std::logic_error(
114  
        throw std::logic_error(
115  
            "post not supported in blocking context");
115  
            "post not supported in blocking context");
116  
    }
116  
    }
117  
};
117  
};
118  

118  

119  
inline inline_context::executor_type
119  
inline inline_context::executor_type
120  
inline_context::get_executor() noexcept
120  
inline_context::get_executor() noexcept
121  
{
121  
{
122  
    return inline_executor{};
122  
    return inline_executor{};
123  
}
123  
}
124  

124  

125  
static_assert(Executor<inline_executor>);
125  
static_assert(Executor<inline_executor>);
126  
static_assert(ExecutionContext<inline_context>);
126  
static_assert(ExecutionContext<inline_context>);
127  

127  

128  
//----------------------------------------------------------
128  
//----------------------------------------------------------
129  
//
129  
//
130  
// blocking_state
130  
// blocking_state
131  
//
131  
//
132  
//----------------------------------------------------------
132  
//----------------------------------------------------------
133  

133  

134  
/** Synchronization state for blocking execution.
134  
/** Synchronization state for blocking execution.
135  

135  

136  
    Holds the mutex, condition variable, and completion flag
136  
    Holds the mutex, condition variable, and completion flag
137  
    used to block the caller until the task completes.
137  
    used to block the caller until the task completes.
138  

138  

139  
    @par Thread Safety
139  
    @par Thread Safety
140  
    Thread-safe when accessed under the mutex.
140  
    Thread-safe when accessed under the mutex.
141  

141  

142  
    @see run_blocking
142  
    @see run_blocking
143  
    @see blocking_handler_wrapper
143  
    @see blocking_handler_wrapper
144  
*/
144  
*/
145  
struct blocking_state
145  
struct blocking_state
146  
{
146  
{
147  
    std::mutex mtx;
147  
    std::mutex mtx;
148  
    std::condition_variable cv;
148  
    std::condition_variable cv;
149  
    bool done = false;
149  
    bool done = false;
150  
    std::exception_ptr ep;
150  
    std::exception_ptr ep;
151  
};
151  
};
152  

152  

153  
//----------------------------------------------------------
153  
//----------------------------------------------------------
154  
//
154  
//
155  
// blocking_handler_wrapper
155  
// blocking_handler_wrapper
156  
//
156  
//
157  
//----------------------------------------------------------
157  
//----------------------------------------------------------
158  

158  

159  
/** Wrapper that signals completion after invoking the underlying handler_pair.
159  
/** Wrapper that signals completion after invoking the underlying handler_pair.
160  

160  

161  
    This wrapper forwards invocations to the contained handler_pair,
161  
    This wrapper forwards invocations to the contained handler_pair,
162  
    then signals the `blocking_state` condition variable so
162  
    then signals the `blocking_state` condition variable so
163  
    that `run_blocking` can unblock. Any exceptions thrown by the
163  
    that `run_blocking` can unblock. Any exceptions thrown by the
164  
    handler are captured and stored for later rethrow.
164  
    handler are captured and stored for later rethrow.
165  

165  

166  
    @tparam H1 The success handler type.
166  
    @tparam H1 The success handler type.
167  
    @tparam H2 The error handler type.
167  
    @tparam H2 The error handler type.
168  

168  

169  
    @par Thread Safety
169  
    @par Thread Safety
170  
    Safe to invoke from any thread. Signals the condition
170  
    Safe to invoke from any thread. Signals the condition
171  
    variable after calling the handler.
171  
    variable after calling the handler.
172  

172  

173  
    @see run_blocking
173  
    @see run_blocking
174  
    @see blocking_state
174  
    @see blocking_state
175  
*/
175  
*/
176  
template<class H1, class H2>
176  
template<class H1, class H2>
177  
struct blocking_handler_wrapper
177  
struct blocking_handler_wrapper
178  
{
178  
{
179  
    blocking_state* state_;
179  
    blocking_state* state_;
180  
    detail::handler_pair<H1, H2> handlers_;
180  
    detail::handler_pair<H1, H2> handlers_;
181  

181  

182  
    /** Invoke the handler with non-void result and signal completion. */
182  
    /** Invoke the handler with non-void result and signal completion. */
183  
    template<class T>
183  
    template<class T>
184  
    void operator()(T&& v)
184  
    void operator()(T&& v)
185  
    {
185  
    {
186  
        try
186  
        try
187  
        {
187  
        {
188  
            handlers_(std::forward<T>(v));
188  
            handlers_(std::forward<T>(v));
189  
        }
189  
        }
190  
        catch(...)
190  
        catch(...)
191  
        {
191  
        {
192  
            std::lock_guard<std::mutex> lock(state_->mtx);
192  
            std::lock_guard<std::mutex> lock(state_->mtx);
193  
            state_->ep = std::current_exception();
193  
            state_->ep = std::current_exception();
194  
            state_->done = true;
194  
            state_->done = true;
195  
            state_->cv.notify_one();
195  
            state_->cv.notify_one();
196  
            return;
196  
            return;
197  
        }
197  
        }
198  
        {
198  
        {
199  
            std::lock_guard<std::mutex> lock(state_->mtx);
199  
            std::lock_guard<std::mutex> lock(state_->mtx);
200  
            state_->done = true;
200  
            state_->done = true;
201  
        }
201  
        }
202  
        state_->cv.notify_one();
202  
        state_->cv.notify_one();
203  
    }
203  
    }
204  

204  

205  
    /** Invoke the handler for void result and signal completion. */
205  
    /** Invoke the handler for void result and signal completion. */
206  
    void operator()()
206  
    void operator()()
207  
    {
207  
    {
208  
        try
208  
        try
209  
        {
209  
        {
210  
            handlers_();
210  
            handlers_();
211  
        }
211  
        }
212  
        catch(...)
212  
        catch(...)
213  
        {
213  
        {
214  
            std::lock_guard<std::mutex> lock(state_->mtx);
214  
            std::lock_guard<std::mutex> lock(state_->mtx);
215  
            state_->ep = std::current_exception();
215  
            state_->ep = std::current_exception();
216  
            state_->done = true;
216  
            state_->done = true;
217  
            state_->cv.notify_one();
217  
            state_->cv.notify_one();
218  
            return;
218  
            return;
219  
        }
219  
        }
220  
        {
220  
        {
221  
            std::lock_guard<std::mutex> lock(state_->mtx);
221  
            std::lock_guard<std::mutex> lock(state_->mtx);
222  
            state_->done = true;
222  
            state_->done = true;
223  
        }
223  
        }
224  
        state_->cv.notify_one();
224  
        state_->cv.notify_one();
225  
    }
225  
    }
226  

226  

227  
    /** Invoke the handler with exception and signal completion. */
227  
    /** Invoke the handler with exception and signal completion. */
228  
    void operator()(std::exception_ptr ep)
228  
    void operator()(std::exception_ptr ep)
229  
    {
229  
    {
230  
        try
230  
        try
231  
        {
231  
        {
232  
            handlers_(ep);
232  
            handlers_(ep);
233  
        }
233  
        }
234  
        catch(...)
234  
        catch(...)
235  
        {
235  
        {
236  
            std::lock_guard<std::mutex> lock(state_->mtx);
236  
            std::lock_guard<std::mutex> lock(state_->mtx);
237  
            state_->ep = std::current_exception();
237  
            state_->ep = std::current_exception();
238  
            state_->done = true;
238  
            state_->done = true;
239  
            state_->cv.notify_one();
239  
            state_->cv.notify_one();
240  
            return;
240  
            return;
241  
        }
241  
        }
242  
        {
242  
        {
243  
            std::lock_guard<std::mutex> lock(state_->mtx);
243  
            std::lock_guard<std::mutex> lock(state_->mtx);
244  
            state_->done = true;
244  
            state_->done = true;
245  
        }
245  
        }
246  
        state_->cv.notify_one();
246  
        state_->cv.notify_one();
247  
    }
247  
    }
248  
};
248  
};
249  

249  

250  
//----------------------------------------------------------
250  
//----------------------------------------------------------
251  
//
251  
//
252  
// run_blocking_wrapper
252  
// run_blocking_wrapper
253  
//
253  
//
254  
//----------------------------------------------------------
254  
//----------------------------------------------------------
255  

255  

256  
/** Wrapper returned by run_blocking that accepts a task for execution.
256  
/** Wrapper returned by run_blocking that accepts a task for execution.
257  

257  

258  
    This wrapper holds the blocking state and handlers. When invoked
258  
    This wrapper holds the blocking state and handlers. When invoked
259  
    with a task, it launches the task via `run_async` and blocks
259  
    with a task, it launches the task via `run_async` and blocks
260  
    until the task completes.
260  
    until the task completes.
261  

261  

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

264  

265  
    @tparam Ex The executor type satisfying the `Executor` concept.
265  
    @tparam Ex The executor type satisfying the `Executor` concept.
266  
    @tparam H1 The success handler type.
266  
    @tparam H1 The success handler type.
267  
    @tparam H2 The error handler type.
267  
    @tparam H2 The error handler type.
268  

268  

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

272  

273  
    @par Example
273  
    @par Example
274  
    @code
274  
    @code
275  
    // Block until task completes
275  
    // Block until task completes
276  
    int result = 0;
276  
    int result = 0;
277  
    run_blocking([&](int v) { result = v; })(my_task());
277  
    run_blocking([&](int v) { result = v; })(my_task());
278  
    @endcode
278  
    @endcode
279  

279  

280  
    @see run_blocking
280  
    @see run_blocking
281  
    @see run_async
281  
    @see run_async
282  
*/
282  
*/
283  
template<Executor Ex, class H1, class H2>
283  
template<Executor Ex, class H1, class H2>
284  
class [[nodiscard]] run_blocking_wrapper
284  
class [[nodiscard]] run_blocking_wrapper
285  
{
285  
{
286  
    Ex ex_;
286  
    Ex ex_;
287  
    std::stop_token st_;
287  
    std::stop_token st_;
288  
    H1 h1_;
288  
    H1 h1_;
289  
    H2 h2_;
289  
    H2 h2_;
290  

290  

291  
public:
291  
public:
292  
    /** Construct wrapper with executor, stop token, and handlers.
292  
    /** Construct wrapper with executor, stop token, and handlers.
293  

293  

294  
        @param ex The executor to execute the task on.
294  
        @param ex The executor to execute the task on.
295  
        @param st The stop token for cooperative cancellation.
295  
        @param st The stop token for cooperative cancellation.
296  
        @param h1 The success handler.
296  
        @param h1 The success handler.
297  
        @param h2 The error handler.
297  
        @param h2 The error handler.
298  
    */
298  
    */
299  
    run_blocking_wrapper(
299  
    run_blocking_wrapper(
300  
        Ex ex,
300  
        Ex ex,
301  
        std::stop_token st,
301  
        std::stop_token st,
302  
        H1 h1,
302  
        H1 h1,
303  
        H2 h2)
303  
        H2 h2)
304  
        : ex_(std::move(ex))
304  
        : ex_(std::move(ex))
305  
        , st_(std::move(st))
305  
        , st_(std::move(st))
306  
        , h1_(std::move(h1))
306  
        , h1_(std::move(h1))
307  
        , h2_(std::move(h2))
307  
        , h2_(std::move(h2))
308  
    {
308  
    {
309  
    }
309  
    }
310  

310  

311  
    run_blocking_wrapper(run_blocking_wrapper const&) = delete;
311  
    run_blocking_wrapper(run_blocking_wrapper const&) = delete;
312  
    run_blocking_wrapper(run_blocking_wrapper&&) = delete;
312  
    run_blocking_wrapper(run_blocking_wrapper&&) = delete;
313  
    run_blocking_wrapper& operator=(run_blocking_wrapper const&) = delete;
313  
    run_blocking_wrapper& operator=(run_blocking_wrapper const&) = delete;
314  
    run_blocking_wrapper& operator=(run_blocking_wrapper&&) = delete;
314  
    run_blocking_wrapper& operator=(run_blocking_wrapper&&) = delete;
315  

315  

316  
    /** Launch the task and block until completion.
316  
    /** Launch the task and block until completion.
317  

317  

318  
        This operator accepts a task, launches it via `run_async`
318  
        This operator accepts a task, launches it via `run_async`
319  
        with wrapped handlers, and blocks until the task completes.
319  
        with wrapped handlers, and blocks until the task completes.
320  

320  

321  
        @tparam Task The IoLaunchableTask type.
321  
        @tparam Task The IoLaunchableTask type.
322  

322  

323  
        @param t The task to execute.
323  
        @param t The task to execute.
324  
    */
324  
    */
325  
    template<IoLaunchableTask Task>
325  
    template<IoLaunchableTask Task>
326  
    void
326  
    void
327  
    operator()(Task t) &&
327  
    operator()(Task t) &&
328  
    {
328  
    {
329  
        blocking_state state;
329  
        blocking_state state;
330  

330  

331  
        auto make_handlers = [&]() {
331  
        auto make_handlers = [&]() {
332  
            if constexpr(std::is_same_v<H2, detail::default_handler>)
332  
            if constexpr(std::is_same_v<H2, detail::default_handler>)
333  
                return detail::handler_pair<H1, H2>{std::move(h1_)};
333  
                return detail::handler_pair<H1, H2>{std::move(h1_)};
334  
            else
334  
            else
335  
                return detail::handler_pair<H1, H2>{std::move(h1_), std::move(h2_)};
335  
                return detail::handler_pair<H1, H2>{std::move(h1_), std::move(h2_)};
336  
        };
336  
        };
337  

337  

338  
        run_async(
338  
        run_async(
339  
            ex_,
339  
            ex_,
340  
            st_,
340  
            st_,
341  
            blocking_handler_wrapper<H1, H2>{&state, make_handlers()}
341  
            blocking_handler_wrapper<H1, H2>{&state, make_handlers()}
342  
        )(std::move(t));
342  
        )(std::move(t));
343  

343  

344  
        std::unique_lock<std::mutex> lock(state.mtx);
344  
        std::unique_lock<std::mutex> lock(state.mtx);
345  
        state.cv.wait(lock, [&] { return state.done; });
345  
        state.cv.wait(lock, [&] { return state.done; });
346  
        if(state.ep)
346  
        if(state.ep)
347  
            std::rethrow_exception(state.ep);
347  
            std::rethrow_exception(state.ep);
348  
    }
348  
    }
349  
};
349  
};
350  

350  

351  
//----------------------------------------------------------
351  
//----------------------------------------------------------
352  
//
352  
//
353  
// run_blocking Overloads
353  
// run_blocking Overloads
354  
//
354  
//
355  
//----------------------------------------------------------
355  
//----------------------------------------------------------
356  

356  

357  
// With inline_executor (default)
357  
// With inline_executor (default)
358  

358  

359  
/** Block until task completes and discard result.
359  
/** Block until task completes and discard result.
360  

360  

361  
    Executes a lazy task using the inline executor and blocks the
361  
    Executes a lazy task using the inline executor and blocks the
362  
    calling thread until the task completes or throws.
362  
    calling thread until the task completes or throws.
363  

363  

364  
    @par Thread Safety
364  
    @par Thread Safety
365  
    The calling thread is blocked. The task executes inline
365  
    The calling thread is blocked. The task executes inline
366  
    on the calling thread.
366  
    on the calling thread.
367  

367  

368  
    @par Exception Safety
368  
    @par Exception Safety
369  
    Basic guarantee. If the task throws, the exception is
369  
    Basic guarantee. If the task throws, the exception is
370  
    rethrown to the caller.
370  
    rethrown to the caller.
371  

371  

372  
    @par Example
372  
    @par Example
373  
    @code
373  
    @code
374  
    run_blocking()(my_void_task());
374  
    run_blocking()(my_void_task());
375  
    @endcode
375  
    @endcode
376  

376  

377  
    @return A wrapper that accepts a task for blocking execution.
377  
    @return A wrapper that accepts a task for blocking execution.
378  

378  

379  
    @see run_async
379  
    @see run_async
380  
*/
380  
*/
381  
[[nodiscard]] inline auto
381  
[[nodiscard]] inline auto
382  
run_blocking()
382  
run_blocking()
383  
{
383  
{
384  
    return run_blocking_wrapper<
384  
    return run_blocking_wrapper<
385  
        inline_executor,
385  
        inline_executor,
386  
        detail::default_handler,
386  
        detail::default_handler,
387  
        detail::default_handler>(
387  
        detail::default_handler>(
388  
            inline_executor{},
388  
            inline_executor{},
389  
            std::stop_token{},
389  
            std::stop_token{},
390  
            detail::default_handler{},
390  
            detail::default_handler{},
391  
            detail::default_handler{});
391  
            detail::default_handler{});
392  
}
392  
}
393  

393  

394  
/** Block until task completes and invoke handler with result.
394  
/** Block until task completes and invoke handler with result.
395  

395  

396  
    Executes a lazy task using the inline executor and blocks until
396  
    Executes a lazy task using the inline executor and blocks until
397  
    completion. The handler `h1` is called with the result on success.
397  
    completion. The handler `h1` is called with the result on success.
398  
    If `h1` is also invocable with `std::exception_ptr`, it handles
398  
    If `h1` is also invocable with `std::exception_ptr`, it handles
399  
    exceptions too. Otherwise, exceptions are rethrown.
399  
    exceptions too. Otherwise, exceptions are rethrown.
400  

400  

401  
    @par Thread Safety
401  
    @par Thread Safety
402  
    The calling thread is blocked. The task and handler execute
402  
    The calling thread is blocked. The task and handler execute
403  
    inline on the calling thread.
403  
    inline on the calling thread.
404  

404  

405  
    @par Exception Safety
405  
    @par Exception Safety
406  
    Basic guarantee. Exceptions from the task are passed to `h1`
406  
    Basic guarantee. Exceptions from the task are passed to `h1`
407  
    if it accepts `std::exception_ptr`, otherwise rethrown.
407  
    if it accepts `std::exception_ptr`, otherwise rethrown.
408  

408  

409  
    @par Example
409  
    @par Example
410  
    @code
410  
    @code
411  
    int result = 0;
411  
    int result = 0;
412  
    run_blocking([&](int v) { result = v; })(compute_value());
412  
    run_blocking([&](int v) { result = v; })(compute_value());
413  
    @endcode
413  
    @endcode
414  

414  

415  
    @param h1 Handler invoked with the result on success, and
415  
    @param h1 Handler invoked with the result on success, and
416  
        optionally with `std::exception_ptr` on failure.
416  
        optionally with `std::exception_ptr` on failure.
417  

417  

418  
    @return A wrapper that accepts a task for blocking execution.
418  
    @return A wrapper that accepts a task for blocking execution.
419  

419  

420  
    @see run_async
420  
    @see run_async
421  
*/
421  
*/
422  
template<class H1>
422  
template<class H1>
423  
[[nodiscard]] auto
423  
[[nodiscard]] auto
424  
run_blocking(H1 h1)
424  
run_blocking(H1 h1)
425  
{
425  
{
426  
    return run_blocking_wrapper<
426  
    return run_blocking_wrapper<
427  
        inline_executor,
427  
        inline_executor,
428  
        H1,
428  
        H1,
429  
        detail::default_handler>(
429  
        detail::default_handler>(
430  
            inline_executor{},
430  
            inline_executor{},
431  
            std::stop_token{},
431  
            std::stop_token{},
432  
            std::move(h1),
432  
            std::move(h1),
433  
            detail::default_handler{});
433  
            detail::default_handler{});
434  
}
434  
}
435  

435  

436  
/** Block until task completes with separate handlers.
436  
/** Block until task completes with separate handlers.
437  

437  

438  
    Executes a lazy task using the inline executor and blocks until
438  
    Executes a lazy task using the inline executor and blocks until
439  
    completion. The handler `h1` is called on success, `h2` on failure.
439  
    completion. The handler `h1` is called on success, `h2` on failure.
440  

440  

441  
    @par Thread Safety
441  
    @par Thread Safety
442  
    The calling thread is blocked. The task and handlers execute
442  
    The calling thread is blocked. The task and handlers execute
443  
    inline on the calling thread.
443  
    inline on the calling thread.
444  

444  

445  
    @par Exception Safety
445  
    @par Exception Safety
446  
    Basic guarantee. Exceptions from the task are passed to `h2`.
446  
    Basic guarantee. Exceptions from the task are passed to `h2`.
447  

447  

448  
    @par Example
448  
    @par Example
449  
    @code
449  
    @code
450  
    int result = 0;
450  
    int result = 0;
451  
    run_blocking(
451  
    run_blocking(
452  
        [&](int v) { result = v; },
452  
        [&](int v) { result = v; },
453  
        [](std::exception_ptr ep) {
453  
        [](std::exception_ptr ep) {
454  
            std::rethrow_exception(ep);
454  
            std::rethrow_exception(ep);
455  
        }
455  
        }
456  
    )(compute_value());
456  
    )(compute_value());
457  
    @endcode
457  
    @endcode
458  

458  

459  
    @param h1 Handler invoked with the result on success.
459  
    @param h1 Handler invoked with the result on success.
460  
    @param h2 Handler invoked with the exception on failure.
460  
    @param h2 Handler invoked with the exception on failure.
461  

461  

462  
    @return A wrapper that accepts a task for blocking execution.
462  
    @return A wrapper that accepts a task for blocking execution.
463  

463  

464  
    @see run_async
464  
    @see run_async
465  
*/
465  
*/
466  
template<class H1, class H2>
466  
template<class H1, class H2>
467  
[[nodiscard]] auto
467  
[[nodiscard]] auto
468  
run_blocking(H1 h1, H2 h2)
468  
run_blocking(H1 h1, H2 h2)
469  
{
469  
{
470  
    return run_blocking_wrapper<
470  
    return run_blocking_wrapper<
471  
        inline_executor,
471  
        inline_executor,
472  
        H1,
472  
        H1,
473  
        H2>(
473  
        H2>(
474  
            inline_executor{},
474  
            inline_executor{},
475  
            std::stop_token{},
475  
            std::stop_token{},
476  
            std::move(h1),
476  
            std::move(h1),
477  
            std::move(h2));
477  
            std::move(h2));
478  
}
478  
}
479  

479  

480  
// With explicit executor
480  
// With explicit executor
481  

481  

482  
/** Block until task completes on the given executor.
482  
/** Block until task completes on the given executor.
483  

483  

484  
    Executes a lazy task on the specified executor and blocks the
484  
    Executes a lazy task on the specified executor and blocks the
485  
    calling thread until the task completes.
485  
    calling thread until the task completes.
486  

486  

487  
    @par Thread Safety
487  
    @par Thread Safety
488  
    The calling thread is blocked. The task may execute on
488  
    The calling thread is blocked. The task may execute on
489  
    a different thread depending on the executor.
489  
    a different thread depending on the executor.
490  

490  

491  
    @par Exception Safety
491  
    @par Exception Safety
492  
    Basic guarantee. If the task throws, the exception is
492  
    Basic guarantee. If the task throws, the exception is
493  
    rethrown to the caller.
493  
    rethrown to the caller.
494  

494  

495  
    @par Example
495  
    @par Example
496  
    @code
496  
    @code
497  
    run_blocking(my_executor)(my_void_task());
497  
    run_blocking(my_executor)(my_void_task());
498  
    @endcode
498  
    @endcode
499  

499  

500  
    @param ex The executor to execute the task on.
500  
    @param ex The executor to execute the task on.
501  

501  

502  
    @return A wrapper that accepts a task for blocking execution.
502  
    @return A wrapper that accepts a task for blocking execution.
503  

503  

504  
    @see run_async
504  
    @see run_async
505  
*/
505  
*/
506  
template<Executor Ex>
506  
template<Executor Ex>
507  
[[nodiscard]] auto
507  
[[nodiscard]] auto
508  
run_blocking(Ex ex)
508  
run_blocking(Ex ex)
509  
{
509  
{
510  
    return run_blocking_wrapper<
510  
    return run_blocking_wrapper<
511  
        Ex,
511  
        Ex,
512  
        detail::default_handler,
512  
        detail::default_handler,
513  
        detail::default_handler>(
513  
        detail::default_handler>(
514  
            std::move(ex),
514  
            std::move(ex),
515  
            std::stop_token{},
515  
            std::stop_token{},
516  
            detail::default_handler{},
516  
            detail::default_handler{},
517  
            detail::default_handler{});
517  
            detail::default_handler{});
518  
}
518  
}
519  

519  

520  
/** Block until task completes on executor with handler.
520  
/** Block until task completes on executor with handler.
521  

521  

522  
    Executes a lazy task on the specified executor and blocks until
522  
    Executes a lazy task on the specified executor and blocks until
523  
    completion. The handler `h1` is called with the result.
523  
    completion. The handler `h1` is called with the result.
524  

524  

525  
    @par Thread Safety
525  
    @par Thread Safety
526  
    The calling thread is blocked. The task and handler may
526  
    The calling thread is blocked. The task and handler may
527  
    execute on a different thread depending on the executor.
527  
    execute on a different thread depending on the executor.
528  

528  

529  
    @par Exception Safety
529  
    @par Exception Safety
530  
    Basic guarantee. Exceptions from the task are passed to `h1`
530  
    Basic guarantee. Exceptions from the task are passed to `h1`
531  
    if it accepts `std::exception_ptr`, otherwise rethrown.
531  
    if it accepts `std::exception_ptr`, otherwise rethrown.
532  

532  

533  
    @param ex The executor to execute the task on.
533  
    @param ex The executor to execute the task on.
534  
    @param h1 Handler invoked with the result on success.
534  
    @param h1 Handler invoked with the result on success.
535  

535  

536  
    @return A wrapper that accepts a task for blocking execution.
536  
    @return A wrapper that accepts a task for blocking execution.
537  

537  

538  
    @see run_async
538  
    @see run_async
539  
*/
539  
*/
540  
template<Executor Ex, class H1>
540  
template<Executor Ex, class H1>
541  
[[nodiscard]] auto
541  
[[nodiscard]] auto
542  
run_blocking(Ex ex, H1 h1)
542  
run_blocking(Ex ex, H1 h1)
543  
{
543  
{
544  
    return run_blocking_wrapper<
544  
    return run_blocking_wrapper<
545  
        Ex,
545  
        Ex,
546  
        H1,
546  
        H1,
547  
        detail::default_handler>(
547  
        detail::default_handler>(
548  
            std::move(ex),
548  
            std::move(ex),
549  
            std::stop_token{},
549  
            std::stop_token{},
550  
            std::move(h1),
550  
            std::move(h1),
551  
            detail::default_handler{});
551  
            detail::default_handler{});
552  
}
552  
}
553  

553  

554  
/** Block until task completes on executor with separate handlers.
554  
/** Block until task completes on executor with separate handlers.
555  

555  

556  
    Executes a lazy task on the specified executor and blocks until
556  
    Executes a lazy task on the specified executor and blocks until
557  
    completion. The handler `h1` is called on success, `h2` on failure.
557  
    completion. The handler `h1` is called on success, `h2` on failure.
558  

558  

559  
    @par Thread Safety
559  
    @par Thread Safety
560  
    The calling thread is blocked. The task and handlers may
560  
    The calling thread is blocked. The task and handlers may
561  
    execute on a different thread depending on the executor.
561  
    execute on a different thread depending on the executor.
562  

562  

563  
    @par Exception Safety
563  
    @par Exception Safety
564  
    Basic guarantee. Exceptions from the task are passed to `h2`.
564  
    Basic guarantee. Exceptions from the task are passed to `h2`.
565  

565  

566  
    @param ex The executor to execute the task on.
566  
    @param ex The executor to execute the task on.
567  
    @param h1 Handler invoked with the result on success.
567  
    @param h1 Handler invoked with the result on success.
568  
    @param h2 Handler invoked with the exception on failure.
568  
    @param h2 Handler invoked with the exception on failure.
569  

569  

570  
    @return A wrapper that accepts a task for blocking execution.
570  
    @return A wrapper that accepts a task for blocking execution.
571  

571  

572  
    @see run_async
572  
    @see run_async
573  
*/
573  
*/
574  
template<Executor Ex, class H1, class H2>
574  
template<Executor Ex, class H1, class H2>
575  
[[nodiscard]] auto
575  
[[nodiscard]] auto
576  
run_blocking(Ex ex, H1 h1, H2 h2)
576  
run_blocking(Ex ex, H1 h1, H2 h2)
577  
{
577  
{
578  
    return run_blocking_wrapper<
578  
    return run_blocking_wrapper<
579  
        Ex,
579  
        Ex,
580  
        H1,
580  
        H1,
581  
        H2>(
581  
        H2>(
582  
            std::move(ex),
582  
            std::move(ex),
583  
            std::stop_token{},
583  
            std::stop_token{},
584  
            std::move(h1),
584  
            std::move(h1),
585  
            std::move(h2));
585  
            std::move(h2));
586  
}
586  
}
587  

587  

588  
// With stop_token
588  
// With stop_token
589  

589  

590  
/** Block until task completes with stop token support.
590  
/** Block until task completes with stop token support.
591  

591  

592  
    Executes a lazy task using the inline executor with the given
592  
    Executes a lazy task using the inline executor with the given
593  
    stop token and blocks until completion.
593  
    stop token and blocks until completion.
594  

594  

595  
    @par Thread Safety
595  
    @par Thread Safety
596  
    The calling thread is blocked. The task executes inline
596  
    The calling thread is blocked. The task executes inline
597  
    on the calling thread.
597  
    on the calling thread.
598  

598  

599  
    @par Exception Safety
599  
    @par Exception Safety
600  
    Basic guarantee. If the task throws, the exception is
600  
    Basic guarantee. If the task throws, the exception is
601  
    rethrown to the caller.
601  
    rethrown to the caller.
602  

602  

603  
    @param st The stop token for cooperative cancellation.
603  
    @param st The stop token for cooperative cancellation.
604  

604  

605  
    @return A wrapper that accepts a task for blocking execution.
605  
    @return A wrapper that accepts a task for blocking execution.
606  

606  

607  
    @see run_async
607  
    @see run_async
608  
*/
608  
*/
609  
[[nodiscard]] inline auto
609  
[[nodiscard]] inline auto
610  
run_blocking(std::stop_token st)
610  
run_blocking(std::stop_token st)
611  
{
611  
{
612  
    return run_blocking_wrapper<
612  
    return run_blocking_wrapper<
613  
        inline_executor,
613  
        inline_executor,
614  
        detail::default_handler,
614  
        detail::default_handler,
615  
        detail::default_handler>(
615  
        detail::default_handler>(
616  
            inline_executor{},
616  
            inline_executor{},
617  
            std::move(st),
617  
            std::move(st),
618  
            detail::default_handler{},
618  
            detail::default_handler{},
619  
            detail::default_handler{});
619  
            detail::default_handler{});
620  
}
620  
}
621  

621  

622  
/** Block until task completes with stop token and handler.
622  
/** Block until task completes with stop token and handler.
623  

623  

624  
    @param st The stop token for cooperative cancellation.
624  
    @param st The stop token for cooperative cancellation.
625  
    @param h1 Handler invoked with the result on success.
625  
    @param h1 Handler invoked with the result on success.
626  

626  

627  
    @return A wrapper that accepts a task for blocking execution.
627  
    @return A wrapper that accepts a task for blocking execution.
628  

628  

629  
    @see run_async
629  
    @see run_async
630  
*/
630  
*/
631  
template<class H1>
631  
template<class H1>
632  
[[nodiscard]] auto
632  
[[nodiscard]] auto
633  
run_blocking(std::stop_token st, H1 h1)
633  
run_blocking(std::stop_token st, H1 h1)
634  
{
634  
{
635  
    return run_blocking_wrapper<
635  
    return run_blocking_wrapper<
636  
        inline_executor,
636  
        inline_executor,
637  
        H1,
637  
        H1,
638  
        detail::default_handler>(
638  
        detail::default_handler>(
639  
            inline_executor{},
639  
            inline_executor{},
640  
            std::move(st),
640  
            std::move(st),
641  
            std::move(h1),
641  
            std::move(h1),
642  
            detail::default_handler{});
642  
            detail::default_handler{});
643  
}
643  
}
644  

644  

645  
/** Block until task completes with stop token and separate handlers.
645  
/** Block until task completes with stop token and separate handlers.
646  

646  

647  
    @param st The stop token for cooperative cancellation.
647  
    @param st The stop token for cooperative cancellation.
648  
    @param h1 Handler invoked with the result on success.
648  
    @param h1 Handler invoked with the result on success.
649  
    @param h2 Handler invoked with the exception on failure.
649  
    @param h2 Handler invoked with the exception on failure.
650  

650  

651  
    @return A wrapper that accepts a task for blocking execution.
651  
    @return A wrapper that accepts a task for blocking execution.
652  

652  

653  
    @see run_async
653  
    @see run_async
654  
*/
654  
*/
655  
template<class H1, class H2>
655  
template<class H1, class H2>
656  
[[nodiscard]] auto
656  
[[nodiscard]] auto
657  
run_blocking(std::stop_token st, H1 h1, H2 h2)
657  
run_blocking(std::stop_token st, H1 h1, H2 h2)
658  
{
658  
{
659  
    return run_blocking_wrapper<
659  
    return run_blocking_wrapper<
660  
        inline_executor,
660  
        inline_executor,
661  
        H1,
661  
        H1,
662  
        H2>(
662  
        H2>(
663  
            inline_executor{},
663  
            inline_executor{},
664  
            std::move(st),
664  
            std::move(st),
665  
            std::move(h1),
665  
            std::move(h1),
666  
            std::move(h2));
666  
            std::move(h2));
667  
}
667  
}
668  

668  

669  
// Executor + stop_token
669  
// Executor + stop_token
670  

670  

671  
/** Block until task completes on executor with stop token.
671  
/** Block until task completes on executor with stop token.
672  

672  

673  
    @param ex The executor to execute the task on.
673  
    @param ex The executor to execute the task on.
674  
    @param st The stop token for cooperative cancellation.
674  
    @param st The stop token for cooperative cancellation.
675  

675  

676  
    @return A wrapper that accepts a task for blocking execution.
676  
    @return A wrapper that accepts a task for blocking execution.
677  

677  

678  
    @see run_async
678  
    @see run_async
679  
*/
679  
*/
680  
template<Executor Ex>
680  
template<Executor Ex>
681  
[[nodiscard]] auto
681  
[[nodiscard]] auto
682  
run_blocking(Ex ex, std::stop_token st)
682  
run_blocking(Ex ex, std::stop_token st)
683  
{
683  
{
684  
    return run_blocking_wrapper<
684  
    return run_blocking_wrapper<
685  
        Ex,
685  
        Ex,
686  
        detail::default_handler,
686  
        detail::default_handler,
687  
        detail::default_handler>(
687  
        detail::default_handler>(
688  
            std::move(ex),
688  
            std::move(ex),
689  
            std::move(st),
689  
            std::move(st),
690  
            detail::default_handler{},
690  
            detail::default_handler{},
691  
            detail::default_handler{});
691  
            detail::default_handler{});
692  
}
692  
}
693  

693  

694  
/** Block until task completes on executor with stop token and handler.
694  
/** Block until task completes on executor with stop token and handler.
695  

695  

696  
    @param ex The executor to execute the task on.
696  
    @param ex The executor to execute the task on.
697  
    @param st The stop token for cooperative cancellation.
697  
    @param st The stop token for cooperative cancellation.
698  
    @param h1 Handler invoked with the result on success.
698  
    @param h1 Handler invoked with the result on success.
699  

699  

700  
    @return A wrapper that accepts a task for blocking execution.
700  
    @return A wrapper that accepts a task for blocking execution.
701  

701  

702  
    @see run_async
702  
    @see run_async
703  
*/
703  
*/
704  
template<Executor Ex, class H1>
704  
template<Executor Ex, class H1>
705  
[[nodiscard]] auto
705  
[[nodiscard]] auto
706  
run_blocking(Ex ex, std::stop_token st, H1 h1)
706  
run_blocking(Ex ex, std::stop_token st, H1 h1)
707  
{
707  
{
708  
    return run_blocking_wrapper<
708  
    return run_blocking_wrapper<
709  
        Ex,
709  
        Ex,
710  
        H1,
710  
        H1,
711  
        detail::default_handler>(
711  
        detail::default_handler>(
712  
            std::move(ex),
712  
            std::move(ex),
713  
            std::move(st),
713  
            std::move(st),
714  
            std::move(h1),
714  
            std::move(h1),
715  
            detail::default_handler{});
715  
            detail::default_handler{});
716  
}
716  
}
717  

717  

718  
/** Block until task completes on executor with stop token and handlers.
718  
/** Block until task completes on executor with stop token and handlers.
719  

719  

720  
    @param ex The executor to execute the task on.
720  
    @param ex The executor to execute the task on.
721  
    @param st The stop token for cooperative cancellation.
721  
    @param st The stop token for cooperative cancellation.
722  
    @param h1 Handler invoked with the result on success.
722  
    @param h1 Handler invoked with the result on success.
723  
    @param h2 Handler invoked with the exception on failure.
723  
    @param h2 Handler invoked with the exception on failure.
724  

724  

725  
    @return A wrapper that accepts a task for blocking execution.
725  
    @return A wrapper that accepts a task for blocking execution.
726  

726  

727  
    @see run_async
727  
    @see run_async
728  
*/
728  
*/
729  
template<Executor Ex, class H1, class H2>
729  
template<Executor Ex, class H1, class H2>
730  
[[nodiscard]] auto
730  
[[nodiscard]] auto
731  
run_blocking(Ex ex, std::stop_token st, H1 h1, H2 h2)
731  
run_blocking(Ex ex, std::stop_token st, H1 h1, H2 h2)
732  
{
732  
{
733  
    return run_blocking_wrapper<
733  
    return run_blocking_wrapper<
734  
        Ex,
734  
        Ex,
735  
        H1,
735  
        H1,
736  
        H2>(
736  
        H2>(
737  
            std::move(ex),
737  
            std::move(ex),
738  
            std::move(st),
738  
            std::move(st),
739  
            std::move(h1),
739  
            std::move(h1),
740  
            std::move(h2));
740  
            std::move(h2));
741  
}
741  
}
742  

742  

743  
} // namespace test
743  
} // namespace test
744  
} // namespace capy
744  
} // namespace capy
745  
} // namespace boost
745  
} // namespace boost
746  

746  

747  
#endif
747  
#endif