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_RUN_HPP
10  
#ifndef BOOST_CAPY_RUN_HPP
11  
#define BOOST_CAPY_RUN_HPP
11  
#define BOOST_CAPY_RUN_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/run.hpp>
14  
#include <boost/capy/detail/run.hpp>
15  
#include <boost/capy/concept/executor.hpp>
15  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/concept/io_runnable.hpp>
16  
#include <boost/capy/concept/io_runnable.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
18  
#include <coroutine>
18  
#include <coroutine>
19  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
21  

21  

22  
#include <memory_resource>
22  
#include <memory_resource>
23  
#include <stop_token>
23  
#include <stop_token>
24  
#include <type_traits>
24  
#include <type_traits>
25  
#include <utility>
25  
#include <utility>
26  
#include <variant>
26  
#include <variant>
27  

27  

28  
/*
28  
/*
29  
    Allocator Lifetime Strategy
29  
    Allocator Lifetime Strategy
30  
    ===========================
30  
    ===========================
31  

31  

32  
    When using run() with a custom allocator:
32  
    When using run() with a custom allocator:
33  

33  

34  
        co_await run(ex, alloc)(my_task());
34  
        co_await run(ex, alloc)(my_task());
35  

35  

36  
    The evaluation order is:
36  
    The evaluation order is:
37  
        1. run(ex, alloc) creates a temporary wrapper
37  
        1. run(ex, alloc) creates a temporary wrapper
38  
        2. my_task() allocates its coroutine frame using TLS
38  
        2. my_task() allocates its coroutine frame using TLS
39  
        3. operator() returns an awaitable
39  
        3. operator() returns an awaitable
40  
        4. Wrapper temporary is DESTROYED
40  
        4. Wrapper temporary is DESTROYED
41  
        5. co_await suspends caller, resumes task
41  
        5. co_await suspends caller, resumes task
42  
        6. Task body executes (wrapper is already dead!)
42  
        6. Task body executes (wrapper is already dead!)
43  

43  

44  
    Problem: The wrapper's frame_memory_resource dies before the task
44  
    Problem: The wrapper's frame_memory_resource dies before the task
45  
    body runs. When initial_suspend::await_resume() restores TLS from
45  
    body runs. When initial_suspend::await_resume() restores TLS from
46  
    the saved pointer, it would point to dead memory.
46  
    the saved pointer, it would point to dead memory.
47  

47  

48  
    Solution: Store a COPY of the allocator in the awaitable (not just
48  
    Solution: Store a COPY of the allocator in the awaitable (not just
49  
    the wrapper). The co_await mechanism extends the awaitable's lifetime
49  
    the wrapper). The co_await mechanism extends the awaitable's lifetime
50  
    until the await completes. In await_suspend, we overwrite the promise's
50  
    until the await completes. In await_suspend, we overwrite the promise's
51  
    saved frame_allocator pointer to point to the awaitable's resource.
51  
    saved frame_allocator pointer to point to the awaitable's resource.
52  

52  

53  
    This works because standard allocator copies are equivalent - memory
53  
    This works because standard allocator copies are equivalent - memory
54  
    allocated with one copy can be deallocated with another copy. The
54  
    allocated with one copy can be deallocated with another copy. The
55  
    task's own frame uses the footer-stored pointer (safe), while nested
55  
    task's own frame uses the footer-stored pointer (safe), while nested
56  
    task creation uses TLS pointing to the awaitable's resource (also safe).
56  
    task creation uses TLS pointing to the awaitable's resource (also safe).
57  
*/
57  
*/
58  

58  

