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/await_suspend_helper.hpp>
14  
#include <boost/capy/detail/await_suspend_helper.hpp>
15  
#include <boost/capy/detail/run.hpp>
15  
#include <boost/capy/detail/run.hpp>
16  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/io_runnable.hpp>
17  
#include <boost/capy/concept/io_runnable.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
19  
#include <coroutine>
19  
#include <coroutine>
20  
#include <boost/capy/ex/frame_allocator.hpp>
20  
#include <boost/capy/ex/frame_allocator.hpp>
21  
#include <boost/capy/ex/io_env.hpp>
21  
#include <boost/capy/ex/io_env.hpp>
22  

22  

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

28  

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

32  

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

34  

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

36  

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

44  

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

48  

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

53  

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

59  

60  
namespace boost::capy::detail {
60  
namespace boost::capy::detail {
61  

61  

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

67  

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

69  

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

75  

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

84  

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

90  

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

92  

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

99  

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

106  

107  
                void await_resume() const noexcept {}
107  
                void await_resume() const noexcept {}
108  
            };
108  
            };
109  
            return awaiter{this};
109  
            return awaiter{this};
110  
        }
110  
        }
111  

111  

112  
        void return_void() noexcept {}
112  
        void return_void() noexcept {}
113  
        void unhandled_exception() noexcept {}
113  
        void unhandled_exception() noexcept {}
114  
    };
114  
    };
115  

115  

116  
    std::coroutine_handle<promise_type> h_{nullptr};
116  
    std::coroutine_handle<promise_type> h_{nullptr};
117  

117  

118  
    dispatch_trampoline() noexcept = default;
118  
    dispatch_trampoline() noexcept = default;
119  

119  

120  
    ~dispatch_trampoline()
120  
    ~dispatch_trampoline()
121  
    {
121  
    {
122  
        if(h_) h_.destroy();
122  
        if(h_) h_.destroy();
123  
    }
123  
    }
124  

124  

125  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
125  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
126  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
126  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
127  

127  

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

130  

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

140  

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

145  

146  
inline dispatch_trampoline make_dispatch_trampoline()
146  
inline dispatch_trampoline make_dispatch_trampoline()
147  
{
147  
{
148  
    co_return;
148  
    co_return;
149  
}
149  
}
150  

150  

151  
//----------------------------------------------------------
151  
//----------------------------------------------------------
152  
//
152  
//
153  
// run_awaitable_ex - with executor (executor switch)
153  
// run_awaitable_ex - with executor (executor switch)
154  
//
154  
//
155  
//----------------------------------------------------------
155  
//----------------------------------------------------------
156  

156  

