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_ASYNC_HPP
10  
#ifndef BOOST_CAPY_RUN_ASYNC_HPP
11  
#define BOOST_CAPY_RUN_ASYNC_HPP
11  
#define BOOST_CAPY_RUN_ASYNC_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/run.hpp>
14  
#include <boost/capy/detail/run.hpp>
15  
#include <boost/capy/detail/run_callbacks.hpp>
15  
#include <boost/capy/detail/run_callbacks.hpp>
16  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/io_launchable_task.hpp>
17  
#include <boost/capy/concept/io_launchable_task.hpp>
18  
#include <boost/capy/ex/execution_context.hpp>
18  
#include <boost/capy/ex/execution_context.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
20  
#include <boost/capy/ex/recycling_memory_resource.hpp>
20  
#include <boost/capy/ex/recycling_memory_resource.hpp>
 
21 +
#include <boost/capy/ex/work_guard.hpp>
21  

22  

22  
#include <coroutine>
23  
#include <coroutine>
23  
#include <memory_resource>
24  
#include <memory_resource>
24  
#include <new>
25  
#include <new>
25  
#include <stop_token>
26  
#include <stop_token>
26  
#include <type_traits>
27  
#include <type_traits>
27  

28  

28  
namespace boost {
29  
namespace boost {
29  
namespace capy {
30  
namespace capy {
30  
namespace detail {
31  
namespace detail {
31  

32  

32  
/// Function pointer type for type-erased frame deallocation.
33  
/// Function pointer type for type-erased frame deallocation.
33  
using dealloc_fn = void(*)(void*, std::size_t);
34  
using dealloc_fn = void(*)(void*, std::size_t);
34  

35  

35  
/// Type-erased deallocator implementation for trampoline frames.
36  
/// Type-erased deallocator implementation for trampoline frames.
36  
template<class Alloc>
37  
template<class Alloc>
37  
void dealloc_impl(void* raw, std::size_t total)
38  
void dealloc_impl(void* raw, std::size_t total)
38  
{
39  
{
39  
    static_assert(std::is_same_v<typename Alloc::value_type, std::byte>);
40  
    static_assert(std::is_same_v<typename Alloc::value_type, std::byte>);
40  
    auto* a = std::launder(reinterpret_cast<Alloc*>(
41  
    auto* a = std::launder(reinterpret_cast<Alloc*>(
41  
        static_cast<char*>(raw) + total - sizeof(Alloc)));
42  
        static_cast<char*>(raw) + total - sizeof(Alloc)));
42  
    Alloc ba(std::move(*a));
43  
    Alloc ba(std::move(*a));
43  
    a->~Alloc();
44  
    a->~Alloc();
44  
    ba.deallocate(static_cast<std::byte*>(raw), total);
45  
    ba.deallocate(static_cast<std::byte*>(raw), total);
45  
}
46  
}
46  

47  

47  
/// Awaiter to access the promise from within the coroutine.
48  
/// Awaiter to access the promise from within the coroutine.
48  
template<class Promise>
49  
template<class Promise>
49  
struct get_promise_awaiter
50  
struct get_promise_awaiter
50  
{
51  
{
51  
    Promise* p_ = nullptr;
52  
    Promise* p_ = nullptr;
52  

53  

53  
    bool await_ready() const noexcept { return false; }
54  
    bool await_ready() const noexcept { return false; }
54  

55  

55  
    bool await_suspend(std::coroutine_handle<Promise> h) noexcept
56  
    bool await_suspend(std::coroutine_handle<Promise> h) noexcept
56  
    {
57  
    {
57  
        p_ = &h.promise();
58  
        p_ = &h.promise();
58  
        return false;
59  
        return false;
59  
    }
60  
    }
60  

61  

61  
    Promise& await_resume() const noexcept
62  
    Promise& await_resume() const noexcept
62  
    {
63  
    {
63  
        return *p_;
64  
        return *p_;
64  
    }
65  
    }
65  
};
66  
};
66  

67  

67  
/** Internal run_async_trampoline coroutine for run_async.
68  
/** Internal run_async_trampoline coroutine for run_async.
68  

69  

69  
    The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation
70  
    The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation
70  
    order) and serves as the task's continuation. When the task final_suspends,
71  
    order) and serves as the task's continuation. When the task final_suspends,
71  
    control returns to the run_async_trampoline which then invokes the appropriate handler.
72  
    control returns to the run_async_trampoline which then invokes the appropriate handler.
72  

73  

73  
    For value-type allocators, the run_async_trampoline stores a frame_memory_resource
74  
    For value-type allocators, the run_async_trampoline stores a frame_memory_resource
74  
    that wraps the allocator. For memory_resource*, it stores the pointer directly.
75  
    that wraps the allocator. For memory_resource*, it stores the pointer directly.
75  

76  

76  
    @tparam Ex The executor type.
77  
    @tparam Ex The executor type.
77  
    @tparam Handlers The handler type (default_handler or handler_pair).
78  
    @tparam Handlers The handler type (default_handler or handler_pair).
78  
    @tparam Alloc The allocator type (value type or memory_resource*).
79  
    @tparam Alloc The allocator type (value type or memory_resource*).
79  
*/
80  
*/
80  
template<class Ex, class Handlers, class Alloc>
81  
template<class Ex, class Handlers, class Alloc>
81  
struct run_async_trampoline
82  
struct run_async_trampoline
82  
{
83  
{
83  
    using invoke_fn = void(*)(void*, Handlers&);
84  
    using invoke_fn = void(*)(void*, Handlers&);
84  

85  

85  
    struct promise_type
86  
    struct promise_type
86  
    {
87  
    {
87 -
        Ex ex_;
88 +
        work_guard<Ex> wg_;
88  
        Handlers handlers_;
89  
        Handlers handlers_;
89  
        frame_memory_resource<Alloc> resource_;
90  
        frame_memory_resource<Alloc> resource_;
90  
        invoke_fn invoke_ = nullptr;
91  
        invoke_fn invoke_ = nullptr;
91  
        void* task_promise_ = nullptr;
92  
        void* task_promise_ = nullptr;
92  
        std::coroutine_handle<> task_h_;
93  
        std::coroutine_handle<> task_h_;
93  

94  

94  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
95  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
95 -
            : ex_(std::move(ex))
96 +
            : wg_(std::move(ex))
96  
            , handlers_(std::move(h))
97  
            , handlers_(std::move(h))
97  
            , resource_(std::move(a))
98  
            , resource_(std::move(a))
98  
        {
99  
        {
99  
        }
100  
        }
100  

101  

101  
        static void* operator new(
102  
        static void* operator new(
102  
            std::size_t size, Ex const&, Handlers const&, Alloc a)
103  
            std::size_t size, Ex const&, Handlers const&, Alloc a)
103  
        {
104  
        {
104  
            using byte_alloc = typename std::allocator_traits<Alloc>
105  
            using byte_alloc = typename std::allocator_traits<Alloc>
105  
                ::template rebind_alloc<std::byte>;
106  
                ::template rebind_alloc<std::byte>;
106  

107  

107  
            constexpr auto footer_align =
108  
            constexpr auto footer_align =
108  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
109  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
109  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
110  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
110  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
111  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
111  

112  

112  
            byte_alloc ba(std::move(a));
113  
            byte_alloc ba(std::move(a));
113  
            void* raw = ba.allocate(total);
114  
            void* raw = ba.allocate(total);
114  

115  

115  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
116  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
116  
                static_cast<char*>(raw) + padded);
117  
                static_cast<char*>(raw) + padded);
117  
            *fn_loc = &dealloc_impl<byte_alloc>;
118  
            *fn_loc = &dealloc_impl<byte_alloc>;
118  

119  

119  
            new (fn_loc + 1) byte_alloc(std::move(ba));
120  
            new (fn_loc + 1) byte_alloc(std::move(ba));
120  

121  

121  
            return raw;
122  
            return raw;
122  
        }
123  
        }
123  

124  

124  
        static void operator delete(void* ptr, std::size_t size)
125  
        static void operator delete(void* ptr, std::size_t size)
125  
        {
126  
        {
126  
            constexpr auto footer_align =
127  
            constexpr auto footer_align =
127  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
128  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
128  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
129  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
129  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
130  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
130  

131  

131  
            auto* fn = reinterpret_cast<dealloc_fn*>(
132  
            auto* fn = reinterpret_cast<dealloc_fn*>(
132  
                static_cast<char*>(ptr) + padded);
133  
                static_cast<char*>(ptr) + padded);
133  
            (*fn)(ptr, total);
134  
            (*fn)(ptr, total);
134  
        }
135  
        }
135  

136  

136  
        std::pmr::memory_resource* get_resource() noexcept
137  
        std::pmr::memory_resource* get_resource() noexcept
137  
        {
138  
        {
138  
            return &resource_;
139  
            return &resource_;
139  
        }
140  
        }
140  

141  

141  
        run_async_trampoline get_return_object() noexcept
142  
        run_async_trampoline get_return_object() noexcept
142  
        {
143  
        {
143  
            return run_async_trampoline{
144  
            return run_async_trampoline{
144  
                std::coroutine_handle<promise_type>::from_promise(*this)};
145  
                std::coroutine_handle<promise_type>::from_promise(*this)};
145  
        }
146  
        }
146  

147  

147  
        std::suspend_always initial_suspend() noexcept
148  
        std::suspend_always initial_suspend() noexcept
148  
        {
149  
        {
149  
            return {};
150  
            return {};
150  
        }
151  
        }
151  

152  

152  
        std::suspend_never final_suspend() noexcept
153  
        std::suspend_never final_suspend() noexcept
153  
        {
154  
        {
154  
            return {};
155  
            return {};
155  
        }
156  
        }
156  

157  

157  
        void return_void() noexcept
158  
        void return_void() noexcept
158  
        {
159  
        {
159  
        }
160  
        }
160  

161  

161  
        void unhandled_exception() noexcept
162  
        void unhandled_exception() noexcept
162  
        {
163  
        {
163  
        }
164  
        }
164  
    };
165  
    };
165  

166  

166  
    std::coroutine_handle<promise_type> h_;
167  
    std::coroutine_handle<promise_type> h_;
167  

168  

168  
    template<IoLaunchableTask Task>
169  
    template<IoLaunchableTask Task>
169  
    static void invoke_impl(void* p, Handlers& h)
170  
    static void invoke_impl(void* p, Handlers& h)
170  
    {
171  
    {
171  
        using R = decltype(std::declval<Task&>().await_resume());
172  
        using R = decltype(std::declval<Task&>().await_resume());
172  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
173  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
173  
        if(promise.exception())
174  
        if(promise.exception())
174  
            h(promise.exception());
175  
            h(promise.exception());
175  
        else if constexpr(std::is_void_v<R>)
176  
        else if constexpr(std::is_void_v<R>)
176  
            h();
177  
            h();
177  
        else
178  
        else
178  
            h(std::move(promise.result()));
179  
            h(std::move(promise.result()));
179  
    }
180  
    }
180  
};
181  
};
181  

182  

182  
/** Specialization for memory_resource* - stores pointer directly.
183  
/** Specialization for memory_resource* - stores pointer directly.
183  

184  

184  
    This avoids double indirection when the user passes a memory_resource*.
185  
    This avoids double indirection when the user passes a memory_resource*.
185  
*/
186  
*/
186  
template<class Ex, class Handlers>
187  
template<class Ex, class Handlers>
187  
struct run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
188  
struct run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
188  
{
189  
{
189  
    using invoke_fn = void(*)(void*, Handlers&);
190  
    using invoke_fn = void(*)(void*, Handlers&);
190  

191  

191  
    struct promise_type
192  
    struct promise_type
192  
    {
193  
    {
193 -
        Ex ex_;
194 +
        work_guard<Ex> wg_;
194  
        Handlers handlers_;
195  
        Handlers handlers_;
195  
        std::pmr::memory_resource* mr_;
196  
        std::pmr::memory_resource* mr_;
196  
        invoke_fn invoke_ = nullptr;
197  
        invoke_fn invoke_ = nullptr;
197  
        void* task_promise_ = nullptr;
198  
        void* task_promise_ = nullptr;
198  
        std::coroutine_handle<> task_h_;
199  
        std::coroutine_handle<> task_h_;
199  

200  

200  
        promise_type(
201  
        promise_type(
201  
            Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
202  
            Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
202 -
            : ex_(std::move(ex))
203 +
            : wg_(std::move(ex))
203  
            , handlers_(std::move(h))
204  
            , handlers_(std::move(h))
204  
            , mr_(mr)
205  
            , mr_(mr)
205  
        {
206  
        {
206  
        }
207  
        }
207  

208  

208  
        static void* operator new(
209  
        static void* operator new(
209  
            std::size_t size, Ex const&, Handlers const&,
210  
            std::size_t size, Ex const&, Handlers const&,
210  
            std::pmr::memory_resource* mr)
211  
            std::pmr::memory_resource* mr)
211  
        {
212  
        {
212  
            auto total = size + sizeof(mr);
213  
            auto total = size + sizeof(mr);
213  
            void* raw = mr->allocate(total, alignof(std::max_align_t));
214  
            void* raw = mr->allocate(total, alignof(std::max_align_t));
214  
            *reinterpret_cast<std::pmr::memory_resource**>(
215  
            *reinterpret_cast<std::pmr::memory_resource**>(
215  
                static_cast<char*>(raw) + size) = mr;
216  
                static_cast<char*>(raw) + size) = mr;
216  
            return raw;
217  
            return raw;
217  
        }
218  
        }
218  

219  

219  
        static void operator delete(void* ptr, std::size_t size)
220  
        static void operator delete(void* ptr, std::size_t size)
220  
        {
221  
        {
221  
            auto* mr = *reinterpret_cast<std::pmr::memory_resource**>(
222  
            auto* mr = *reinterpret_cast<std::pmr::memory_resource**>(
222  
                static_cast<char*>(ptr) + size);
223  
                static_cast<char*>(ptr) + size);
223  
            mr->deallocate(ptr, size + sizeof(mr), alignof(std::max_align_t));
224  
            mr->deallocate(ptr, size + sizeof(mr), alignof(std::max_align_t));
224  
        }
225  
        }
225  

226  

226  
        std::pmr::memory_resource* get_resource() noexcept
227  
        std::pmr::memory_resource* get_resource() noexcept
227  
        {
228  
        {
228  
            return mr_;
229  
            return mr_;
229  
        }
230  
        }
230  

231  

231  
        run_async_trampoline get_return_object() noexcept
232  
        run_async_trampoline get_return_object() noexcept
232  
        {
233  
        {
233  
            return run_async_trampoline{
234  
            return run_async_trampoline{
234  
                std::coroutine_handle<promise_type>::from_promise(*this)};
235  
                std::coroutine_handle<promise_type>::from_promise(*this)};
235  
        }
236  
        }
236  

237  

237  
        std::suspend_always initial_suspend() noexcept
238  
        std::suspend_always initial_suspend() noexcept
238  
        {
239  
        {
239  
            return {};
240  
            return {};
240  
        }
241  
        }
241  

242  

242  
        std::suspend_never final_suspend() noexcept
243  
        std::suspend_never final_suspend() noexcept
243  
        {
244  
        {
244  
            return {};
245  
            return {};
245  
        }
246  
        }
246  

247  

247  
        void return_void() noexcept
248  
        void return_void() noexcept
248  
        {
249  
        {
249  
        }
250  
        }
250  

251  

251  
        void unhandled_exception() noexcept
252  
        void unhandled_exception() noexcept
252  
        {
253  
        {
253  
        }
254  
        }
254  
    };
255  
    };
255  

256  

256  
    std::coroutine_handle<promise_type> h_;
257  
    std::coroutine_handle<promise_type> h_;
257  

258  

258  
    template<IoLaunchableTask Task>
259  
    template<IoLaunchableTask Task>
259  
    static void invoke_impl(void* p, Handlers& h)
260  
    static void invoke_impl(void* p, Handlers& h)
260  
    {
261  
    {
261  
        using R = decltype(std::declval<Task&>().await_resume());
262  
        using R = decltype(std::declval<Task&>().await_resume());
262  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
263  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
263  
        if(promise.exception())
264  
        if(promise.exception())
264  
            h(promise.exception());
265  
            h(promise.exception());
265  
        else if constexpr(std::is_void_v<R>)
266  
        else if constexpr(std::is_void_v<R>)
266  
            h();
267  
            h();
267  
        else
268  
        else
268  
            h(std::move(promise.result()));
269  
            h(std::move(promise.result()));
269  
    }
270  
    }
270  
};
271  
};
271  

272  

272  
/// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
273  
/// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
273  
template<class Ex, class Handlers, class Alloc>
274  
template<class Ex, class Handlers, class Alloc>
274  
run_async_trampoline<Ex, Handlers, Alloc>
275  
run_async_trampoline<Ex, Handlers, Alloc>
275  
make_trampoline(Ex, Handlers, Alloc)
276  
make_trampoline(Ex, Handlers, Alloc)
276  
{
277  
{
277  
    // promise_type ctor steals the parameters
278  
    // promise_type ctor steals the parameters
278  
    auto& p = co_await get_promise_awaiter<
279  
    auto& p = co_await get_promise_awaiter<
279  
        typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
280  
        typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
280  
    
281  
    
281  
    p.invoke_(p.task_promise_, p.handlers_);
282  
    p.invoke_(p.task_promise_, p.handlers_);
282  
    p.task_h_.destroy();
283  
    p.task_h_.destroy();
283  
}
284  
}
284  

285  

285  
} // namespace detail
286  
} // namespace detail
286  

287  

287  
//----------------------------------------------------------
288  
//----------------------------------------------------------
288  
//
289  
//
289  
// run_async_wrapper
290  
// run_async_wrapper
290  
//
291  
//
291  
//----------------------------------------------------------
292  
//----------------------------------------------------------
292  

293  

293  
/** Wrapper returned by run_async that accepts a task for execution.
294  
/** Wrapper returned by run_async that accepts a task for execution.
294  

295  

295  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
296  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
296  
    and handlers. The run_async_trampoline is allocated when the wrapper is constructed
297  
    and handlers. The run_async_trampoline is allocated when the wrapper is constructed
297  
    (before the task due to C++17 postfix evaluation order).
298  
    (before the task due to C++17 postfix evaluation order).
298  

299  

299  
    The rvalue ref-qualifier on `operator()` ensures the wrapper can only
300  
    The rvalue ref-qualifier on `operator()` ensures the wrapper can only
300  
    be used as a temporary, preventing misuse that would violate LIFO ordering.
301  
    be used as a temporary, preventing misuse that would violate LIFO ordering.
301  

302  

302  
    @tparam Ex The executor type satisfying the `Executor` concept.
303  
    @tparam Ex The executor type satisfying the `Executor` concept.
303  
    @tparam Handlers The handler type (default_handler or handler_pair).
304  
    @tparam Handlers The handler type (default_handler or handler_pair).
304  
    @tparam Alloc The allocator type (value type or memory_resource*).
305  
    @tparam Alloc The allocator type (value type or memory_resource*).
305  

306  

306  
    @par Thread Safety
307  
    @par Thread Safety
307  
    The wrapper itself should only be used from one thread. The handlers
308  
    The wrapper itself should only be used from one thread. The handlers
308  
    may be invoked from any thread where the executor schedules work.
309  
    may be invoked from any thread where the executor schedules work.
309  

310  

310  
    @par Example
311  
    @par Example
311  
    @code
312  
    @code
312  
    // Correct usage - wrapper is temporary
313  
    // Correct usage - wrapper is temporary
313  
    run_async(ex)(my_task());
314  
    run_async(ex)(my_task());
314  

315  

315  
    // Compile error - cannot call operator() on lvalue
316  
    // Compile error - cannot call operator() on lvalue
316  
    auto w = run_async(ex);
317  
    auto w = run_async(ex);
317  
    w(my_task());  // Error: operator() requires rvalue
318  
    w(my_task());  // Error: operator() requires rvalue
318  
    @endcode
319  
    @endcode
319  

320  

320  
    @see run_async
321  
    @see run_async
321  
*/
322  
*/
322  
template<Executor Ex, class Handlers, class Alloc>
323  
template<Executor Ex, class Handlers, class Alloc>
323  
class [[nodiscard]] run_async_wrapper
324  
class [[nodiscard]] run_async_wrapper
324  
{
325  
{
325  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
326  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
326  
    std::stop_token st_;
327  
    std::stop_token st_;
327  

328  

328  
public:
329  
public:
329  
    /// Construct wrapper with executor, stop token, handlers, and allocator.
330  
    /// Construct wrapper with executor, stop token, handlers, and allocator.
330  
    run_async_wrapper(
331  
    run_async_wrapper(
331  
        Ex ex,
332  
        Ex ex,
332  
        std::stop_token st,
333  
        std::stop_token st,
333  
        Handlers h,
334  
        Handlers h,
334  
        Alloc a) noexcept
335  
        Alloc a) noexcept
335  
        : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
336  
        : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
336  
            std::move(ex), std::move(h), std::move(a)))
337  
            std::move(ex), std::move(h), std::move(a)))
337  
        , st_(std::move(st))
338  
        , st_(std::move(st))
338  
    {
339  
    {
339  
        if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
340  
        if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
340  
        {
341  
        {
341  
            static_assert(
342  
            static_assert(
342  
                std::is_nothrow_move_constructible_v<Alloc>,
343  
                std::is_nothrow_move_constructible_v<Alloc>,
343  
                "Allocator must be nothrow move constructible");
344  
                "Allocator must be nothrow move constructible");
344  
        }
345  
        }
345  
        // Set TLS before task argument is evaluated
346  
        // Set TLS before task argument is evaluated
346  
        current_frame_allocator() = tr_.h_.promise().get_resource();
347  
        current_frame_allocator() = tr_.h_.promise().get_resource();
347  
    }
348  
    }
348  

349  

349  
    // Non-copyable, non-movable (must be used immediately)
350  
    // Non-copyable, non-movable (must be used immediately)
350  
    run_async_wrapper(run_async_wrapper const&) = delete;
351  
    run_async_wrapper(run_async_wrapper const&) = delete;
351  
    run_async_wrapper(run_async_wrapper&&) = delete;
352  
    run_async_wrapper(run_async_wrapper&&) = delete;
352  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
353  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
353  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
354  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
354  

355  

355  
    /** Launch the task for execution.
356  
    /** Launch the task for execution.
356  

357  

357  
        This operator accepts a task and launches it on the executor.
358  
        This operator accepts a task and launches it on the executor.
358  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
359  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
359  
        correct LIFO destruction order.
360  
        correct LIFO destruction order.
360  

361  

361  
        @tparam Task The IoLaunchableTask type.
362  
        @tparam Task The IoLaunchableTask type.
362  

363  

363  
        @param t The task to execute. Ownership is transferred to the
364  
        @param t The task to execute. Ownership is transferred to the
364  
                 run_async_trampoline which will destroy it after completion.
365  
                 run_async_trampoline which will destroy it after completion.
365  
    */
366  
    */
366  
    template<IoLaunchableTask Task>
367  
    template<IoLaunchableTask Task>
367  
    void operator()(Task t) &&
368  
    void operator()(Task t) &&
368  
    {
369  
    {
369  
        auto task_h = t.handle();
370  
        auto task_h = t.handle();
370  
        auto& task_promise = task_h.promise();
371  
        auto& task_promise = task_h.promise();
371  
        t.release();
372  
        t.release();
372  

373  

373  
        auto& p = tr_.h_.promise();
374  
        auto& p = tr_.h_.promise();
374  

375  

375  
        // Inject Task-specific invoke function
376  
        // Inject Task-specific invoke function
376  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
377  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
377  
        p.task_promise_ = &task_promise;
378  
        p.task_promise_ = &task_promise;
378  
        p.task_h_ = task_h;
379  
        p.task_h_ = task_h;
379  

380  

380  
        // Setup task's continuation to return to run_async_trampoline
381  
        // Setup task's continuation to return to run_async_trampoline
381 -
        task_promise.set_continuation(tr_.h_, p.ex_);
382 +
        task_promise.set_continuation(tr_.h_, p.wg_.executor());
382 -
        task_promise.set_executor(p.ex_);
383 +
        task_promise.set_executor(p.wg_.executor());
383  
        task_promise.set_stop_token(st_);
384  
        task_promise.set_stop_token(st_);
384  

385  

385  
        // Resume task through executor
386  
        // Resume task through executor
386 -
        p.ex_.dispatch(task_h);
387 +
        p.wg_.executor().dispatch(task_h);
387  
    }
388  
    }
388  
};
389  
};
389  