59  
namespace boost::capy::detail {
59  
namespace boost::capy::detail {
60  

60  

61  
//----------------------------------------------------------
61  
//----------------------------------------------------------
62  
//
62  
//
63  
// dispatch_trampoline - cross-executor dispatch
63  
// dispatch_trampoline - cross-executor dispatch
64  
//
64  
//
65  
//----------------------------------------------------------
65  
//----------------------------------------------------------
66  

66  

67  
/** Minimal coroutine that dispatches through the caller's executor.
67  
/** Minimal coroutine that dispatches through the caller's executor.
68  

68  

69  
    Sits between the inner task and the parent when executors
69  
    Sits between the inner task and the parent when executors
70  
    diverge. The inner task's `final_suspend` resumes this
70  
    diverge. The inner task's `final_suspend` resumes this
71  
    trampoline via symmetric transfer. The trampoline's own
71  
    trampoline via symmetric transfer. The trampoline's own
72  
    `final_suspend` dispatches the parent through the caller's
72  
    `final_suspend` dispatches the parent through the caller's
73  
    executor to restore the correct execution context.
73  
    executor to restore the correct execution context.
74  

74  

75  
    The trampoline never touches the task's result.
75  
    The trampoline never touches the task's result.
76  
*/
76  
*/
77  
struct dispatch_trampoline
77  
struct dispatch_trampoline
78  
{
78  
{
79  
    struct promise_type
79  
    struct promise_type
80  
    {
80  
    {
81  
        executor_ref caller_ex_;
81  
        executor_ref caller_ex_;
82  
        std::coroutine_handle<> parent_;
82  
        std::coroutine_handle<> parent_;
83  

83  

84  
        dispatch_trampoline get_return_object() noexcept
84  
        dispatch_trampoline get_return_object() noexcept
85  
        {
85  
        {
86  
            return dispatch_trampoline{
86  
            return dispatch_trampoline{
87  
                std::coroutine_handle<promise_type>::from_promise(*this)};
87  
                std::coroutine_handle<promise_type>::from_promise(*this)};
88  
        }
88  
        }
89  

89  

90  
        std::suspend_always initial_suspend() noexcept { return {}; }
90  
        std::suspend_always initial_suspend() noexcept { return {}; }
91  

91  

92  
        auto final_suspend() noexcept
92  
        auto final_suspend() noexcept
93  
        {
93  
        {
94  
            struct awaiter
94  
            struct awaiter
95  
            {
95  
            {
96  
                promise_type* p_;
96  
                promise_type* p_;
97  
                bool await_ready() const noexcept { return false; }
97  
                bool await_ready() const noexcept { return false; }
98  

98  

99  
                std::coroutine_handle<> await_suspend(
99  
                std::coroutine_handle<> await_suspend(
100  
                    std::coroutine_handle<>) noexcept
100  
                    std::coroutine_handle<>) noexcept
101  
                {
101  
                {
102  
                    return p_->caller_ex_.dispatch(p_->parent_);
102  
                    return p_->caller_ex_.dispatch(p_->parent_);
103  
                }
103  
                }
104  

104  

105  
                void await_resume() const noexcept {}
105  
                void await_resume() const noexcept {}
106  
            };
106  
            };
107  
            return awaiter{this};
107  
            return awaiter{this};
108  
        }
108  
        }
109  

109  

110  
        void return_void() noexcept {}
110  
        void return_void() noexcept {}
111  
        void unhandled_exception() noexcept {}
111  
        void unhandled_exception() noexcept {}
112  
    };
112  
    };
113  

113  

114  
    std::coroutine_handle<promise_type> h_{nullptr};
114  
    std::coroutine_handle<promise_type> h_{nullptr};
115  

115  

116  
    dispatch_trampoline() noexcept = default;
116  
    dispatch_trampoline() noexcept = default;
117  

117  

118  
    ~dispatch_trampoline()
118  
    ~dispatch_trampoline()
119  
    {
119  
    {
120  
        if(h_) h_.destroy();
120  
        if(h_) h_.destroy();
121  
    }
121  
    }
122  

122  

123  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
123  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
124  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
124  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
125  

125  

126  
    dispatch_trampoline(dispatch_trampoline&& o) noexcept
126  
    dispatch_trampoline(dispatch_trampoline&& o) noexcept
127  
        : h_(std::exchange(o.h_, nullptr)) {}
127  
        : h_(std::exchange(o.h_, nullptr)) {}
128  

128  

129  
    dispatch_trampoline& operator=(dispatch_trampoline&& o) noexcept
129  
    dispatch_trampoline& operator=(dispatch_trampoline&& o) noexcept
130  
    {
130  
    {
131  
        if(this != &o)
131  
        if(this != &o)
132  
        {
132  
        {
133  
            if(h_) h_.destroy();
133  
            if(h_) h_.destroy();
134  
            h_ = std::exchange(o.h_, nullptr);
134  
            h_ = std::exchange(o.h_, nullptr);
135  
        }
135  
        }
136  
        return *this;
136  
        return *this;
137  
    }
137  
    }
138  

138  

139  
private:
139  
private:
140  
    explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
140  
    explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
141  
        : h_(h) {}
141  
        : h_(h) {}
142  
};
142  
};
143  

143  

144  
inline dispatch_trampoline make_dispatch_trampoline()
144  
inline dispatch_trampoline make_dispatch_trampoline()
145  
{
145  
{
146  
    co_return;
146  
    co_return;
147  
}
147  
}
148  

148  

149  
//----------------------------------------------------------
149  
//----------------------------------------------------------
150  
//
150  
//
151  
// run_awaitable_ex - with executor (executor switch)
151  
// run_awaitable_ex - with executor (executor switch)
152  
//
152  
//
153  
//----------------------------------------------------------
153  
//----------------------------------------------------------
154  

154  

155  
/** Awaitable that binds an IoRunnable to a specific executor.
155  
/** Awaitable that binds an IoRunnable to a specific executor.
156  

156  

157  
    Stores the executor and inner task by value. When co_awaited, the
157  
    Stores the executor and inner task by value. When co_awaited, the
158  
    co_await expression's lifetime extension keeps both alive for the
158  
    co_await expression's lifetime extension keeps both alive for the
159  
    duration of the operation.
159  
    duration of the operation.
160  

160  

161  
    A dispatch trampoline handles the executor switch on completion:
161  
    A dispatch trampoline handles the executor switch on completion:
162  
    the inner task's `final_suspend` resumes the trampoline, which
162  
    the inner task's `final_suspend` resumes the trampoline, which
163  
    dispatches back through the caller's executor.
163  
    dispatches back through the caller's executor.
164  

164  

165  
    The `io_env` is owned by this awaitable and is guaranteed to
165  
    The `io_env` is owned by this awaitable and is guaranteed to
166  
    outlive the inner task and all awaitables in its chain. Awaitables
166  
    outlive the inner task and all awaitables in its chain. Awaitables
167  
    may store `io_env const*` without concern for dangling references.
167  
    may store `io_env const*` without concern for dangling references.
168  

168  

169  
    @tparam Task The IoRunnable type
169  
    @tparam Task The IoRunnable type
170  
    @tparam Ex The executor type
170  
    @tparam Ex The executor type
171  
    @tparam InheritStopToken If true, inherit caller's stop token
171  
    @tparam InheritStopToken If true, inherit caller's stop token
172  
    @tparam Alloc The allocator type (void for no allocator)
172  
    @tparam Alloc The allocator type (void for no allocator)
173  
*/
173  
*/
174  
template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
174  
template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
175  
struct [[nodiscard]] run_awaitable_ex
175  
struct [[nodiscard]] run_awaitable_ex
176  
{
176  
{
177  
    Ex ex_;
177  
    Ex ex_;
178  
    frame_memory_resource<Alloc> resource_;
178  
    frame_memory_resource<Alloc> resource_;
179  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
179  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
180  
    io_env env_;
180  
    io_env env_;
181  
    dispatch_trampoline tr_;
181  
    dispatch_trampoline tr_;
182  
    Task inner_;  // Last: destroyed first, while env_ is still valid
182  
    Task inner_;  // Last: destroyed first, while env_ is still valid
183  

183  

184  
    // void allocator, inherit stop token
184  
    // void allocator, inherit stop token
185  
    run_awaitable_ex(Ex ex, Task inner)
185  
    run_awaitable_ex(Ex ex, Task inner)
186  
        requires (InheritStopToken && std::is_void_v<Alloc>)
186  
        requires (InheritStopToken && std::is_void_v<Alloc>)
187  
        : ex_(std::move(ex))
187  
        : ex_(std::move(ex))
188  
        , inner_(std::move(inner))
188  
        , inner_(std::move(inner))
189  
    {
189  
    {
190  
    }
190  
    }
191  

191  

192  
    // void allocator, explicit stop token
192  
    // void allocator, explicit stop token
193  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
193  
    run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
194  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
194  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
195  
        : ex_(std::move(ex))
195  
        : ex_(std::move(ex))
196  
        , st_(std::move(st))
196  
        , st_(std::move(st))
197  
        , inner_(std::move(inner))
197  
        , inner_(std::move(inner))
198  
    {
198  
    {
199  
    }
199  
    }
200  

200  

201  
    // with allocator, inherit stop token (use template to avoid void parameter)
201  
    // with allocator, inherit stop token (use template to avoid void parameter)
202  
    template<class A>
202  
    template<class A>
203  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
203  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
204  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
204  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
205  
        : ex_(std::move(ex))
205  
        : ex_(std::move(ex))
206  
        , resource_(std::move(alloc))
206  
        , resource_(std::move(alloc))
207  
        , inner_(std::move(inner))
207  
        , inner_(std::move(inner))
208  
    {
208  
    {
209  
    }
209  
    }
210  

210  

211  
    // with allocator, explicit stop token (use template to avoid void parameter)
211  
    // with allocator, explicit stop token (use template to avoid void parameter)
212  
    template<class A>
212  
    template<class A>
213  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
213  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
214  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
214  
    run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
215  
        : ex_(std::move(ex))
215  
        : ex_(std::move(ex))
216  
        , resource_(std::move(alloc))
216  
        , resource_(std::move(alloc))
217  
        , st_(std::move(st))
217  
        , st_(std::move(st))
218  
        , inner_(std::move(inner))
218  
        , inner_(std::move(inner))
219  
    {
219  
    {
220  
    }
220  
    }
221  

221  

222  
    bool await_ready() const noexcept
222  
    bool await_ready() const noexcept
223  
    {
223  
    {
224  
        return inner_.await_ready();
224  
        return inner_.await_ready();
225  
    }
225  
    }
226  

226  

227  
    decltype(auto) await_resume()
227  
    decltype(auto) await_resume()
228  
    {
228  
    {
229  
        return inner_.await_resume();
229  
        return inner_.await_resume();
230  
    }
230  
    }
231  

231  

232  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
232  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
233  
    {
233  
    {
234  
        tr_ = make_dispatch_trampoline();
234  
        tr_ = make_dispatch_trampoline();
235  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
235  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
236  
        tr_.h_.promise().parent_ = cont;
236  
        tr_.h_.promise().parent_ = cont;
237  

237  

238  
        auto h = inner_.handle();
238  
        auto h = inner_.handle();
239  
        auto& p = h.promise();
239  
        auto& p = h.promise();
240  
        p.set_continuation(tr_.h_);
240  
        p.set_continuation(tr_.h_);
241  

241  

242  
        env_.executor = ex_;
242  
        env_.executor = ex_;
243  
        if constexpr (InheritStopToken)
243  
        if constexpr (InheritStopToken)
244  
            env_.stop_token = caller_env->stop_token;
244  
            env_.stop_token = caller_env->stop_token;
245  
        else
245  
        else
246  
            env_.stop_token = st_;
246  
            env_.stop_token = st_;
247  

247  

248  
        if constexpr (!std::is_void_v<Alloc>)
248  
        if constexpr (!std::is_void_v<Alloc>)
249  
            env_.allocator = resource_.get();
249  
            env_.allocator = resource_.get();
250  
        else
250  
        else
251  
            env_.allocator = caller_env->allocator;
251  
            env_.allocator = caller_env->allocator;
252  

252  

253  
        p.set_environment(&env_);
253  
        p.set_environment(&env_);
254  
        return h;
254  
        return h;
255  
    }
255  
    }
256  

256  

257  
    // Non-copyable
257  
    // Non-copyable
258  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
258  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
259  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
259  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
260  

260  

261  
    // Movable (no noexcept - Task may throw)
261  
    // Movable (no noexcept - Task may throw)
262  
    run_awaitable_ex(run_awaitable_ex&&) = default;
262  
    run_awaitable_ex(run_awaitable_ex&&) = default;
263  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
263  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
264  
};
264  
};
265  

265  

266  
//----------------------------------------------------------
266  
//----------------------------------------------------------
267  
//
267  
//
268  
// run_awaitable - no executor (inherits caller's executor)
268  
// run_awaitable - no executor (inherits caller's executor)
269  
//
269  
//
270  
//----------------------------------------------------------
270  
//----------------------------------------------------------
271  

271  

272  
/** Awaitable that runs a task with optional stop_token override.
272  
/** Awaitable that runs a task with optional stop_token override.
273  

273  

274  
    Does NOT store an executor - the task inherits the caller's executor
274  
    Does NOT store an executor - the task inherits the caller's executor
275  
    directly. Executors always match, so no dispatch trampoline is needed.
275  
    directly. Executors always match, so no dispatch trampoline is needed.
276  
    The inner task's `final_suspend` resumes the parent directly via
276  
    The inner task's `final_suspend` resumes the parent directly via
277  
    unconditional symmetric transfer.
277  
    unconditional symmetric transfer.
278  

278  

279  
    @tparam Task The IoRunnable type
279  
    @tparam Task The IoRunnable type
280  
    @tparam InheritStopToken If true, inherit caller's stop token
280  
    @tparam InheritStopToken If true, inherit caller's stop token
281  
    @tparam Alloc The allocator type (void for no allocator)
281  
    @tparam Alloc The allocator type (void for no allocator)
282  
*/
282  
*/
283  
template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
283  
template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
284  
struct [[nodiscard]] run_awaitable
284  
struct [[nodiscard]] run_awaitable
285  
{
285  
{
286  
    frame_memory_resource<Alloc> resource_;
286  
    frame_memory_resource<Alloc> resource_;
287  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
287  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
288  
    io_env env_;
288  
    io_env env_;
289  
    Task inner_;  // Last: destroyed first, while env_ is still valid
289  
    Task inner_;  // Last: destroyed first, while env_ is still valid
290  

290  

291  
    // void allocator, inherit stop token
291  
    // void allocator, inherit stop token
292  
    explicit run_awaitable(Task inner)
292  
    explicit run_awaitable(Task inner)
293  
        requires (InheritStopToken && std::is_void_v<Alloc>)
293  
        requires (InheritStopToken && std::is_void_v<Alloc>)
294  
        : inner_(std::move(inner))
294  
        : inner_(std::move(inner))
295  
    {
295  
    {
296  
    }
296  
    }
297  

297  

298  
    // void allocator, explicit stop token
298  
    // void allocator, explicit stop token
299  
    run_awaitable(Task inner, std::stop_token st)
299  
    run_awaitable(Task inner, std::stop_token st)
300  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
300  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
301  
        : st_(std::move(st))
301  
        : st_(std::move(st))
302  
        , inner_(std::move(inner))
302  
        , inner_(std::move(inner))
303  
    {
303  
    {
304  
    }
304  
    }
305  

305  

306  
    // with allocator, inherit stop token (use template to avoid void parameter)
306  
    // with allocator, inherit stop token (use template to avoid void parameter)
307  
    template<class A>
307  
    template<class A>
308  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
308  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
309  
    run_awaitable(A alloc, Task inner)
309  
    run_awaitable(A alloc, Task inner)
310  
        : resource_(std::move(alloc))
310  
        : resource_(std::move(alloc))
311  
        , inner_(std::move(inner))
311  
        , inner_(std::move(inner))
312  
    {
312  
    {
313  
    }
313  
    }
314  

314  

315  
    // with allocator, explicit stop token (use template to avoid void parameter)
315  
    // with allocator, explicit stop token (use template to avoid void parameter)
316  
    template<class A>
316  
    template<class A>
317  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
317  
        requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
318  
    run_awaitable(A alloc, Task inner, std::stop_token st)
318  
    run_awaitable(A alloc, Task inner, std::stop_token st)
319  
        : resource_(std::move(alloc))
319  
        : resource_(std::move(alloc))
320  
        , st_(std::move(st))
320  
        , st_(std::move(st))
321  
        , inner_(std::move(inner))
321  
        , inner_(std::move(inner))
322  
    {
322  
    {
323  
    }
323  
    }
324  

324  

325  
    bool await_ready() const noexcept
325  
    bool await_ready() const noexcept
326  
    {
326  
    {
327  
        return inner_.await_ready();
327  
        return inner_.await_ready();
328  
    }
328  
    }
329  

329  

330  
    decltype(auto) await_resume()
330  
    decltype(auto) await_resume()
331  
    {
331  
    {
332  
        return inner_.await_resume();
332  
        return inner_.await_resume();
333  
    }
333  
    }
334  

334  

335  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
335  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
336  
    {
336  
    {
337  
        auto h = inner_.handle();
337  
        auto h = inner_.handle();
338  
        auto& p = h.promise();
338  
        auto& p = h.promise();
339  
        p.set_continuation(cont);
339  
        p.set_continuation(cont);
340  

340  

341  
        env_.executor = caller_env->executor;
341  
        env_.executor = caller_env->executor;
342  
        if constexpr (InheritStopToken)
342  
        if constexpr (InheritStopToken)
343  
            env_.stop_token = caller_env->stop_token;
343  
            env_.stop_token = caller_env->stop_token;
344  
        else
344  
        else
345  
            env_.stop_token = st_;
345  
            env_.stop_token = st_;
346  

346  

347  
        if constexpr (!std::is_void_v<Alloc>)
347  
        if constexpr (!std::is_void_v<Alloc>)
348  
            env_.allocator = resource_.get();
348  
            env_.allocator = resource_.get();
349  
        else
349  
        else
350  
            env_.allocator = caller_env->allocator;
350  
            env_.allocator = caller_env->allocator;
351  

351  

352  
        p.set_environment(&env_);
352  
        p.set_environment(&env_);
353  
        return h;
353  
        return h;
354  
    }
354  
    }
355  

355  

356  
    // Non-copyable
356  
    // Non-copyable
357  
    run_awaitable(run_awaitable const&) = delete;
357  
    run_awaitable(run_awaitable const&) = delete;
358  
    run_awaitable& operator=(run_awaitable const&) = delete;
358  
    run_awaitable& operator=(run_awaitable const&) = delete;
359  

359  

360  
    // Movable (no noexcept - Task may throw)
360  
    // Movable (no noexcept - Task may throw)
361  
    run_awaitable(run_awaitable&&) = default;
361  
    run_awaitable(run_awaitable&&) = default;
362  
    run_awaitable& operator=(run_awaitable&&) = default;
362  
    run_awaitable& operator=(run_awaitable&&) = default;
363  
};
363  
};
364  

364  

365  
//----------------------------------------------------------
365  
//----------------------------------------------------------
366  
//
366  
//
367  
// run_wrapper_ex - with executor
367  
// run_wrapper_ex - with executor
368  
//
368  
//
369  
//----------------------------------------------------------
369  
//----------------------------------------------------------
370  

370  

371  
/** Wrapper returned by run(ex, ...) that accepts a task for execution.
371  
/** Wrapper returned by run(ex, ...) that accepts a task for execution.
372  

372  

373  
    @tparam Ex The executor type.
373  
    @tparam Ex The executor type.
374  
    @tparam InheritStopToken If true, inherit caller's stop token.
374  
    @tparam InheritStopToken If true, inherit caller's stop token.
375  
    @tparam Alloc The allocator type (void for no allocator).
375  
    @tparam Alloc The allocator type (void for no allocator).
376  
*/
376  
*/
377  
template<Executor Ex, bool InheritStopToken, class Alloc>
377  
template<Executor Ex, bool InheritStopToken, class Alloc>
378  
class [[nodiscard]] run_wrapper_ex
378  
class [[nodiscard]] run_wrapper_ex
379  
{
379  
{
380  
    Ex ex_;
380  
    Ex ex_;
381  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
381  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
382  
    frame_memory_resource<Alloc> resource_;
382  
    frame_memory_resource<Alloc> resource_;
383  
    Alloc alloc_;  // Copy to pass to awaitable
383  
    Alloc alloc_;  // Copy to pass to awaitable
384  

384  

385  
public:
385  
public:
386  
    run_wrapper_ex(Ex ex, Alloc alloc)
386  
    run_wrapper_ex(Ex ex, Alloc alloc)
387  
        requires InheritStopToken
387  
        requires InheritStopToken
388  
        : ex_(std::move(ex))
388  
        : ex_(std::move(ex))
389  
        , resource_(alloc)
389  
        , resource_(alloc)
390  
        , alloc_(std::move(alloc))
390  
        , alloc_(std::move(alloc))
391  
    {
391  
    {
392  
        current_frame_allocator() = &resource_;
392  
        current_frame_allocator() = &resource_;
393  
    }
393  
    }
394  

394  

395  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
395  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
396  
        requires (!InheritStopToken)
396  
        requires (!InheritStopToken)
397  
        : ex_(std::move(ex))
397  
        : ex_(std::move(ex))
398  
        , st_(std::move(st))
398  
        , st_(std::move(st))
399  
        , resource_(alloc)
399  
        , resource_(alloc)
400  
        , alloc_(std::move(alloc))
400  
        , alloc_(std::move(alloc))
401  
    {
401  
    {
402  
        current_frame_allocator() = &resource_;
402  
        current_frame_allocator() = &resource_;
403  
    }
403  
    }
404  

404  

405  
    // Non-copyable, non-movable (must be used immediately)
405  
    // Non-copyable, non-movable (must be used immediately)
406  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
406  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
407  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
407  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
408  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
408  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
409  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
409  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
410  

410  

411  
    template<IoRunnable Task>
411  
    template<IoRunnable Task>
412  
    [[nodiscard]] auto operator()(Task t) &&
412  
    [[nodiscard]] auto operator()(Task t) &&
413  
    {
413  
    {
414  
        if constexpr (InheritStopToken)
414  
        if constexpr (InheritStopToken)
415  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
415  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
416  
                std::move(ex_), std::move(alloc_), std::move(t)};
416  
                std::move(ex_), std::move(alloc_), std::move(t)};
417  
        else
417  
        else
418  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
418  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
419  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
419  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
420  
    }
420  
    }
421  
};
421  
};
422  

