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_alloc_mixin.hpp>
20  
#include <boost/capy/ex/frame_alloc_mixin.hpp>
21  
#include <boost/capy/ex/frame_allocator.hpp>
21  
#include <boost/capy/ex/frame_allocator.hpp>
22  
#include <boost/capy/ex/io_env.hpp>
22  
#include <boost/capy/ex/io_env.hpp>
23  

23  

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

29  

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

33  

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

35  

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

37  

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

45  

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

49  

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

54  

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

60  

61  
namespace boost::capy::detail {
61  
namespace boost::capy::detail {
62  

62  

63  
/** Minimal coroutine that dispatches through the caller's executor.
63  
/** Minimal coroutine that dispatches through the caller's executor.
64  

64  

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

70  

71  
    The trampoline never touches the task's result.
71  
    The trampoline never touches the task's result.
72  
*/
72  
*/
73  
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE dispatch_trampoline
73  
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE dispatch_trampoline
74  
{
74  
{
75  
    struct promise_type
75  
    struct promise_type
76  
        : frame_alloc_mixin
76  
        : frame_alloc_mixin
77  
    {
77  
    {
78  
        executor_ref caller_ex_;
78  
        executor_ref caller_ex_;
79  
        continuation parent_;
79  
        continuation parent_;
80  

80  

81  
        dispatch_trampoline get_return_object() noexcept
81  
        dispatch_trampoline get_return_object() noexcept
82  
        {
82  
        {
83  
            return dispatch_trampoline{
83  
            return dispatch_trampoline{
84  
                std::coroutine_handle<promise_type>::from_promise(*this)};
84  
                std::coroutine_handle<promise_type>::from_promise(*this)};
85  
        }
85  
        }
86  

86  

87  
        std::suspend_always initial_suspend() noexcept { return {}; }
87  
        std::suspend_always initial_suspend() noexcept { return {}; }
88  

88  

89  
        auto final_suspend() noexcept
89  
        auto final_suspend() noexcept
90  
        {
90  
        {
91  
            struct awaiter
91  
            struct awaiter
92  
            {
92  
            {
93  
                promise_type* p_;
93  
                promise_type* p_;
94  
                bool await_ready() const noexcept { return false; }
94  
                bool await_ready() const noexcept { return false; }
95  

95  

96  
                auto await_suspend(
96  
                auto await_suspend(
97  
                    std::coroutine_handle<>) noexcept
97  
                    std::coroutine_handle<>) noexcept
98  
                {
98  
                {
99  
                    return detail::symmetric_transfer(
99  
                    return detail::symmetric_transfer(
100  
                        p_->caller_ex_.dispatch(p_->parent_));
100  
                        p_->caller_ex_.dispatch(p_->parent_));
101  
                }
101  
                }
102  

102  

103  
                void await_resume() const noexcept {}
103  
                void await_resume() const noexcept {}
104  
            };
104  
            };
105  
            return awaiter{this};
105  
            return awaiter{this};
106  
        }
106  
        }
107  

107  

108  
        void return_void() noexcept {}
108  
        void return_void() noexcept {}
109  
        void unhandled_exception() noexcept {}
109  
        void unhandled_exception() noexcept {}
110  
    };
110  
    };
111  

111  

112  
    std::coroutine_handle<promise_type> h_{nullptr};
112  
    std::coroutine_handle<promise_type> h_{nullptr};
113  

113  

114  
    dispatch_trampoline() noexcept = default;
114  
    dispatch_trampoline() noexcept = default;
115  

115  

116  
    ~dispatch_trampoline()
116  
    ~dispatch_trampoline()
117  
    {
117  
    {
118  
        if(h_) h_.destroy();
118  
        if(h_) h_.destroy();
119  
    }
119  
    }
120  

120  

121  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
121  
    dispatch_trampoline(dispatch_trampoline const&) = delete;
122  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
122  
    dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
123  

123  

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

126  

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

136  

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

141  

142  
inline dispatch_trampoline make_dispatch_trampoline()
142  
inline dispatch_trampoline make_dispatch_trampoline()
143  
{
143  
{
144  
    co_return;
144  
    co_return;
145  
}
145  
}
146  

146  

147  
/** Awaitable that binds an IoRunnable to a specific executor.
147  
/** Awaitable that binds an IoRunnable to a specific executor.
148  

148  

149  
    Stores the executor and inner task by value. When co_awaited, the
149  
    Stores the executor and inner task by value. When co_awaited, the
150  
    co_await expression's lifetime extension keeps both alive for the
150  
    co_await expression's lifetime extension keeps both alive for the
151  
    duration of the operation.
151  
    duration of the operation.
152  

152  

153  
    A dispatch trampoline handles the executor switch on completion:
153  
    A dispatch trampoline handles the executor switch on completion:
154  
    the inner task's `final_suspend` resumes the trampoline, which
154  
    the inner task's `final_suspend` resumes the trampoline, which
155  
    dispatches back through the caller's executor.
155  
    dispatches back through the caller's executor.
156  

156  

157  
    The `io_env` is owned by this awaitable and is guaranteed to
157  
    The `io_env` is owned by this awaitable and is guaranteed to
158  
    outlive the inner task and all awaitables in its chain. Awaitables
158  
    outlive the inner task and all awaitables in its chain. Awaitables
159  
    may store `io_env const*` without concern for dangling references.
159  
    may store `io_env const*` without concern for dangling references.
160  

160  

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

176  

177  
    // void allocator, inherit stop token
177  
    // void allocator, inherit stop token
178  
    run_awaitable_ex(Ex ex, Task inner)
178  
    run_awaitable_ex(Ex ex, Task inner)
179  
        requires (InheritStopToken && std::is_void_v<Alloc>)
179  
        requires (InheritStopToken && std::is_void_v<Alloc>)
180  
        : ex_(std::move(ex))
180  
        : ex_(std::move(ex))
181  
        , inner_(std::move(inner))
181  
        , inner_(std::move(inner))
182  
    {
182  
    {
183  
    }
183  
    }
184  

184  

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

193  

194  
    // with allocator, inherit stop token (use template to avoid void parameter)
194  
    // with allocator, inherit stop token (use template to avoid void parameter)
195  
    template<class A>
195  
    template<class A>
196  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
196  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
197  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
197  
    run_awaitable_ex(Ex ex, A alloc, Task inner)
198  
        : ex_(std::move(ex))
198  
        : ex_(std::move(ex))
199  
        , resource_(std::move(alloc))
199  
        , resource_(std::move(alloc))
200  
        , inner_(std::move(inner))
200  
        , inner_(std::move(inner))
201  
    {
201  
    {
202  
    }
202  
    }
203  

203  

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

214  

215  
    bool await_ready() const noexcept
215  
    bool await_ready() const noexcept
216  
    {
216  
    {
217  
        return inner_.await_ready();
217  
        return inner_.await_ready();
218  
    }
218  
    }
219  

219  

220  
    decltype(auto) await_resume()
220  
    decltype(auto) await_resume()
221  
    {
221  
    {
222  
        return inner_.await_resume();
222  
        return inner_.await_resume();
223  
    }
223  
    }
224  

224  

225  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
225  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
226  
    {
226  
    {
227  
        tr_ = make_dispatch_trampoline();
227  
        tr_ = make_dispatch_trampoline();
228  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
228  
        tr_.h_.promise().caller_ex_ = caller_env->executor;
229  
        tr_.h_.promise().parent_.h = cont;
229  
        tr_.h_.promise().parent_.h = cont;
230  

230  

231  
        auto h = inner_.handle();
231  
        auto h = inner_.handle();
232  
        auto& p = h.promise();
232  
        auto& p = h.promise();
233  
        p.set_continuation(tr_.h_);
233  
        p.set_continuation(tr_.h_);
234  

234  

235  
        env_.executor = ex_;
235  
        env_.executor = ex_;
236  
        if constexpr (InheritStopToken)
236  
        if constexpr (InheritStopToken)
237  
            env_.stop_token = caller_env->stop_token;
237  
            env_.stop_token = caller_env->stop_token;
238  
        else
238  
        else
239  
            env_.stop_token = st_;
239  
            env_.stop_token = st_;
240  

240  

241  
        if constexpr (!std::is_void_v<Alloc>)
241  
        if constexpr (!std::is_void_v<Alloc>)
242  
            env_.frame_allocator = resource_.get();
242  
            env_.frame_allocator = resource_.get();
243  
        else
243  
        else
244  
            env_.frame_allocator = caller_env->frame_allocator;
244  
            env_.frame_allocator = caller_env->frame_allocator;
245  

245  

246  
        p.set_environment(&env_);
246  
        p.set_environment(&env_);
247  
        task_cont_.h = h;
247  
        task_cont_.h = h;
248  
        return ex_.dispatch(task_cont_);
248  
        return ex_.dispatch(task_cont_);
249  
    }
249  
    }
250  

250  

251  
    // Non-copyable
251  
    // Non-copyable
252  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
252  
    run_awaitable_ex(run_awaitable_ex const&) = delete;
253  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
253  
    run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
254  

254  

255  
    // Movable (no noexcept - Task may throw)
255  
    // Movable (no noexcept - Task may throw)
256  
    run_awaitable_ex(run_awaitable_ex&&) = default;
256  
    run_awaitable_ex(run_awaitable_ex&&) = default;
257  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
257  
    run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
258  
};
258  
};
259  

259  

260  
/** Awaitable that runs a task with optional stop_token override.
260  
/** Awaitable that runs a task with optional stop_token override.
261  

261  

262  
    Does NOT store an executor - the task inherits the caller's executor
262  
    Does NOT store an executor - the task inherits the caller's executor
263  
    directly. Executors always match, so no dispatch trampoline is needed.
263  
    directly. Executors always match, so no dispatch trampoline is needed.
264  
    The inner task's `final_suspend` resumes the parent directly via
264  
    The inner task's `final_suspend` resumes the parent directly via
265  
    unconditional symmetric transfer.
265  
    unconditional symmetric transfer.
266  

266  

267  
    @tparam Task The IoRunnable type
267  
    @tparam Task The IoRunnable type
268  
    @tparam InheritStopToken If true, inherit caller's stop token
268  
    @tparam InheritStopToken If true, inherit caller's stop token
269  
    @tparam Alloc The allocator type (void for no allocator)
269  
    @tparam Alloc The allocator type (void for no allocator)
270  
*/
270  
*/
271  
template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
271  
template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
272  
struct [[nodiscard]] run_awaitable
272  
struct [[nodiscard]] run_awaitable
273  
{
273  
{
274  
    frame_memory_resource<Alloc> resource_;
274  
    frame_memory_resource<Alloc> resource_;
275  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
275  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
276  
    io_env env_;
276  
    io_env env_;
277  
    Task inner_;  // Last: destroyed first, while env_ is still valid
277  
    Task inner_;  // Last: destroyed first, while env_ is still valid
278  

278  

279  
    // void allocator, inherit stop token
279  
    // void allocator, inherit stop token
280  
    explicit run_awaitable(Task inner)
280  
    explicit run_awaitable(Task inner)
281  
        requires (InheritStopToken && std::is_void_v<Alloc>)
281  
        requires (InheritStopToken && std::is_void_v<Alloc>)
282  
        : inner_(std::move(inner))
282  
        : inner_(std::move(inner))
283  
    {
283  
    {
284  
    }
284  
    }
285  

285  

286  
    // void allocator, explicit stop token
286  
    // void allocator, explicit stop token
287  
    run_awaitable(Task inner, std::stop_token st)
287  
    run_awaitable(Task inner, std::stop_token st)
288  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
288  
        requires (!InheritStopToken && std::is_void_v<Alloc>)
289  
        : st_(std::move(st))
289  
        : st_(std::move(st))
290  
        , inner_(std::move(inner))
290  
        , inner_(std::move(inner))
291  
    {
291  
    {
292  
    }
292  
    }
293  

293  

294  
    // with allocator, inherit stop token (use template to avoid void parameter)
294  
    // with allocator, inherit stop token (use template to avoid void parameter)
295  
    template<class A>
295  
    template<class A>
296  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
296  
        requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
297  
    run_awaitable(A alloc, Task inner)
297  
    run_awaitable(A alloc, Task inner)
298  
        : resource_(std::move(alloc))
298  
        : resource_(std::move(alloc))
299  
        , inner_(std::move(inner))
299  
        , inner_(std::move(inner))
300  
    {
300  
    {
301  
    }
301  
    }
302  

302  

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

312  

313  
    bool await_ready() const noexcept
313  
    bool await_ready() const noexcept
314  
    {
314  
    {
315  
        return inner_.await_ready();
315  
        return inner_.await_ready();
316  
    }
316  
    }
317  

317  

318  
    decltype(auto) await_resume()
318  
    decltype(auto) await_resume()
319  
    {
319  
    {
320  
        return inner_.await_resume();
320  
        return inner_.await_resume();
321  
    }
321  
    }
322  

322  

323  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
323  
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
324  
    {
324  
    {
325  
        auto h = inner_.handle();
325  
        auto h = inner_.handle();
326  
        auto& p = h.promise();
326  
        auto& p = h.promise();
327  
        p.set_continuation(cont);
327  
        p.set_continuation(cont);
328  

328  

329  
        env_.executor = caller_env->executor;
329  
        env_.executor = caller_env->executor;
330  
        if constexpr (InheritStopToken)
330  
        if constexpr (InheritStopToken)
331  
            env_.stop_token = caller_env->stop_token;
331  
            env_.stop_token = caller_env->stop_token;
332  
        else
332  
        else
333  
            env_.stop_token = st_;
333  
            env_.stop_token = st_;
334  

334  

335  
        if constexpr (!std::is_void_v<Alloc>)
335  
        if constexpr (!std::is_void_v<Alloc>)
336  
            env_.frame_allocator = resource_.get();
336  
            env_.frame_allocator = resource_.get();
337  
        else
337  
        else
338  
            env_.frame_allocator = caller_env->frame_allocator;
338  
            env_.frame_allocator = caller_env->frame_allocator;
339  

339  

340  
        p.set_environment(&env_);
340  
        p.set_environment(&env_);
341  
        return h;
341  
        return h;
342  
    }
342  
    }
343  

343  

344  
    // Non-copyable
344  
    // Non-copyable
345  
    run_awaitable(run_awaitable const&) = delete;
345  
    run_awaitable(run_awaitable const&) = delete;
346  
    run_awaitable& operator=(run_awaitable const&) = delete;
346  
    run_awaitable& operator=(run_awaitable const&) = delete;
347  

347  

348  
    // Movable (no noexcept - Task may throw)
348  
    // Movable (no noexcept - Task may throw)
349  
    run_awaitable(run_awaitable&&) = default;
349  
    run_awaitable(run_awaitable&&) = default;
350  
    run_awaitable& operator=(run_awaitable&&) = default;
350  
    run_awaitable& operator=(run_awaitable&&) = default;
351  
};
351  
};
352  

352  

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

354  

355  
    @tparam Ex The executor type.
355  
    @tparam Ex The executor type.
356  
    @tparam InheritStopToken If true, inherit caller's stop token.
356  
    @tparam InheritStopToken If true, inherit caller's stop token.
357  
    @tparam Alloc The allocator type (void for no allocator).
357  
    @tparam Alloc The allocator type (void for no allocator).
358  
*/
358  
*/
359  
template<Executor Ex, bool InheritStopToken, class Alloc>
359  
template<Executor Ex, bool InheritStopToken, class Alloc>
360  
class [[nodiscard]] run_wrapper_ex
360  
class [[nodiscard]] run_wrapper_ex
361  
{
361  
{
362  
    Ex ex_;
362  
    Ex ex_;
363  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
363  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
364  
    frame_memory_resource<Alloc> resource_;
364  
    frame_memory_resource<Alloc> resource_;
365  
    Alloc alloc_;  // Copy to pass to awaitable
365  
    Alloc alloc_;  // Copy to pass to awaitable
366  

366  

367  
public:
367  
public:
368  
    run_wrapper_ex(Ex ex, Alloc alloc)
368  
    run_wrapper_ex(Ex ex, Alloc alloc)
369  
        requires InheritStopToken
369  
        requires InheritStopToken
370  
        : ex_(std::move(ex))
370  
        : ex_(std::move(ex))
371  
        , resource_(alloc)
371  
        , resource_(alloc)
372  
        , alloc_(std::move(alloc))
372  
        , alloc_(std::move(alloc))
373  
    {
373  
    {
374  
        set_current_frame_allocator(&resource_);
374  
        set_current_frame_allocator(&resource_);
375  
    }
375  
    }
376  

376  

377  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
377  
    run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
378  
        requires (!InheritStopToken)
378  
        requires (!InheritStopToken)
379  
        : ex_(std::move(ex))
379  
        : ex_(std::move(ex))
380  
        , st_(std::move(st))
380  
        , st_(std::move(st))
381  
        , resource_(alloc)
381  
        , resource_(alloc)
382  
        , alloc_(std::move(alloc))
382  
        , alloc_(std::move(alloc))
383  
    {
383  
    {
384  
        set_current_frame_allocator(&resource_);
384  
        set_current_frame_allocator(&resource_);
385  
    }
385  
    }
386  

386  

387  
    // Non-copyable, non-movable (must be used immediately)
387  
    // Non-copyable, non-movable (must be used immediately)
388  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
388  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
389  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
389  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
390  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
390  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
391  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
391  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
392  

392  

393  
    template<IoRunnable Task>
393  
    template<IoRunnable Task>
394  
    [[nodiscard]] auto operator()(Task t) &&
394  
    [[nodiscard]] auto operator()(Task t) &&
395  
    {
395  
    {
396  
        if constexpr (InheritStopToken)
396  
        if constexpr (InheritStopToken)
397  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
397  
            return run_awaitable_ex<Task, Ex, true, Alloc>{
398  
                std::move(ex_), std::move(alloc_), std::move(t)};
398  
                std::move(ex_), std::move(alloc_), std::move(t)};
399  
        else
399  
        else
400  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
400  
            return run_awaitable_ex<Task, Ex, false, Alloc>{
401  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
401  
                std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
402  
    }
402  
    }
403  
};
403  
};
404  

404  

405  
/// Specialization for memory_resource* - stores pointer directly.
405  
/// Specialization for memory_resource* - stores pointer directly.
406  
template<Executor Ex, bool InheritStopToken>
406  
template<Executor Ex, bool InheritStopToken>
407  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
407  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
408  
{
408  
{
409  
    Ex ex_;
409  
    Ex ex_;
410  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
410  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
411  
    std::pmr::memory_resource* mr_;
411  
    std::pmr::memory_resource* mr_;
412  

412  

413  
public:
413  
public:
414  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
414  
    run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
415  
        requires InheritStopToken
415  
        requires InheritStopToken
416  
        : ex_(std::move(ex))
416  
        : ex_(std::move(ex))
417  
        , mr_(mr)
417  
        , mr_(mr)
418  
    {
418  
    {
419  
        set_current_frame_allocator(mr_);
419  
        set_current_frame_allocator(mr_);
420  
    }
420  
    }
421  

421  

422  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
422  
    run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
423  
        requires (!InheritStopToken)
423  
        requires (!InheritStopToken)
424  
        : ex_(std::move(ex))
424  
        : ex_(std::move(ex))
425  
        , st_(std::move(st))
425  
        , st_(std::move(st))
426  
        , mr_(mr)
426  
        , mr_(mr)
427  
    {
427  
    {
428  
        set_current_frame_allocator(mr_);
428  
        set_current_frame_allocator(mr_);
429  
    }
429  
    }
430  

430  

431  
    // Non-copyable, non-movable (must be used immediately)
431  
    // Non-copyable, non-movable (must be used immediately)
432  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
432  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
433  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
433  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
434  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
434  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
435  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
435  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
436  

436  

437  
    template<IoRunnable Task>
437  
    template<IoRunnable Task>
438  
    [[nodiscard]] auto operator()(Task t) &&
438  
    [[nodiscard]] auto operator()(Task t) &&
439  
    {
439  
    {
440  
        if constexpr (InheritStopToken)
440  
        if constexpr (InheritStopToken)
441  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
441  
            return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
442  
                std::move(ex_), mr_, std::move(t)};
442  
                std::move(ex_), mr_, std::move(t)};
443  
        else
443  
        else
444  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
444  
            return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
445  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
445  
                std::move(ex_), mr_, std::move(t), std::move(st_)};
446  
    }
446  
    }
447  
};
447  
};
448  

448  

449  
/// Specialization for no allocator (void).
449  
/// Specialization for no allocator (void).
450  
template<Executor Ex, bool InheritStopToken>
450  
template<Executor Ex, bool InheritStopToken>
451  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
451  
class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
452  
{
452  
{
453  
    Ex ex_;
453  
    Ex ex_;
454  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
454  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
455  

455  

456  
public:
456  
public:
457  
    explicit run_wrapper_ex(Ex ex)
457  
    explicit run_wrapper_ex(Ex ex)
458  
        requires InheritStopToken
458  
        requires InheritStopToken
459  
        : ex_(std::move(ex))
459  
        : ex_(std::move(ex))
460  
    {
460  
    {
461  
    }
461  
    }
462  

462  

463  
    run_wrapper_ex(Ex ex, std::stop_token st)
463  
    run_wrapper_ex(Ex ex, std::stop_token st)
464  
        requires (!InheritStopToken)
464  
        requires (!InheritStopToken)
465  
        : ex_(std::move(ex))
465  
        : ex_(std::move(ex))
466  
        , st_(std::move(st))
466  
        , st_(std::move(st))
467  
    {
467  
    {
468  
    }
468  
    }
469  

469  

470  
    // Non-copyable, non-movable (must be used immediately)
470  
    // Non-copyable, non-movable (must be used immediately)
471  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
471  
    run_wrapper_ex(run_wrapper_ex const&) = delete;
472  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
472  
    run_wrapper_ex(run_wrapper_ex&&) = delete;
473  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
473  
    run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
474  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
474  
    run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
475  

475  

476  
    template<IoRunnable Task>
476  
    template<IoRunnable Task>
477  
    [[nodiscard]] auto operator()(Task t) &&
477  
    [[nodiscard]] auto operator()(Task t) &&
478  
    {
478  
    {
479  
        if constexpr (InheritStopToken)
479  
        if constexpr (InheritStopToken)
480  
            return run_awaitable_ex<Task, Ex, true>{
480  
            return run_awaitable_ex<Task, Ex, true>{
481  
                std::move(ex_), std::move(t)};
481  
                std::move(ex_), std::move(t)};
482  
        else
482  
        else
483  
            return run_awaitable_ex<Task, Ex, false>{
483  
            return run_awaitable_ex<Task, Ex, false>{
484  
                std::move(ex_), std::move(t), std::move(st_)};
484  
                std::move(ex_), std::move(t), std::move(st_)};
485  
    }
485  
    }
486  
};
486  
};
487  

487  

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

489  

490  
    @tparam InheritStopToken If true, inherit caller's stop token.
490  
    @tparam InheritStopToken If true, inherit caller's stop token.
491  
    @tparam Alloc The allocator type (void for no allocator).
491  
    @tparam Alloc The allocator type (void for no allocator).
492  
*/
492  
*/
493  
template<bool InheritStopToken, class Alloc>
493  
template<bool InheritStopToken, class Alloc>
494  
class [[nodiscard]] run_wrapper
494  
class [[nodiscard]] run_wrapper
495  
{
495  
{
496  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
496  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
497  
    frame_memory_resource<Alloc> resource_;
497  
    frame_memory_resource<Alloc> resource_;
498  
    Alloc alloc_;  // Copy to pass to awaitable
498  
    Alloc alloc_;  // Copy to pass to awaitable
499  

499  

500  
public:
500  
public:
501  
    explicit run_wrapper(Alloc alloc)
501  
    explicit run_wrapper(Alloc alloc)
502  
        requires InheritStopToken
502  
        requires InheritStopToken
503  
        : resource_(alloc)
503  
        : resource_(alloc)
504  
        , alloc_(std::move(alloc))
504  
        , alloc_(std::move(alloc))
505  
    {
505  
    {
506  
        set_current_frame_allocator(&resource_);
506  
        set_current_frame_allocator(&resource_);
507  
    }
507  
    }
508  

508  

509  
    run_wrapper(std::stop_token st, Alloc alloc)
509  
    run_wrapper(std::stop_token st, Alloc alloc)
510  
        requires (!InheritStopToken)
510  
        requires (!InheritStopToken)
511  
        : st_(std::move(st))
511  
        : st_(std::move(st))
512  
        , resource_(alloc)
512  
        , resource_(alloc)
513  
        , alloc_(std::move(alloc))
513  
        , alloc_(std::move(alloc))
514  
    {
514  
    {
515  
        set_current_frame_allocator(&resource_);
515  
        set_current_frame_allocator(&resource_);
516  
    }
516  
    }
517  

517  

518  
    // Non-copyable, non-movable (must be used immediately)
518  
    // Non-copyable, non-movable (must be used immediately)
519  
    run_wrapper(run_wrapper const&) = delete;
519  
    run_wrapper(run_wrapper const&) = delete;
520  
    run_wrapper(run_wrapper&&) = delete;
520  
    run_wrapper(run_wrapper&&) = delete;
521  
    run_wrapper& operator=(run_wrapper const&) = delete;
521  
    run_wrapper& operator=(run_wrapper const&) = delete;
522  
    run_wrapper& operator=(run_wrapper&&) = delete;
522  
    run_wrapper& operator=(run_wrapper&&) = delete;
523  

523  

524  
    template<IoRunnable Task>
524  
    template<IoRunnable Task>
525  
    [[nodiscard]] auto operator()(Task t) &&
525  
    [[nodiscard]] auto operator()(Task t) &&
526  
    {
526  
    {
527  
        if constexpr (InheritStopToken)
527  
        if constexpr (InheritStopToken)
528  
            return run_awaitable<Task, true, Alloc>{
528  
            return run_awaitable<Task, true, Alloc>{
529  
                std::move(alloc_), std::move(t)};
529  
                std::move(alloc_), std::move(t)};
530  
        else
530  
        else
531  
            return run_awaitable<Task, false, Alloc>{
531  
            return run_awaitable<Task, false, Alloc>{
532  
                std::move(alloc_), std::move(t), std::move(st_)};
532  
                std::move(alloc_), std::move(t), std::move(st_)};
533  
    }
533  
    }
534  
};
534  
};
535  

535  

536  
/// Specialization for memory_resource* - stores pointer directly.
536  
/// Specialization for memory_resource* - stores pointer directly.
537  
template<bool InheritStopToken>
537  
template<bool InheritStopToken>
538  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
538  
class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
539  
{
539  
{
540  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
540  
    std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
541  
    std::pmr::memory_resource* mr_;
541  
    std::pmr::memory_resource* mr_;
542  

542  

543  
public:
543  
public:
544  
    explicit run_wrapper(std::pmr::memory_resource* mr)
544  
    explicit run_wrapper(std::pmr::memory_resource* mr)
545  
        requires InheritStopToken
545  
        requires InheritStopToken
546  
        : mr_(mr)
546  
        : mr_(mr)
547  
    {
547  
    {
548  
        set_current_frame_allocator(mr_);
548  
        set_current_frame_allocator(mr_);
549  
    }
549  
    }
550  

550  

551  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
551  
    run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
552  
        requires (!InheritStopToken)
552  
        requires (!InheritStopToken)
553  
        : st_(std::move(st))
553  
        : st_(std::move(st))
554  
        , mr_(mr)
554  
        , mr_(mr)
555  
    {
555  
    {
556  
        set_current_frame_allocator(mr_);
556  
        set_current_frame_allocator(mr_);
557  
    }
557  
    }
558  

558  

559  
    // Non-copyable, non-movable (must be used immediately)
559  
    // Non-copyable, non-movable (must be used immediately)
560  
    run_wrapper(run_wrapper const&) = delete;
560  
    run_wrapper(run_wrapper const&) = delete;
561  
    run_wrapper(run_wrapper&&) = delete;
561  
    run_wrapper(run_wrapper&&) = delete;
562  
    run_wrapper& operator=(run_wrapper const&) = delete;
562  
    run_wrapper& operator=(run_wrapper const&) = delete;
563  
    run_wrapper& operator=(run_wrapper&&) = delete;
563  
    run_wrapper& operator=(run_wrapper&&) = delete;
564  

564  

565  
    template<IoRunnable Task>
565  
    template<IoRunnable Task>
566  
    [[nodiscard]] auto operator()(Task t) &&
566  
    [[nodiscard]] auto operator()(Task t) &&
567  
    {
567  
    {
568  
        if constexpr (InheritStopToken)
568  
        if constexpr (InheritStopToken)
569  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
569  
            return run_awaitable<Task, true, std::pmr::memory_resource*>{
570  
                mr_, std::move(t)};
570  
                mr_, std::move(t)};
571  
        else
571  
        else
572  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
572  
            return run_awaitable<Task, false, std::pmr::memory_resource*>{
573  
                mr_, std::move(t), std::move(st_)};
573  
                mr_, std::move(t), std::move(st_)};
574  
    }
574  
    }
575  
};
575  
};
576  