390  

390  
//----------------------------------------------------------
391  
//----------------------------------------------------------
391  
//
392  
//
392  
// run_async Overloads
393  
// run_async Overloads
393  
//
394  
//
394  
//----------------------------------------------------------
395  
//----------------------------------------------------------
395  

396  

396  
// Executor only (uses default recycling allocator)
397  
// Executor only (uses default recycling allocator)
397  

398  

398  
/** Asynchronously launch a lazy task on the given executor.
399  
/** Asynchronously launch a lazy task on the given executor.
399  

400  

400  
    Use this to start execution of a `task<T>` that was created lazily.
401  
    Use this to start execution of a `task<T>` that was created lazily.
401  
    The returned wrapper must be immediately invoked with the task;
402  
    The returned wrapper must be immediately invoked with the task;
402  
    storing the wrapper and calling it later violates LIFO ordering.
403  
    storing the wrapper and calling it later violates LIFO ordering.
403  

404  

404  
    Uses the default recycling frame allocator for coroutine frames.
405  
    Uses the default recycling frame allocator for coroutine frames.
405  
    With no handlers, the result is discarded and exceptions are rethrown.
406  
    With no handlers, the result is discarded and exceptions are rethrown.
406  

407  

407  
    @par Thread Safety
408  
    @par Thread Safety
408  
    The wrapper and handlers may be called from any thread where the
409  
    The wrapper and handlers may be called from any thread where the
409  
    executor schedules work.
410  
    executor schedules work.
410  

411  

411  
    @par Example
412  
    @par Example
412  
    @code
413  
    @code
413  
    run_async(ioc.get_executor())(my_task());
414  
    run_async(ioc.get_executor())(my_task());
414  
    @endcode
415  
    @endcode
415  

416  

416  
    @param ex The executor to execute the task on.
417  
    @param ex The executor to execute the task on.
417  

418  

418  
    @return A wrapper that accepts a `task<T>` for immediate execution.
419  
    @return A wrapper that accepts a `task<T>` for immediate execution.
419  

420  

420  
    @see task
421  
    @see task
421  
    @see executor
422  
    @see executor
422  
*/
423  
*/
423  
template<Executor Ex>
424  
template<Executor Ex>
424  
[[nodiscard]] auto
425  
[[nodiscard]] auto
425  
run_async(Ex ex)
426  
run_async(Ex ex)
426  
{
427  
{
427  
    auto* mr = ex.context().get_frame_allocator();
428  
    auto* mr = ex.context().get_frame_allocator();
428  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
429  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
429  
        std::move(ex),
430  
        std::move(ex),
430  
        std::stop_token{},
431  
        std::stop_token{},
431  
        detail::default_handler{},
432  
        detail::default_handler{},
432  
        mr);
433  
        mr);