422  

423  
/// Specialization for memory_resource* - stores pointer directly.
423  
/// Specialization for memory_resource* - stores pointer directly.
424  
template<Executor Ex, bool InheritStopToken>
424  
template<Executor Ex, bool InheritStopToken>
425  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
425  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
426  
{
426  
{
427  
    Ex ex_;
427  
    Ex ex_;
428  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
428  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
429  
    std::pmr::memory_resource* mr_;
429  
    std::pmr::memory_resource* mr_;
430  

430  

431  
public:
431  
public:
432  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
432  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
433  
        requires InheritStopToken
433  
        requires InheritStopToken
434  
        : ex_(std::move(ex))
434  
        : ex_(std::move(ex))
435  
        , mr_(mr)
435  
        , mr_(mr)
436  
    {
436  
    {
437  
        current_frame_allocator() = mr_;
437  
        current_frame_allocator() = mr_;
438  
    }
438  
    }
439  

439  

440  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
440  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
441  
        requires (!InheritStopToken)
441  
        requires (!InheritStopToken)
442  
        : ex_(std::move(ex))
442  
        : ex_(std::move(ex))
443  
        , st_(std::move(st))
443  
        , st_(std::move(st))
444  
        , mr_(mr)
444  
        , mr_(mr)
445  
    {
445  
    {
446  
        current_frame_allocator() = mr_;
446  
        current_frame_allocator() = mr_;
447  
    }
447  
    }
448  

448  

449  
    // Non-copyable, non-movable (must be used immediately)
449  
    // Non-copyable, non-movable (must be used immediately)
450  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
450  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
451  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
451  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
452  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
452  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
453  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
453  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
454  

454  

455  
    template<IoRunnable Task>
455  
    template<IoRunnable Task>
456  
    [[nodiscard]] auto operator()(Task t) &&
456  
    [[nodiscard]] auto operator()(Task t) &&
457  
    {
457  
    {
458  
        if constexpr (InheritStopToken)
458  
        if constexpr (InheritStopToken)
459  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
459  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
460  
                std::move(ex_), mr_, std::move(t)};
460  
                std::move(ex_), mr_, std::move(t)};
461  
        else
461  
        else
462  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
462  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
463  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
463  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
464  
    }
464  
    }