157  
/** Awaitable that binds an IoRunnable to a specific executor.
157  
/** Awaitable that binds an IoRunnable to a specific executor.
158  

158  

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

162  

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

166  

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

170  

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

185  

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

193  

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

202  

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

212  

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

223  

224  
    bool await_ready() const noexcept
224  
    bool await_ready() const noexcept
225  
    {
225  
    {
226  
        return inner_.await_ready();
226  
        return inner_.await_ready();
227  
    }
227  
    }
228  

228  

229  
    decltype(auto) await_resume()
229  
    decltype(auto) await_resume()
230  
    {
230  
    {
231  
        return inner_.await_resume();
231  
        return inner_.await_resume();
232  
    }
232  
    }
233  

233  

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

239  

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

243  

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

249  

250  
        if constexpr (!std::is_void_v<Alloc>)
250  
        if constexpr (!std::is_void_v<Alloc>)
251  
            env_.frame_allocator = resource_.get();
251  
            env_.frame_allocator = resource_.get();
252  
        else
252  
        else
253  
            env_.frame_allocator = caller_env->frame_allocator;
253  
            env_.frame_allocator = caller_env->frame_allocator;
254  

254  

255  
        p.set_environment(&env_);
255  
        p.set_environment(&env_);
256  
        return h;
256  
        return h;
257  
    }
257  
    }
258  

258  

259  
    // Non-copyable
259  
    // Non-copyable
260  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
260  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
261  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
261  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
262  

262  

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

267  

268  
//----------------------------------------------------------
268  
//----------------------------------------------------------
269  
//
269  
//
270  
// run_awaitable - no executor (inherits caller's executor)
270  
// run_awaitable - no executor (inherits caller's executor)
271  
//
271  
//
272  
//----------------------------------------------------------
272  
//----------------------------------------------------------
273  

273  

274  
/** Awaitable that runs a task with optional stop_token override.
274  
/** Awaitable that runs a task with optional stop_token override.
275  

275  

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

280  

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

292  

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

299  

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

307  

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

316  

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

326  

327  
    bool await_ready() const noexcept
327  
    bool await_ready() const noexcept
328  
    {
328  
    {
329  
        return inner_.await_ready();
329  
        return inner_.await_ready();
330  
    }
330  
    }
331  

331  

332  
    decltype(auto) await_resume()
332  
    decltype(auto) await_resume()
333  
    {
333  
    {
334  
        return inner_.await_resume();
334  
        return inner_.await_resume();
335  
    }
335  
    }
336  

336  

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

342  

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

348  

349  
        if constexpr (!std::is_void_v<Alloc>)
349  
        if constexpr (!std::is_void_v<Alloc>)
350  
            env_.frame_allocator = resource_.get();
350  
            env_.frame_allocator = resource_.get();
351  
        else
351  
        else
352  
            env_.frame_allocator = caller_env->frame_allocator;
352  
            env_.frame_allocator = caller_env->frame_allocator;
353  

353  

354  
        p.set_environment(&env_);
354  
        p.set_environment(&env_);
355  
        return h;
355  
        return h;
356  
    }
356  
    }
357  

357  

358  
    // Non-copyable
358  
    // Non-copyable
359  
    run_awaitable(run_awaitable const&) = delete;
359  
    run_awaitable(run_awaitable const&) = delete;
360  
    run_awaitable& operator=(run_awaitable const&) = delete;
360  
    run_awaitable& operator=(run_awaitable const&) = delete;
361  

361  

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

366  

367  
//----------------------------------------------------------
367  
//----------------------------------------------------------
368  
//
368  
//
369  
// run_wrapper_ex - with executor
369  
// run_wrapper_ex - with executor
370  
//
370  
//
371  
//----------------------------------------------------------
371  
//----------------------------------------------------------
372  

372  

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

374  

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

386  

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

396  

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

406  

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

412  

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

424  

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

432  

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

441  

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

450  

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

456  

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

468  

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

475  

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

482  

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

489  

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

495  

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

507  

508  
//----------------------------------------------------------
508  
//----------------------------------------------------------
509  
//
509  
//
510  
// run_wrapper - no executor (inherits caller's executor)
510  
// run_wrapper - no executor (inherits caller's executor)
511  
//
511  
//
512  
//----------------------------------------------------------
512  
//----------------------------------------------------------
513  

513  

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

515  

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

525  

526  
public:
526  
public:
527  
    explicit run_wrapper(Alloc alloc)
527  
    explicit run_wrapper(Alloc alloc)
528  
        requires InheritStopToken
528  
        requires InheritStopToken
529  
        : resource_(alloc)
529  
        : resource_(alloc)
530  
        , alloc_(std::move(alloc))
530  
        , alloc_(std::move(alloc))
531  
    {
531  
    {
532  
        set_current_frame_allocator(&resource_);
532  
        set_current_frame_allocator(&resource_);
533  
    }
533  
    }
534  

534  

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

543  

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

549  

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

561  

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

568  

569  
public:
569  
public:
570  
    explicit run_wrapper(std::pmr::memory_resource* mr)
570  
    explicit run_wrapper(std::pmr::memory_resource* mr)
571  
        requires InheritStopToken
571  
        requires InheritStopToken
572  
        : mr_(mr)
572  
        : mr_(mr)
573  
    {
573  
    {
574  
        set_current_frame_allocator(mr_);
574  
        set_current_frame_allocator(mr_);
575  
    }
575  
    }
576  

576  

577  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
577  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
578  
        requires (!InheritStopToken)
578  
        requires (!InheritStopToken)
579  
        : st_(std::move(st))
579  
        : st_(std::move(st))
580  
        , mr_(mr)
580  
        , mr_(mr)
581  
    {
581  
    {
582  
        set_current_frame_allocator(mr_);
582  
        set_current_frame_allocator(mr_);
583  
    }
583  
    }
584  

584  

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

590  

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

602  

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

608  

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

614  

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

620  

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

627  

628  
} // namespace boost::capy::detail
628  
} // namespace boost::capy::detail
629  

629  

630  
namespace boost::capy {
630  
namespace boost::capy {
631  

631  

632  
//----------------------------------------------------------
632  
//----------------------------------------------------------
633  
//
633  
//
634  
// run() overloads - with executor
634  
// run() overloads - with executor
635  
//
635  
//
636  
//----------------------------------------------------------
636  
//----------------------------------------------------------
637  

637  

638  
/** Bind a task to execute on a specific executor.
638  
/** Bind a task to execute on a specific executor.
639  

639  

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

642  

643  
    @par Example
643  
    @par Example
644  
    @code
644  
    @code
645  
    co_await run(other_executor)(my_task());
645  
    co_await run(other_executor)(my_task());
646  
    @endcode
646  
    @endcode
647  

647  

648  
    @param ex The executor on which the task should run.
648  
    @param ex The executor on which the task should run.
649  

649  

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

651  

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

661  

662  
/** Bind a task to an executor with a stop token.
662  
/** Bind a task to an executor with a stop token.
663  

663  

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

666  

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

676  

677  
/** Bind a task to an executor with a memory resource.
677  
/** Bind a task to an executor with a memory resource.
678  

678  

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

681  

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

691  

692  
/** Bind a task to an executor with a standard allocator.
692  
/** Bind a task to an executor with a standard allocator.
693  

693  

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

696  

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

706  

707  
/** Bind a task to an executor with stop token and memory resource.
707  
/** Bind a task to an executor with stop token and memory resource.
708  

708  

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

712  

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

722  

723  
/** Bind a task to an executor with stop token and standard allocator.
723  
/** Bind a task to an executor with stop token and standard allocator.
724  

724  

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

728  

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

738  

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

744  

745  
/** Run a task with a custom stop token.
745  
/** Run a task with a custom stop token.
746  

746  

747  
    The task inherits the caller's executor. Only the stop token
747  
    The task inherits the caller's executor. Only the stop token
748  
    is overridden.
748  
    is overridden.
749  

749  

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

755  

756  
    @param st The stop token for cooperative cancellation.
756  
    @param st The stop token for cooperative cancellation.
757  

757  

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

765  

766  
/** Run a task with a custom memory resource.
766  
/** Run a task with a custom memory resource.
767  

767  

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

770  

771  
    @param mr The memory resource for frame allocation.
771  
    @param mr The memory resource for frame allocation.
772  

772  

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

780  

781  
/** Run a task with a custom standard allocator.
781  
/** Run a task with a custom standard allocator.
782  

782  

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

785  

786  
    @param alloc The allocator for frame allocation.
786  
    @param alloc The allocator for frame allocation.
787  

787  

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

796  

797  
/** Run a task with stop token and memory resource.
797  
/** Run a task with stop token and memory resource.
798  

798  

799  
    The task inherits the caller's executor.
799  
    The task inherits the caller's executor.
800  

800  

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

803  

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

812  

813  
/** Run a task with stop token and standard allocator.
813  
/** Run a task with stop token and standard allocator.
814  

814  

815  
    The task inherits the caller's executor.
815  
    The task inherits the caller's executor.
816  

816  

817  
    @param st The stop token for cooperative cancellation.
817  
    @param st The stop token for cooperative cancellation.
818  
    @param alloc The allocator for frame allocation.
818  
    @param alloc The allocator for frame allocation.
819  

819  

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

829  

830  
} // namespace boost::capy
830  
} // namespace boost::capy
831  

831  

832  
#endif
832  
#endif