433  
}
434  
}
434  

435  

435  
/** Asynchronously launch a lazy task with a result handler.
436  
/** Asynchronously launch a lazy task with a result handler.
436  

437  

437  
    The handler `h1` is called with the task's result on success. If `h1`
438  
    The handler `h1` is called with the task's result on success. If `h1`
438  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
439  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
439  
    Otherwise, exceptions are rethrown.
440  
    Otherwise, exceptions are rethrown.
440  

441  

441  
    @par Thread Safety
442  
    @par Thread Safety
442  
    The handler may be called from any thread where the executor
443  
    The handler may be called from any thread where the executor
443  
    schedules work.
444  
    schedules work.
444  

445  

445  
    @par Example
446  
    @par Example
446  
    @code
447  
    @code
447  
    // Handler for result only (exceptions rethrown)
448  
    // Handler for result only (exceptions rethrown)
448  
    run_async(ex, [](int result) {
449  
    run_async(ex, [](int result) {
449  
        std::cout << "Got: " << result << "\n";
450  
        std::cout << "Got: " << result << "\n";
450  
    })(compute_value());
451  
    })(compute_value());
451  

452  

452  
    // Overloaded handler for both result and exception
453  
    // Overloaded handler for both result and exception
453  
    run_async(ex, overloaded{
454  
    run_async(ex, overloaded{
454  
        [](int result) { std::cout << "Got: " << result << "\n"; },
455  
        [](int result) { std::cout << "Got: " << result << "\n"; },
455  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
456  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
456  
    })(compute_value());
457  
    })(compute_value());
457  
    @endcode
458  
    @endcode
458  

459  

459  
    @param ex The executor to execute the task on.
460  
    @param ex The executor to execute the task on.
460  
    @param h1 The handler to invoke with the result (and optionally exception).
461  
    @param h1 The handler to invoke with the result (and optionally exception).
461  

462  

462  
    @return A wrapper that accepts a `task<T>` for immediate execution.
463  
    @return A wrapper that accepts a `task<T>` for immediate execution.
463  

464  

464  
    @see task
465  
    @see task
465  
    @see executor
466  
    @see executor
466  
*/
467  
*/
467  
template<Executor Ex, class H1>
468  
template<Executor Ex, class H1>
468  
[[nodiscard]] auto
469  
[[nodiscard]] auto
469  
run_async(Ex ex, H1 h1)
470  
run_async(Ex ex, H1 h1)
470  
{
471  
{
471  
    auto* mr = ex.context().get_frame_allocator();
472  
    auto* mr = ex.context().get_frame_allocator();
472  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
473  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
473  
        std::move(ex),
474  
        std::move(ex),
474  
        std::stop_token{},
475  
        std::stop_token{},
475  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
476  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
476  
        mr);
477  
        mr);