465  
};
465  
};
466  

466  

467  
/// Specialization for no allocator (void).
467  
/// Specialization for no allocator (void).
468  
template<Executor Ex, bool InheritStopToken>
468  
template<Executor Ex, bool InheritStopToken>
469  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
469  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
470  
{
470  
{
471  
    Ex ex_;
471  
    Ex ex_;
472  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
472  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
473  

473  

474  
public:
474  
public:
475  
    explicit run_wrapper_ex(Ex ex)
475  
    explicit run_wrapper_ex(Ex ex)
476  
        requires InheritStopToken
476  
        requires InheritStopToken
477  
        : ex_(std::move(ex))
477  
        : ex_(std::move(ex))
478  
    {
478  
    {
479  
    }
479  
    }
480  

480  

481  
    run_wrapper_ex(Ex ex, std::stop_token st)
481  
    run_wrapper_ex(Ex ex, std::stop_token st)
482  
        requires (!InheritStopToken)
482  
        requires (!InheritStopToken)
483  
        : ex_(std::move(ex))
483  
        : ex_(std::move(ex))
484  
        , st_(std::move(st))
484  
        , st_(std::move(st))
485  
    {
485  
    {
486  
    }
486  
    }
487  

487  

488  
    // Non-copyable, non-movable (must be used immediately)
488  
    // Non-copyable, non-movable (must be used immediately)
489  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
489  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
490  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
490  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
491  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
491  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
492  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
492  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
493  

493  

494  
    template<IoRunnable Task>
494  
    template<IoRunnable Task>
495  
    [[nodiscard]] auto operator()(Task t) &&
495  
    [[nodiscard]] auto operator()(Task t) &&
496  
    {
496  
    {
497  
        if constexpr (InheritStopToken)
497  
        if constexpr (InheritStopToken)
498  
            return run_awaitable_ex<Task, Ex, true>{
498  
            return run_awaitable_ex<Task, Ex, true>{
499  
                std::move(ex_), std::move(t)};
499  
                std::move(ex_), std::move(t)};
500  
        else
500  
        else
501  
            return run_awaitable_ex<Task, Ex, false>{
501  
            return run_awaitable_ex<Task, Ex, false>{
502  
                std::move(ex_), std::move(t), std::move(st_)};
502  
                std::move(ex_), std::move(t), std::move(st_)};
503  
    }
503  
    }
504  
};
504  
};
505  