576  

577  
/// Specialization for stop_token only (no allocator).
577  
/// Specialization for stop_token only (no allocator).
578  
template<>
578  
template<>
579  
class [[nodiscard]] run_wrapper<false, void>
579  
class [[nodiscard]] run_wrapper<false, void>
580  
{
580  
{
581  
    std::stop_token st_;
581  
    std::stop_token st_;
582  

582  

583  
public:
583  
public:
584  
    explicit run_wrapper(std::stop_token st)
584  
    explicit run_wrapper(std::stop_token st)
585  
        : st_(std::move(st))
585  
        : st_(std::move(st))
586  
    {
586  
    {
587  
    }
587  
    }
588  

588  

589  
    // Non-copyable, non-movable (must be used immediately)
589  
    // Non-copyable, non-movable (must be used immediately)
590  
    run_wrapper(run_wrapper const&) = delete;
590  
    run_wrapper(run_wrapper const&) = delete;
591  
    run_wrapper(run_wrapper&&) = delete;
591  
    run_wrapper(run_wrapper&&) = delete;
592  
    run_wrapper& operator=(run_wrapper const&) = delete;
592  
    run_wrapper& operator=(run_wrapper const&) = delete;
593  
    run_wrapper& operator=(run_wrapper&&) = delete;
593  
    run_wrapper& operator=(run_wrapper&&) = delete;
594  

594  

595  
    template<IoRunnable Task>
595  
    template<IoRunnable Task>
596  
    [[nodiscard]] auto operator()(Task t) &&
596  
    [[nodiscard]] auto operator()(Task t) &&
597  
    {
597  
    {
598  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
598  
        return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
599  
    }
599  
    }
600  
};
600  
};
601  