477  
}
478  
}
478  

479  

479  
/** Asynchronously launch a lazy task with separate result and error handlers.
480  
/** Asynchronously launch a lazy task with separate result and error handlers.
480  

481  

481  
    The handler `h1` is called with the task's result on success.
482  
    The handler `h1` is called with the task's result on success.
482  
    The handler `h2` is called with the exception_ptr on failure.
483  
    The handler `h2` is called with the exception_ptr on failure.
483  

484  

484  
    @par Thread Safety
485  
    @par Thread Safety
485  
    The handlers may be called from any thread where the executor
486  
    The handlers may be called from any thread where the executor
486  
    schedules work.
487  
    schedules work.
487  

488  

488  
    @par Example
489  
    @par Example
489  
    @code
490  
    @code
490  
    run_async(ex,
491  
    run_async(ex,
491  
        [](int result) { std::cout << "Got: " << result << "\n"; },
492  
        [](int result) { std::cout << "Got: " << result << "\n"; },
492  
        [](std::exception_ptr ep) {
493  
        [](std::exception_ptr ep) {
493  
            try { std::rethrow_exception(ep); }
494  
            try { std::rethrow_exception(ep); }
494  
            catch (std::exception const& e) {
495  
            catch (std::exception const& e) {
495  
                std::cout << "Error: " << e.what() << "\n";
496  
                std::cout << "Error: " << e.what() << "\n";
496  
            }
497  
            }
497  
        }
498  
        }
498  
    )(compute_value());
499  
    )(compute_value());
499  
    @endcode
500  
    @endcode
500  

501  

501  
    @param ex The executor to execute the task on.
502  
    @param ex The executor to execute the task on.
502  
    @param h1 The handler to invoke with the result on success.
503  
    @param h1 The handler to invoke with the result on success.
503  
    @param h2 The handler to invoke with the exception on failure.
504  
    @param h2 The handler to invoke with the exception on failure.
504  

505  

505  
    @return A wrapper that accepts a `task<T>` for immediate execution.
506  
    @return A wrapper that accepts a `task<T>` for immediate execution.
506  

507  

507  
    @see task
508  
    @see task
508  
    @see executor
509  
    @see executor
509  
*/
510  
*/
510  
template<Executor Ex, class H1, class H2>
511  
template<Executor Ex, class H1, class H2>
511  
[[nodiscard]] auto
512  
[[nodiscard]] auto
512  
run_async(Ex ex, H1 h1, H2 h2)
513  
run_async(Ex ex, H1 h1, H2 h2)
513  
{
514  
{
514  
    auto* mr = ex.context().get_frame_allocator();
515  
    auto* mr = ex.context().get_frame_allocator();
515  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
516  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
516  
        std::move(ex),
517  
        std::move(ex),
517  
        std::stop_token{},
518  
        std::stop_token{},
518  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
519  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
519  
        mr);
520  
        mr);