505  

506  
//----------------------------------------------------------
506  
//----------------------------------------------------------
507  
//
507  
//
508  
// run_wrapper - no executor (inherits caller's executor)
508  
// run_wrapper - no executor (inherits caller's executor)
509  
//
509  
//
510  
//----------------------------------------------------------
510  
//----------------------------------------------------------
511  

511  

512  
/** Wrapper returned by run(st) or run(alloc) that accepts a task.
512  
/** Wrapper returned by run(st) or run(alloc) that accepts a task.
513  

513  

514  
    @tparam InheritStopToken If true, inherit caller's stop token.
514  
    @tparam InheritStopToken If true, inherit caller's stop token.
515  
    @tparam Alloc The allocator type (void for no allocator).
515  
    @tparam Alloc The allocator type (void for no allocator).
516  
*/
516  
*/
517  
template<bool InheritStopToken, class Alloc>
517  
template<bool InheritStopToken, class Alloc>
518  
class [[nodiscard]] run_wrapper
518  
class [[nodiscard]] run_wrapper
519  
{
519  
{
520  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
520  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
521  
    frame_memory_resource<Alloc> resource_;
521  
    frame_memory_resource<Alloc> resource_;
522  
    Alloc alloc_;  // Copy to pass to awaitable
522  
    Alloc alloc_;  // Copy to pass to awaitable
523  

523  

524  
public:
524  
public:
525  
    explicit run_wrapper(Alloc alloc)
525  
    explicit run_wrapper(Alloc alloc)
526  
        requires InheritStopToken
526  
        requires InheritStopToken
527  
        : resource_(alloc)
527  
        : resource_(alloc)
528  
        , alloc_(std::move(alloc))
528  
        , alloc_(std::move(alloc))
529  
    {
529  
    {
530  
        current_frame_allocator() = &resource_;
530  
        current_frame_allocator() = &resource_;
531  
    }
531  
    }
532  

532  

533  
    run_wrapper(std::stop_token st, Alloc alloc)
533  
    run_wrapper(std::stop_token st, Alloc alloc)
534  
        requires (!InheritStopToken)
534  
        requires (!InheritStopToken)
535  
        : st_(std::move(st))
535  
        : st_(std::move(st))
536  
        , resource_(alloc)
536  
        , resource_(alloc)
537  
        , alloc_(std::move(alloc))
537  
        , alloc_(std::move(alloc))
538  
    {
538  
    {
539  
        current_frame_allocator() = &resource_;
539  
        current_frame_allocator() = &resource_;
540  
    }
540  
    }
541  

541  

542  
    // Non-copyable, non-movable (must be used immediately)
542  
    // Non-copyable, non-movable (must be used immediately)
543  
    run_wrapper(run_wrapper const&) = delete;
543  
    run_wrapper(run_wrapper const&) = delete;
544  
    run_wrapper(run_wrapper&&) = delete;
544  
    run_wrapper(run_wrapper&&) = delete;
545  
    run_wrapper& operator=(run_wrapper const&) = delete;
545  
    run_wrapper& operator=(run_wrapper const&) = delete;
546  
    run_wrapper& operator=(run_wrapper&&) = delete;
546  
    run_wrapper& operator=(run_wrapper&&) = delete;
547  

547  

548  
    template<IoRunnable Task>
548  
    template<IoRunnable Task>
549  
    [[nodiscard]] auto operator()(Task t) &&
549  
    [[nodiscard]] auto operator()(Task t) &&
550  
    {
550  
    {
551  
        if constexpr (InheritStopToken)
551  
        if constexpr (InheritStopToken)
552  
            return run_awaitable<Task, true, Alloc>{
552  
            return run_awaitable<Task, true, Alloc>{
553  
                std::move(alloc_), std::move(t)};
553  
                std::move(alloc_), std::move(t)};
554  
        else
554  
        else
555  
            return run_awaitable<Task, false, Alloc>{
555  
            return run_awaitable<Task, false, Alloc>{
556  
                std::move(alloc_), std::move(t), std::move(st_)};
556  
                std::move(alloc_), std::move(t), std::move(st_)};
557  
    }
557  
    }
558  
};
558  
};
559  