601  

602  
} // namespace boost::capy::detail
602  
} // namespace boost::capy::detail
603  

603  

604  
namespace boost::capy {
604  
namespace boost::capy {
605  

605  

606  
/** Bind a task to execute on a specific executor.
606  
/** Bind a task to execute on a specific executor.
607  

607  

608  
    Returns a wrapper that accepts a task and produces an awaitable.
608  
    Returns a wrapper that accepts a task and produces an awaitable.
609  
    When co_awaited, the task runs on the specified executor.
609  
    When co_awaited, the task runs on the specified executor.
610  

610  

611  
    @par Example
611  
    @par Example
612  
    @code
612  
    @code
613  
    co_await run(other_executor)(my_task());
613  
    co_await run(other_executor)(my_task());
614  
    @endcode
614  
    @endcode
615  

615  

616  
    @param ex The executor on which the task should run.
616  
    @param ex The executor on which the task should run.
617  

617  

618  
    @return A wrapper that accepts a task for execution.
618  
    @return A wrapper that accepts a task for execution.
619  

619  

620  
    @see task
620  
    @see task
621  
    @see executor
621  
    @see executor
622  
*/
622  
*/
623  
template<Executor Ex>
623  
template<Executor Ex>
624  
[[nodiscard]] auto
624  
[[nodiscard]] auto
625  
run(Ex ex)
625  
run(Ex ex)
626  
{
626  
{
627  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
627  
    return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
628  
}
628  
}
629  

629  

630  
/** Bind a task to an executor with a stop token.
630  
/** Bind a task to an executor with a stop token.
631  

631  

632  
    @param ex The executor on which the task should run.
632  
    @param ex The executor on which the task should run.
633  
    @param st The stop token for cooperative cancellation.
633  
    @param st The stop token for cooperative cancellation.
634  

634  

635  
    @return A wrapper that accepts a task for execution.
635  
    @return A wrapper that accepts a task for execution.
636  
*/
636  
*/
637  
template<Executor Ex>
637  
template<Executor Ex>
638  
[[nodiscard]] auto
638  
[[nodiscard]] auto
639  
run(Ex ex, std::stop_token st)
639  
run(Ex ex, std::stop_token st)
640  
{
640  
{
641  
    return detail::run_wrapper_ex<Ex, false, void>{
641  
    return detail::run_wrapper_ex<Ex, false, void>{
642  
        std::move(ex), std::move(st)};
642  
        std::move(ex), std::move(st)};
643  
}
643  
}
644  

644  

645  
/** Bind a task to an executor with a memory resource.
645  
/** Bind a task to an executor with a memory resource.
646  

646  

647  
    @param ex The executor on which the task should run.
647  
    @param ex The executor on which the task should run.
648  
    @param mr The memory resource for frame allocation.
648  
    @param mr The memory resource for frame allocation.
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  
template<Executor Ex>
652  
template<Executor Ex>
653  
[[nodiscard]] auto
653  
[[nodiscard]] auto
654  
run(Ex ex, std::pmr::memory_resource* mr)
654  
run(Ex ex, std::pmr::memory_resource* mr)
655  
{
655  
{
656  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
656  
    return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
657  
        std::move(ex), mr};
657  
        std::move(ex), mr};
658  
}
658  
}
659  

659  

660  
/** Bind a task to an executor with a standard allocator.
660  
/** Bind a task to an executor with a standard allocator.
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 alloc The allocator for frame allocation.
663  
    @param alloc The allocator for frame allocation.
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, detail::Allocator Alloc>
667  
template<Executor Ex, detail::Allocator Alloc>
668  
[[nodiscard]] auto
668  
[[nodiscard]] auto
669  
run(Ex ex, Alloc alloc)
669  
run(Ex ex, Alloc alloc)
670  
{
670  
{
671  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
671  
    return detail::run_wrapper_ex<Ex, true, Alloc>{
672  
        std::move(ex), std::move(alloc)};
672  
        std::move(ex), std::move(alloc)};
673  
}
673  
}
674  

674  

675  
/** Bind a task to an executor with stop token and memory resource.
675  
/** Bind a task to an executor with stop token and 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 st The stop token for cooperative cancellation.
678  
    @param st The stop token for cooperative cancellation.
679  
    @param mr The memory resource for frame allocation.
679  
    @param mr The memory resource for frame allocation.
680  

680  

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

690  

691  
/** Bind a task to an executor with stop token and standard allocator.
691  
/** Bind a task to an executor with stop token and standard allocator.
692  

692  

693  
    @param ex The executor on which the task should run.
693  
    @param ex The executor on which the task should run.
694  
    @param st The stop token for cooperative cancellation.
694  
    @param st The stop token for cooperative cancellation.
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, std::stop_token st, Alloc alloc)
701  
run(Ex ex, std::stop_token st, Alloc alloc)
702  
{
702  
{
703  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
703  
    return detail::run_wrapper_ex<Ex, false, Alloc>{
704  
        std::move(ex), std::move(st), std::move(alloc)};
704  
        std::move(ex), std::move(st), std::move(alloc)};
705  
}
705  
}
706  

706  

707  
/** Run a task with a custom stop token.
707  
/** Run a task with a custom stop token.
708  

708  

709  
    The task inherits the caller's executor. Only the stop token
709  
    The task inherits the caller's executor. Only the stop token
710  
    is overridden.
710  
    is overridden.
711  

711  

712  
    @par Example
712  
    @par Example
713  
    @code
713  
    @code
714  
    std::stop_source source;
714  
    std::stop_source source;
715  
    co_await run(source.get_token())(cancellable_task());
715  
    co_await run(source.get_token())(cancellable_task());
716  
    @endcode
716  
    @endcode
717  

717  

718  
    @param st The stop token for cooperative cancellation.
718  
    @param st The stop token for cooperative cancellation.
719  

719  

720  
    @return A wrapper that accepts a task for execution.
720  
    @return A wrapper that accepts a task for execution.
721  
*/
721  
*/
722  
[[nodiscard]] inline auto
722  
[[nodiscard]] inline auto
723  
run(std::stop_token st)
723  
run(std::stop_token st)
724  
{
724  
{
725  
    return detail::run_wrapper<false, void>{std::move(st)};
725  
    return detail::run_wrapper<false, void>{std::move(st)};
726  
}
726  
}
727  

727  

728  
/** Run a task with a custom memory resource.
728  
/** Run a task with a custom memory resource.
729  

729  

730  
    The task inherits the caller's executor. The memory resource
730  
    The task inherits the caller's executor. The memory resource
731  
    is used for nested frame allocations.
731  
    is used for nested frame allocations.
732  

732  

733  
    @param mr The memory resource for frame allocation.
733  
    @param mr The memory resource for frame allocation.
734  

734  

735  
    @return A wrapper that accepts a task for execution.
735  
    @return A wrapper that accepts a task for execution.
736  
*/
736  
*/
737  
[[nodiscard]] inline auto
737  
[[nodiscard]] inline auto
738  
run(std::pmr::memory_resource* mr)
738  
run(std::pmr::memory_resource* mr)
739  
{
739  
{
740  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
740  
    return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
741  
}
741  
}
742  

742  

743  
/** Run a task with a custom standard allocator.
743  
/** Run a task with a custom standard allocator.
744  

744  

745  
    The task inherits the caller's executor. The allocator is used
745  
    The task inherits the caller's executor. The allocator is used
746  
    for nested frame allocations.
746  
    for nested frame allocations.
747  

747  

748  
    @param alloc The allocator for frame allocation.
748  
    @param alloc The allocator for frame allocation.
749  

749  

750  
    @return A wrapper that accepts a task for execution.
750  
    @return A wrapper that accepts a task for execution.
751  
*/
751  
*/
752  
template<detail::Allocator Alloc>
752  
template<detail::Allocator Alloc>
753  
[[nodiscard]] auto
753  
[[nodiscard]] auto
754  
run(Alloc alloc)
754  
run(Alloc alloc)
755  
{
755  
{
756  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
756  
    return detail::run_wrapper<true, Alloc>{std::move(alloc)};
757  
}
757  
}
758  

758  

759  
/** Run a task with stop token and memory resource.
759  
/** Run a task with stop token and memory resource.
760  

760  

761  
    The task inherits the caller's executor.
761  
    The task inherits the caller's executor.
762  

762  

763  
    @param st The stop token for cooperative cancellation.
763  
    @param st The stop token for cooperative cancellation.
764  
    @param mr The memory resource for frame allocation.
764  
    @param mr The memory resource for frame allocation.
765  

765  

766  
    @return A wrapper that accepts a task for execution.
766  
    @return A wrapper that accepts a task for execution.
767  
*/
767  
*/
768  
[[nodiscard]] inline auto
768  
[[nodiscard]] inline auto
769  
run(std::stop_token st, std::pmr::memory_resource* mr)
769  
run(std::stop_token st, std::pmr::memory_resource* mr)
770  
{
770  
{
771  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
771  
    return detail::run_wrapper<false, std::pmr::memory_resource*>{
772  
        std::move(st), mr};
772  
        std::move(st), mr};
773  
}
773  
}
774  

774  

775  
/** Run a task with stop token and standard allocator.
775  
/** Run a task with stop token and standard allocator.
776  

776  

777  
    The task inherits the caller's executor.
777  
    The task inherits the caller's executor.
778  

778  

779  
    @param st The stop token for cooperative cancellation.
779  
    @param st The stop token for cooperative cancellation.
780  
    @param alloc The allocator for frame allocation.
780  
    @param alloc The allocator for frame allocation.
781  

781  

782  
    @return A wrapper that accepts a task for execution.
782  
    @return A wrapper that accepts a task for execution.
783  
*/
783  
*/
784  
template<detail::Allocator Alloc>
784  
template<detail::Allocator Alloc>
785  
[[nodiscard]] auto
785  
[[nodiscard]] auto
786  
run(std::stop_token st, Alloc alloc)
786  
run(std::stop_token st, Alloc alloc)
787  
{
787  
{
788  
    return detail::run_wrapper<false, Alloc>{
788  
    return detail::run_wrapper<false, Alloc>{
789  
        std::move(st), std::move(alloc)};
789  
        std::move(st), std::move(alloc)};
790  
}
790  
}
791  

791  

792  
} // namespace boost::capy
792  
} // namespace boost::capy
793  

793  

794  
#endif
794  
#endif