520  
}
521  
}
521  

522  

522  
// Ex + stop_token
523  
// Ex + stop_token
523  

524  

524  
/** Asynchronously launch a lazy task with stop token support.
525  
/** Asynchronously launch a lazy task with stop token support.
525  

526  

526  
    The stop token is propagated to the task, enabling cooperative
527  
    The stop token is propagated to the task, enabling cooperative
527  
    cancellation. With no handlers, the result is discarded and
528  
    cancellation. With no handlers, the result is discarded and
528  
    exceptions are rethrown.
529  
    exceptions are rethrown.
529  

530  

530  
    @par Thread Safety
531  
    @par Thread Safety
531  
    The wrapper may be called from any thread where the executor
532  
    The wrapper may be called from any thread where the executor
532  
    schedules work.
533  
    schedules work.
533  

534  

534  
    @par Example
535  
    @par Example
535  
    @code
536  
    @code
536  
    std::stop_source source;
537  
    std::stop_source source;
537  
    run_async(ex, source.get_token())(cancellable_task());
538  
    run_async(ex, source.get_token())(cancellable_task());
538  
    // Later: source.request_stop();
539  
    // Later: source.request_stop();
539  
    @endcode
540  
    @endcode
540  

541  

541  
    @param ex The executor to execute the task on.
542  
    @param ex The executor to execute the task on.
542  
    @param st The stop token for cooperative cancellation.
543  
    @param st The stop token for cooperative cancellation.
543  

544  

544  
    @return A wrapper that accepts a `task<T>` for immediate execution.
545  
    @return A wrapper that accepts a `task<T>` for immediate execution.
545  

546  

546  
    @see task
547  
    @see task
547  
    @see executor
548  
    @see executor
548  
*/
549  
*/
549  
template<Executor Ex>
550  
template<Executor Ex>
550  
[[nodiscard]] auto
551  
[[nodiscard]] auto
551  
run_async(Ex ex, std::stop_token st)
552  
run_async(Ex ex, std::stop_token st)
552  
{
553  
{
553  
    auto* mr = ex.context().get_frame_allocator();
554  
    auto* mr = ex.context().get_frame_allocator();
554  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
555  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
555  
        std::move(ex),
556  
        std::move(ex),
556  
        std::move(st),
557  
        std::move(st),
557  
        detail::default_handler{},
558  
        detail::default_handler{},
558  
        mr);
559  
        mr);