559  

560  
/// Specialization for memory_resource* - stores pointer directly.
560  
/// Specialization for memory_resource* - stores pointer directly.
561  
template<bool InheritStopToken>
561  
template<bool InheritStopToken>
562  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
562  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
563  
{
563  
{
564  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
564  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
565  
    std::pmr::memory_resource* mr_;
565  
    std::pmr::memory_resource* mr_;
566  

566  

567  
public:
567  
public:
568  
    explicit run_wrapper(std::pmr::memory_resource* mr)
568  
    explicit run_wrapper(std::pmr::memory_resource* mr)
569  
        requires InheritStopToken
569  
        requires InheritStopToken
570  
        : mr_(mr)
570  
        : mr_(mr)
571  
    {
571  
    {
572  
        current_frame_allocator() = mr_;
572  
        current_frame_allocator() = mr_;
573  
    }
573  
    }
574  

574  

575  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
575  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
576  
        requires (!InheritStopToken)
576  
        requires (!InheritStopToken)
577  
        : st_(std::move(st))
577  
        : st_(std::move(st))
578  
        , mr_(mr)
578  
        , mr_(mr)
579  
    {
579  
    {
580  
        current_frame_allocator() = mr_;
580  
        current_frame_allocator() = mr_;
581  
    }
581  
    }
582  

582  

583  
    // Non-copyable, non-movable (must be used immediately)
583  
    // Non-copyable, non-movable (must be used immediately)
584  
    run_wrapper(run_wrapper const&) = delete;
584  
    run_wrapper(run_wrapper const&) = delete;
585  
    run_wrapper(run_wrapper&&) = delete;
585  
    run_wrapper(run_wrapper&&) = delete;
586  
    run_wrapper& operator=(run_wrapper const&) = delete;
586  
    run_wrapper& operator=(run_wrapper const&) = delete;
587  
    run_wrapper& operator=(run_wrapper&&) = delete;
587  
    run_wrapper& operator=(run_wrapper&&) = delete;
588  

588  

589  
    template<IoRunnable Task>
589  
    template<IoRunnable Task>
590  
    [[nodiscard]] auto operator()(Task t) &&
590  
    [[nodiscard]] auto operator()(Task t) &&
591  
    {
591  
    {
592  
        if constexpr (InheritStopToken)
592  
        if constexpr (InheritStopToken)
593  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
593  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
594  
                mr_, std::move(t)};
594  
                mr_, std::move(t)};
595  
        else
595  
        else
596  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
596  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
597  
                mr_, std::move(t), std::move(st_)};
597  
                mr_, std::move(t), std::move(st_)};
598  
    }
598  
    }
599  
};
599  
};
600  

600  

601  
/// Specialization for stop_token only (no allocator).
601  
/// Specialization for stop_token only (no allocator).
602  
template<>
602  
template<>
603  
class [[nodiscard]] run_wrapper<false, void>
603  
class [[nodiscard]] run_wrapper<false, void>
604  
{
604  
{
605  
    std::stop_token st_;
605  
    std::stop_token st_;
606  

606  

607  
public:
607  
public:
608  
    explicit run_wrapper(std::stop_token st)
608  
    explicit run_wrapper(std::stop_token st)
609  
        : st_(std::move(st))
609  
        : st_(std::move(st))
610  
    {
610  
    {
611  
    }
611  
    }
612  

612  

613  
    // Non-copyable, non-movable (must be used immediately)
613  
    // Non-copyable, non-movable (must be used immediately)
614  
    run_wrapper(run_wrapper const&) = delete;
614  
    run_wrapper(run_wrapper const&) = delete;
615  
    run_wrapper(run_wrapper&&) = delete;
615  
    run_wrapper(run_wrapper&&) = delete;
616  
    run_wrapper& operator=(run_wrapper const&) = delete;
616  
    run_wrapper& operator=(run_wrapper const&) = delete;
617  
    run_wrapper& operator=(run_wrapper&&) = delete;
617  
    run_wrapper& operator=(run_wrapper&&) = delete;
618  

618  

619  
    template<IoRunnable Task>
619  
    template<IoRunnable Task>
620  
    [[nodiscard]] auto operator()(Task t) &&
620  
    [[nodiscard]] auto operator()(Task t) &&
621  
    {
621  
    {
622  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
622  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
623  
    }
623  
    }
624  
};
624  
};
625  