559  
}
560  
}
560  

561  

561  
/** Asynchronously launch a lazy task with stop token and result handler.
562  
/** Asynchronously launch a lazy task with stop token and result handler.
562  

563  

563  
    The stop token is propagated to the task for cooperative cancellation.
564  
    The stop token is propagated to the task for cooperative cancellation.
564  
    The handler `h1` is called with the result on success, and optionally
565  
    The handler `h1` is called with the result on success, and optionally
565  
    with exception_ptr if it accepts that type.
566  
    with exception_ptr if it accepts that type.
566  

567  

567  
    @param ex The executor to execute the task on.
568  
    @param ex The executor to execute the task on.
568  
    @param st The stop token for cooperative cancellation.
569  
    @param st The stop token for cooperative cancellation.
569  
    @param h1 The handler to invoke with the result (and optionally exception).
570  
    @param h1 The handler to invoke with the result (and optionally exception).
570  

571  

571  
    @return A wrapper that accepts a `task<T>` for immediate execution.
572  
    @return A wrapper that accepts a `task<T>` for immediate execution.
572  

573  

573  
    @see task
574  
    @see task
574  
    @see executor
575  
    @see executor
575  
*/
576  
*/
576  
template<Executor Ex, class H1>
577  
template<Executor Ex, class H1>
577  
[[nodiscard]] auto
578  
[[nodiscard]] auto
578  
run_async(Ex ex, std::stop_token st, H1 h1)
579  
run_async(Ex ex, std::stop_token st, H1 h1)
579  
{
580  
{
580  
    auto* mr = ex.context().get_frame_allocator();
581  
    auto* mr = ex.context().get_frame_allocator();
581  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
582  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
582  
        std::move(ex),
583  
        std::move(ex),
583  
        std::move(st),
584  
        std::move(st),
584  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
585  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
585  
        mr);
586  
        mr);
586  
}
587  
}
587  

588  

588  
/** Asynchronously launch a lazy task with stop token and separate handlers.
589  
/** Asynchronously launch a lazy task with stop token and separate handlers.
589  

590  

590  
    The stop token is propagated to the task for cooperative cancellation.
591  
    The stop token is propagated to the task for cooperative cancellation.
591  
    The handler `h1` is called on success, `h2` on failure.
592  
    The handler `h1` is called on success, `h2` on failure.
592  

593  

593  
    @param ex The executor to execute the task on.
594  
    @param ex The executor to execute the task on.
594  
    @param st The stop token for cooperative cancellation.
595  
    @param st The stop token for cooperative cancellation.
595  
    @param h1 The handler to invoke with the result on success.
596  
    @param h1 The handler to invoke with the result on success.
596  
    @param h2 The handler to invoke with the exception on failure.
597  
    @param h2 The handler to invoke with the exception on failure.
597  

598  

598  
    @return A wrapper that accepts a `task<T>` for immediate execution.
599  
    @return A wrapper that accepts a `task<T>` for immediate execution.
599  

600  

600  
    @see task
601  
    @see task
601  
    @see executor
602  
    @see executor
602  
*/
603  
*/
603  
template<Executor Ex, class H1, class H2>
604  
template<Executor Ex, class H1, class H2>
604  
[[nodiscard]] auto
605  
[[nodiscard]] auto
605  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
606  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
606  
{
607  
{
607  
    auto* mr = ex.context().get_frame_allocator();
608  
    auto* mr = ex.context().get_frame_allocator();
608  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
609  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
609  
        std::move(ex),
610  
        std::move(ex),
610  
        std::move(st),
611  
        std::move(st),
611  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
612  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
612  
        mr);
613  
        mr);
613  
}
614  
}
614  

615  

615  
// Ex + memory_resource*
616  
// Ex + memory_resource*
616  

617  

617  
/** Asynchronously launch a lazy task with custom memory resource.
618  
/** Asynchronously launch a lazy task with custom memory resource.
618  

619  

619  
    The memory resource is used for coroutine frame allocation. The caller
620  
    The memory resource is used for coroutine frame allocation. The caller
620  
    is responsible for ensuring the memory resource outlives all tasks.
621  
    is responsible for ensuring the memory resource outlives all tasks.
621  

622  

622  
    @param ex The executor to execute the task on.
623  
    @param ex The executor to execute the task on.
623  
    @param mr The memory resource for frame allocation.
624  
    @param mr The memory resource for frame allocation.
624  

625  

625  
    @return A wrapper that accepts a `task<T>` for immediate execution.
626  
    @return A wrapper that accepts a `task<T>` for immediate execution.
626  

627  

627  
    @see task
628  
    @see task
628  
    @see executor
629  
    @see executor
629  
*/
630  
*/
630  
template<Executor Ex>
631  
template<Executor Ex>
631  
[[nodiscard]] auto
632  
[[nodiscard]] auto
632  
run_async(Ex ex, std::pmr::memory_resource* mr)
633  
run_async(Ex ex, std::pmr::memory_resource* mr)
633  
{
634  
{
634  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
635  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
635  
        std::move(ex),
636  
        std::move(ex),
636  
        std::stop_token{},
637  
        std::stop_token{},
637  
        detail::default_handler{},
638  
        detail::default_handler{},
638  
        mr);
639  
        mr);
639  
}
640  
}
640  

641  

641  
/** Asynchronously launch a lazy task with memory resource and handler.
642  
/** Asynchronously launch a lazy task with memory resource and handler.
642  

643  

643  
    @param ex The executor to execute the task on.
644  
    @param ex The executor to execute the task on.
644  
    @param mr The memory resource for frame allocation.
645  
    @param mr The memory resource for frame allocation.
645  
    @param h1 The handler to invoke with the result (and optionally exception).
646  
    @param h1 The handler to invoke with the result (and optionally exception).
646  

647  

647  
    @return A wrapper that accepts a `task<T>` for immediate execution.
648  
    @return A wrapper that accepts a `task<T>` for immediate execution.
648  

649  

649  
    @see task
650  
    @see task
650  
    @see executor
651  
    @see executor
651  
*/
652  
*/
652  
template<Executor Ex, class H1>
653  
template<Executor Ex, class H1>
653  
[[nodiscard]] auto
654  
[[nodiscard]] auto
654  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
655  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
655  
{
656  
{
656  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
657  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
657  
        std::move(ex),
658  
        std::move(ex),
658  
        std::stop_token{},
659  
        std::stop_token{},
659  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
660  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
660  
        mr);
661  
        mr);
661  
}
662  
}
662  

663  

663  
/** Asynchronously launch a lazy task with memory resource and handlers.
664  
/** Asynchronously launch a lazy task with memory resource and handlers.
664  

665  

665  
    @param ex The executor to execute the task on.
666  
    @param ex The executor to execute the task on.
666  
    @param mr The memory resource for frame allocation.
667  
    @param mr The memory resource for frame allocation.
667  
    @param h1 The handler to invoke with the result on success.
668  
    @param h1 The handler to invoke with the result on success.
668  
    @param h2 The handler to invoke with the exception on failure.
669  
    @param h2 The handler to invoke with the exception on failure.
669  

670  

670  
    @return A wrapper that accepts a `task<T>` for immediate execution.
671  
    @return A wrapper that accepts a `task<T>` for immediate execution.
671  

672  

672  
    @see task
673  
    @see task
673  
    @see executor
674  
    @see executor
674  
*/
675  
*/
675  
template<Executor Ex, class H1, class H2>
676  
template<Executor Ex, class H1, class H2>
676  
[[nodiscard]] auto
677  
[[nodiscard]] auto
677  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
678  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
678  
{
679  
{
679  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
680  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
680  
        std::move(ex),
681  
        std::move(ex),
681  
        std::stop_token{},
682  
        std::stop_token{},
682  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
683  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
683  
        mr);
684  
        mr);
684  
}
685  
}
685  

686  

686  
// Ex + stop_token + memory_resource*
687  
// Ex + stop_token + memory_resource*
687  

688  

688  
/** Asynchronously launch a lazy task with stop token and memory resource.
689  
/** Asynchronously launch a lazy task with stop token and memory resource.
689  

690  

690  
    @param ex The executor to execute the task on.
691  
    @param ex The executor to execute the task on.
691  
    @param st The stop token for cooperative cancellation.
692  
    @param st The stop token for cooperative cancellation.
692  
    @param mr The memory resource for frame allocation.
693  
    @param mr The memory resource for frame allocation.
693  

694  

694  
    @return A wrapper that accepts a `task<T>` for immediate execution.
695  
    @return A wrapper that accepts a `task<T>` for immediate execution.
695  

696  

696  
    @see task
697  
    @see task
697  
    @see executor
698  
    @see executor
698  
*/
699  
*/
699  
template<Executor Ex>
700  
template<Executor Ex>
700  
[[nodiscard]] auto
701  
[[nodiscard]] auto
701  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
702  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
702  
{
703  
{
703  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
704  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
704  
        std::move(ex),
705  
        std::move(ex),
705  
        std::move(st),
706  
        std::move(st),
706  
        detail::default_handler{},
707  
        detail::default_handler{},
707  
        mr);
708  
        mr);
708  
}
709  
}
709  

710  

710  
/** Asynchronously launch a lazy task with stop token, memory resource, and handler.
711  
/** Asynchronously launch a lazy task with stop token, memory resource, and handler.
711  

712  

712  
    @param ex The executor to execute the task on.
713  
    @param ex The executor to execute the task on.
713  
    @param st The stop token for cooperative cancellation.
714  
    @param st The stop token for cooperative cancellation.
714  
    @param mr The memory resource for frame allocation.
715  
    @param mr The memory resource for frame allocation.
715  
    @param h1 The handler to invoke with the result (and optionally exception).
716  
    @param h1 The handler to invoke with the result (and optionally exception).
716  

717  

717  
    @return A wrapper that accepts a `task<T>` for immediate execution.
718  
    @return A wrapper that accepts a `task<T>` for immediate execution.
718  

719  

719  
    @see task
720  
    @see task
720  
    @see executor
721  
    @see executor
721  
*/
722  
*/
722  
template<Executor Ex, class H1>
723  
template<Executor Ex, class H1>
723  
[[nodiscard]] auto
724  
[[nodiscard]] auto
724  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
725  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
725  
{
726  
{
726  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
727  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
727  
        std::move(ex),
728  
        std::move(ex),
728  
        std::move(st),
729  
        std::move(st),
729  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
730  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
730  
        mr);
731  
        mr);
731  
}
732  
}
732  

733  

733  
/** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
734  
/** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
734  

735  

735  
    @param ex The executor to execute the task on.
736  
    @param ex The executor to execute the task on.
736  
    @param st The stop token for cooperative cancellation.
737  
    @param st The stop token for cooperative cancellation.
737  
    @param mr The memory resource for frame allocation.
738  
    @param mr The memory resource for frame allocation.
738  
    @param h1 The handler to invoke with the result on success.
739  
    @param h1 The handler to invoke with the result on success.
739  
    @param h2 The handler to invoke with the exception on failure.
740  
    @param h2 The handler to invoke with the exception on failure.
740  

741  

741  
    @return A wrapper that accepts a `task<T>` for immediate execution.
742  
    @return A wrapper that accepts a `task<T>` for immediate execution.
742  

743  

743  
    @see task
744  
    @see task
744  
    @see executor
745  
    @see executor
745  
*/
746  
*/
746  
template<Executor Ex, class H1, class H2>
747  
template<Executor Ex, class H1, class H2>
747  
[[nodiscard]] auto
748  
[[nodiscard]] auto
748  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
749  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
749  
{
750  
{
750  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
751  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
751  
        std::move(ex),
752  
        std::move(ex),
752  
        std::move(st),
753  
        std::move(st),
753  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
754  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
754  
        mr);
755  
        mr);
755  
}
756  
}
756  

757  

757  
// Ex + standard Allocator (value type)
758  
// Ex + standard Allocator (value type)
758  

759  

759  
/** Asynchronously launch a lazy task with custom allocator.
760  
/** Asynchronously launch a lazy task with custom allocator.
760  

761  

761  
    The allocator is wrapped in a frame_memory_resource and stored in the
762  
    The allocator is wrapped in a frame_memory_resource and stored in the
762  
    run_async_trampoline, ensuring it outlives all coroutine frames.
763  
    run_async_trampoline, ensuring it outlives all coroutine frames.
763  

764  

764  
    @param ex The executor to execute the task on.
765  
    @param ex The executor to execute the task on.
765  
    @param alloc The allocator for frame allocation (copied and stored).
766  
    @param alloc The allocator for frame allocation (copied and stored).
766  

767  

767  
    @return A wrapper that accepts a `task<T>` for immediate execution.
768  
    @return A wrapper that accepts a `task<T>` for immediate execution.
768  

769  

769  
    @see task
770  
    @see task
770  
    @see executor
771  
    @see executor
771  
*/
772  
*/
772  
template<Executor Ex, detail::Allocator Alloc>
773  
template<Executor Ex, detail::Allocator Alloc>
773  
[[nodiscard]] auto
774  
[[nodiscard]] auto
774  
run_async(Ex ex, Alloc alloc)
775  
run_async(Ex ex, Alloc alloc)
775  
{
776  
{
776  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
777  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
777  
        std::move(ex),
778  
        std::move(ex),
778  
        std::stop_token{},
779  
        std::stop_token{},
779  
        detail::default_handler{},
780  
        detail::default_handler{},
780  
        std::move(alloc));
781  
        std::move(alloc));
781  
}
782  
}
782  

783  