625  

626  
} // namespace boost::capy::detail
626  
} // namespace boost::capy::detail
627  

627  

628  
namespace boost::capy {
628  
namespace boost::capy {
629  

629  

630  
//----------------------------------------------------------
630  
//----------------------------------------------------------
631  
//
631  
//
632  
// run() overloads - with executor
632  
// run() overloads - with executor
633  
//
633  
//
634  
//----------------------------------------------------------
634  
//----------------------------------------------------------
635  

635  

636  
/** Bind a task to execute on a specific executor.
636  
/** Bind a task to execute on a specific executor.
637  

637  

638  
    Returns a wrapper that accepts a task and produces an awaitable.
638  
    Returns a wrapper that accepts a task and produces an awaitable.
639  
    When co_awaited, the task runs on the specified executor.
639  
    When co_awaited, the task runs on the specified executor.
640  

640  

641  
    @par Example
641  
    @par Example
642  
    @code
642  
    @code
643  
    co_await run(other_executor)(my_task());
643  
    co_await run(other_executor)(my_task());
644  
    @endcode
644  
    @endcode
645  

645  

646  
    @param ex The executor on which the task should run.
646  
    @param ex The executor on which the task should run.
647  

647  

648  
    @return A wrapper that accepts a task for execution.
648  
    @return A wrapper that accepts a task for execution.
649  

649  

650  
    @see task
650  
    @see task
651  
    @see executor
651  
    @see executor
652  
*/
652  
*/
653  
template<Executor Ex>
653  
template<Executor Ex>
654  
[[nodiscard]] auto
654  
[[nodiscard]] auto
655  
run(Ex ex)
655  
run(Ex ex)
656  
{
656  
{
657  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
657  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
658  
}
658  
}
659  

659  

660  
/** Bind a task to an executor with a stop token.
660  
/** Bind a task to an executor with a stop token.
661  

661  

662  
    @param ex The executor on which the task should run.
662  
    @param ex The executor on which the task should run.
663  
    @param st The stop token for cooperative cancellation.
663  
    @param st The stop token for cooperative cancellation.
664  

664  

665  
    @return A wrapper that accepts a task for execution.
665  
    @return A wrapper that accepts a task for execution.
666  
*/
666  
*/
667  
template<Executor Ex>
667  
template<Executor Ex>
668  
[[nodiscard]] auto
668  
[[nodiscard]] auto
669  
run(Ex ex, std::stop_token st)
669  
run(Ex ex, std::stop_token st)
670  
{
670  
{
671  
    return detail::run_wrapper_ex<Ex, false, void>{
671  
    return detail::run_wrapper_ex<Ex, false, void>{
672  
        std::move(ex), std::move(st)};
672  
        std::move(ex), std::move(st)};
673  
}
673  
}
674  

674  

675  
/** Bind a task to an executor with a memory resource.
675  
/** Bind a task to an executor with a memory resource.
676  

676  

677  
    @param ex The executor on which the task should run.
677  
    @param ex The executor on which the task should run.
678  
    @param mr The memory resource for frame allocation.
678  
    @param mr The memory resource for frame allocation.
679  

679  

680  
    @return A wrapper that accepts a task for execution.
680  
    @return A wrapper that accepts a task for execution.
681  
*/
681  
*/
682  
template<Executor Ex>
682  
template<Executor Ex>
683  
[[nodiscard]] auto
683  
[[nodiscard]] auto
684  
run(Ex ex, std::pmr::memory_resource* mr)
684  
run(Ex ex, std::pmr::memory_resource* mr)
685  
{
685  
{
686  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
686  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
687  
        std::move(ex), mr};
687  
        std::move(ex), mr};
688  
}
688  
}
689  

689  

690  
/** Bind a task to an executor with a standard allocator.
690  
/** Bind a task to an executor with a standard allocator.
691  

691  

692  
    @param ex The executor on which the task should run.
692  
    @param ex The executor on which the task should run.
693  
    @param alloc The allocator for frame allocation.
693  
    @param alloc The allocator for frame allocation.
694  

694  

695  
    @return A wrapper that accepts a task for execution.
695  
    @return A wrapper that accepts a task for execution.
696  
*/
696  
*/
697  
template<Executor Ex, detail::Allocator Alloc>
697  
template<Executor Ex, detail::Allocator Alloc>
698  
[[nodiscard]] auto
698  
[[nodiscard]] auto
699  
run(Ex ex, Alloc alloc)
699  
run(Ex ex, Alloc alloc)
700  
{
700  
{
701  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
701  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
702  
        std::move(ex), std::move(alloc)};
702  
        std::move(ex), std::move(alloc)};
703  
}
703  
}
704  

704  

705  
/** Bind a task to an executor with stop token and memory resource.
705  
/** Bind a task to an executor with stop token and memory resource.
706  

706  

707  
    @param ex The executor on which the task should run.
707  
    @param ex The executor on which the task should run.
708  
    @param st The stop token for cooperative cancellation.
708  
    @param st The stop token for cooperative cancellation.
709  
    @param mr The memory resource for frame allocation.
709  
    @param mr The memory resource for frame allocation.
710  

710  

711  
    @return A wrapper that accepts a task for execution.
711  
    @return A wrapper that accepts a task for execution.
712  
*/
712  
*/
713  
template<Executor Ex>
713  
template<Executor Ex>
714  
[[nodiscard]] auto
714  
[[nodiscard]] auto
715  
run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
715  
run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
716  
{
716  
{
717  
    return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
717  
    return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
718  
        std::move(ex), std::move(st), mr};
718  
        std::move(ex), std::move(st), mr};
719  
}
719  
}
720  

720  