783  
/** Asynchronously launch a lazy task with allocator and handler.
784  
/** Asynchronously launch a lazy task with allocator and handler.
784  

785  

785  
    @param ex The executor to execute the task on.
786  
    @param ex The executor to execute the task on.
786  
    @param alloc The allocator for frame allocation (copied and stored).
787  
    @param alloc The allocator for frame allocation (copied and stored).
787  
    @param h1 The handler to invoke with the result (and optionally exception).
788  
    @param h1 The handler to invoke with the result (and optionally exception).
788  

789  

789  
    @return A wrapper that accepts a `task<T>` for immediate execution.
790  
    @return A wrapper that accepts a `task<T>` for immediate execution.
790  

791  

791  
    @see task
792  
    @see task
792  
    @see executor
793  
    @see executor
793  
*/
794  
*/
794  
template<Executor Ex, detail::Allocator Alloc, class H1>
795  
template<Executor Ex, detail::Allocator Alloc, class H1>
795  
[[nodiscard]] auto
796  
[[nodiscard]] auto
796  
run_async(Ex ex, Alloc alloc, H1 h1)
797  
run_async(Ex ex, Alloc alloc, H1 h1)
797  
{
798  
{
798  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
799  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
799  
        std::move(ex),
800  
        std::move(ex),
800  
        std::stop_token{},
801  
        std::stop_token{},
801  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
802  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
802  
        std::move(alloc));
803  
        std::move(alloc));
803  
}
804  
}
804  

805  

805  
/** Asynchronously launch a lazy task with allocator and handlers.
806  
/** Asynchronously launch a lazy task with allocator and handlers.
806  

807  

807  
    @param ex The executor to execute the task on.
808  
    @param ex The executor to execute the task on.
808  
    @param alloc The allocator for frame allocation (copied and stored).
809  
    @param alloc The allocator for frame allocation (copied and stored).
809  
    @param h1 The handler to invoke with the result on success.
810  
    @param h1 The handler to invoke with the result on success.
810  
    @param h2 The handler to invoke with the exception on failure.
811  
    @param h2 The handler to invoke with the exception on failure.
811  

812  

812  
    @return A wrapper that accepts a `task<T>` for immediate execution.
813  
    @return A wrapper that accepts a `task<T>` for immediate execution.
813  

814  

814  
    @see task
815  
    @see task
815  
    @see executor
816  
    @see executor
816  
*/
817  
*/
817  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
818  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
818  
[[nodiscard]] auto
819  
[[nodiscard]] auto
819  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
820  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
820  
{
821  
{
821  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
822  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
822  
        std::move(ex),
823  
        std::move(ex),
823  
        std::stop_token{},
824  
        std::stop_token{},
824  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
825  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
825  
        std::move(alloc));
826  
        std::move(alloc));
826  
}
827  
}
827  

828  

828  
// Ex + stop_token + standard Allocator
829  
// Ex + stop_token + standard Allocator
829  

830  

830  
/** Asynchronously launch a lazy task with stop token and allocator.
831  
/** Asynchronously launch a lazy task with stop token and allocator.
831  

832  

832  
    @param ex The executor to execute the task on.
833  
    @param ex The executor to execute the task on.
833  
    @param st The stop token for cooperative cancellation.
834  
    @param st The stop token for cooperative cancellation.
834  
    @param alloc The allocator for frame allocation (copied and stored).
835  
    @param alloc The allocator for frame allocation (copied and stored).
835  

836  

836  
    @return A wrapper that accepts a `task<T>` for immediate execution.
837  
    @return A wrapper that accepts a `task<T>` for immediate execution.
837  

838  

838  
    @see task
839  
    @see task
839  
    @see executor
840  
    @see executor
840  
*/
841  
*/
841  
template<Executor Ex, detail::Allocator Alloc>
842  
template<Executor Ex, detail::Allocator Alloc>
842  
[[nodiscard]] auto
843  
[[nodiscard]] auto
843  
run_async(Ex ex, std::stop_token st, Alloc alloc)
844  
run_async(Ex ex, std::stop_token st, Alloc alloc)
844  
{
845  
{
845  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
846  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
846  
        std::move(ex),
847  
        std::move(ex),
847  
        std::move(st),
848  
        std::move(st),
848  
        detail::default_handler{},
849  
        detail::default_handler{},
849  
        std::move(alloc));
850  
        std::move(alloc));
850  
}
851  
}
851  

852  

852  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
853  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
853  

854  

854  
    @param ex The executor to execute the task on.
855  
    @param ex The executor to execute the task on.
855  
    @param st The stop token for cooperative cancellation.
856  
    @param st The stop token for cooperative cancellation.
856  
    @param alloc The allocator for frame allocation (copied and stored).
857  
    @param alloc The allocator for frame allocation (copied and stored).
857  
    @param h1 The handler to invoke with the result (and optionally exception).
858  
    @param h1 The handler to invoke with the result (and optionally exception).
858  

859  

859  
    @return A wrapper that accepts a `task<T>` for immediate execution.
860  
    @return A wrapper that accepts a `task<T>` for immediate execution.
860  

861  

861  
    @see task
862  
    @see task
862  
    @see executor
863  
    @see executor
863  
*/
864  
*/
864  
template<Executor Ex, detail::Allocator Alloc, class H1>
865  
template<Executor Ex, detail::Allocator Alloc, class H1>
865  
[[nodiscard]] auto
866  
[[nodiscard]] auto
866  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
867  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
867  
{
868  
{
868  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
869  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
869  
        std::move(ex),
870  
        std::move(ex),
870  
        std::move(st),
871  
        std::move(st),
871  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
872  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
872  
        std::move(alloc));
873  
        std::move(alloc));
873  
}
874  
}
874  

875  

875  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
876  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
876  

877  

877  
    @param ex The executor to execute the task on.
878  
    @param ex The executor to execute the task on.
878  
    @param st The stop token for cooperative cancellation.
879  
    @param st The stop token for cooperative cancellation.
879  
    @param alloc The allocator for frame allocation (copied and stored).
880  
    @param alloc The allocator for frame allocation (copied and stored).
880  
    @param h1 The handler to invoke with the result on success.
881  
    @param h1 The handler to invoke with the result on success.
881  
    @param h2 The handler to invoke with the exception on failure.
882  
    @param h2 The handler to invoke with the exception on failure.
882  

883  

883  
    @return A wrapper that accepts a `task<T>` for immediate execution.
884  
    @return A wrapper that accepts a `task<T>` for immediate execution.
884  

885  

885  
    @see task
886  
    @see task
886  
    @see executor
887  
    @see executor
887  
*/
888  
*/
888  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
889  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
889  
[[nodiscard]] auto
890  
[[nodiscard]] auto
890  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
891  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
891  
{
892  
{
892  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
893  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
893  
        std::move(ex),
894  
        std::move(ex),
894  
        std::move(st),
895  
        std::move(st),
895  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
896  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
896  
        std::move(alloc));
897  
        std::move(alloc));
897  
}
898  
}
898  

899  

899  
} // namespace capy
900  
} // namespace capy
900  
} // namespace boost
901  
} // namespace boost
901  

902  

902  
#endif
903  
#endif