721  
/** Bind a task to an executor with stop token and standard allocator.
721  
/** Bind a task to an executor with stop token and standard allocator.
722  

722  

723  
    @param ex The executor on which the task should run.
723  
    @param ex The executor on which the task should run.
724  
    @param st The stop token for cooperative cancellation.
724  
    @param st The stop token for cooperative cancellation.
725  
    @param alloc The allocator for frame allocation.
725  
    @param alloc The allocator for frame allocation.
726  

726  

727  
    @return A wrapper that accepts a task for execution.
727  
    @return A wrapper that accepts a task for execution.
728  
*/
728  
*/
729  
template<Executor Ex, detail::Allocator Alloc>
729  
template<Executor Ex, detail::Allocator Alloc>
730  
[[nodiscard]] auto
730  
[[nodiscard]] auto
731  
run(Ex ex, std::stop_token st, Alloc alloc)
731  
run(Ex ex, std::stop_token st, Alloc alloc)
732  
{
732  
{
733  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
733  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
734  
        std::move(ex), std::move(st), std::move(alloc)};
734  
        std::move(ex), std::move(st), std::move(alloc)};
735  
}
735  
}
736  

736  

737  
//----------------------------------------------------------
737  
//----------------------------------------------------------
738  
//
738  
//
739  
// run() overloads - no executor (inherits caller's)
739  
// run() overloads - no executor (inherits caller's)
740  
//
740  
//
741  
//----------------------------------------------------------
741  
//----------------------------------------------------------
742  

742  

743  
/** Run a task with a custom stop token.
743  
/** Run a task with a custom stop token.
744  

744  

745  
    The task inherits the caller's executor. Only the stop token
745  
    The task inherits the caller's executor. Only the stop token
746  
    is overridden.
746  
    is overridden.
747  

747  

748  
    @par Example
748  
    @par Example
749  
    @code
749  
    @code
750  
    std::stop_source source;
750  
    std::stop_source source;
751  
    co_await run(source.get_token())(cancellable_task());
751  
    co_await run(source.get_token())(cancellable_task());
752  
    @endcode
752  
    @endcode
753  

753  

754  
    @param st The stop token for cooperative cancellation.
754  
    @param st The stop token for cooperative cancellation.
755  

755  

756  
    @return A wrapper that accepts a task for execution.
756  
    @return A wrapper that accepts a task for execution.
757  
*/
757  
*/
758  
[[nodiscard]] inline auto
758  
[[nodiscard]] inline auto
759  
run(std::stop_token st)
759  
run(std::stop_token st)
760  
{
760  
{
761  
    return detail::run_wrapper<false, void>{std::move(st)};
761  
    return detail::run_wrapper<false, void>{std::move(st)};
762  
}
762  
}
763  

763  

764  
/** Run a task with a custom memory resource.
764  
/** Run a task with a custom memory resource.
765  

765  

766  
    The task inherits the caller's executor. The memory resource
766  
    The task inherits the caller's executor. The memory resource
767  
    is used for nested frame allocations.
767  
    is used for nested frame allocations.
768  

768  

769  
    @param mr The memory resource for frame allocation.
769  
    @param mr The memory resource for frame allocation.
770  

770  

771  
    @return A wrapper that accepts a task for execution.
771  
    @return A wrapper that accepts a task for execution.
772  
*/
772  
*/
773  
[[nodiscard]] inline auto
773  
[[nodiscard]] inline auto
774  
run(std::pmr::memory_resource* mr)
774  
run(std::pmr::memory_resource* mr)
775  
{
775  
{
776  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
776  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
777  
}
777  
}
778  

778  

779  
/** Run a task with a custom standard allocator.
779  
/** Run a task with a custom standard allocator.
780  

780  

781  
    The task inherits the caller's executor. The allocator is used
781  
    The task inherits the caller's executor. The allocator is used
782  
    for nested frame allocations.
782  
    for nested frame allocations.
783  

783  

784  
    @param alloc The allocator for frame allocation.
784  
    @param alloc The allocator for frame allocation.
785  

785  

786  
    @return A wrapper that accepts a task for execution.
786  
    @return A wrapper that accepts a task for execution.
787  
*/
787  
*/
788  
template<detail::Allocator Alloc>
788  
template<detail::Allocator Alloc>
789  
[[nodiscard]] auto
789  
[[nodiscard]] auto
790  
run(Alloc alloc)
790  
run(Alloc alloc)
791  
{
791  
{
792  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
792  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
793  
}
793  
}
794  

794  

795  
/** Run a task with stop token and memory resource.
795  
/** Run a task with stop token and memory resource.
796  

796  

797  
    The task inherits the caller's executor.
797  
    The task inherits the caller's executor.
798  

798  

799  
    @param st The stop token for cooperative cancellation.
799  
    @param st The stop token for cooperative cancellation.
800  
    @param mr The memory resource for frame allocation.
800  
    @param mr The memory resource for frame allocation.
801  

801  

802  
    @return A wrapper that accepts a task for execution.
802  
    @return A wrapper that accepts a task for execution.
803  
*/
803  
*/
804  
[[nodiscard]] inline auto
804  
[[nodiscard]] inline auto
805  
run(std::stop_token st, std::pmr::memory_resource* mr)
805  
run(std::stop_token st, std::pmr::memory_resource* mr)
806  
{
806  
{
807  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
807  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
808  
        std::move(st), mr};
808  
        std::move(st), mr};
809  
}
809  
}
810  

810  

811  
/** Run a task with stop token and standard allocator.
811  
/** Run a task with stop token and standard allocator.
812  

812  

813  
    The task inherits the caller's executor.
813  
    The task inherits the caller's executor.
814  

814  

815  
    @param st The stop token for cooperative cancellation.
815  
    @param st The stop token for cooperative cancellation.
816  
    @param alloc The allocator for frame allocation.
816  
    @param alloc The allocator for frame allocation.
817  

817  

818  
    @return A wrapper that accepts a task for execution.
818  
    @return A wrapper that accepts a task for execution.
819  
*/
819  
*/
820  
template<detail::Allocator Alloc>
820  
template<detail::Allocator Alloc>
821  
[[nodiscard]] auto
821  
[[nodiscard]] auto
822  
run(std::stop_token st, Alloc alloc)
822  
run(std::stop_token st, Alloc alloc)
823  
{
823  
{
824  
    return detail::run_wrapper<false, Alloc>{
824  
    return detail::run_wrapper<false, Alloc>{
825  
        std::move(st), std::move(alloc)};
825  
        std::move(st), std::move(alloc)};
826  
}
826  
}
827  

827  

828  
} // namespace boost::capy
828  
} // namespace boost::capy
829  

829  

830  
#endif
830  
#endif