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_runnable.hpp>
17  
#include <boost/capy/concept/io_runnable.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/io_env.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
21  
#include <boost/capy/ex/recycling_memory_resource.hpp>
21  
#include <boost/capy/ex/recycling_memory_resource.hpp>
22  
#include <boost/capy/ex/work_guard.hpp>
22  
#include <boost/capy/ex/work_guard.hpp>
23  

23  

24  
#include <coroutine>
24  
#include <coroutine>
 
25 +
#include <cstring>
25  
#include <memory_resource>
26  
#include <memory_resource>
26  
#include <new>
27  
#include <new>
27  
#include <stop_token>
28  
#include <stop_token>
28  
#include <type_traits>
29  
#include <type_traits>
29  

30  

30  
namespace boost {
31  
namespace boost {
31  
namespace capy {
32  
namespace capy {
32  
namespace detail {
33  
namespace detail {
33  

34  

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

37  

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

49  

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

55  

55  
    bool await_ready() const noexcept { return false; }
56  
    bool await_ready() const noexcept { return false; }
56  

57  

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

63  

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

69  

69  
/** Internal run_async_trampoline coroutine for run_async.
70  
/** Internal run_async_trampoline coroutine for run_async.
70  

71  

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

75  

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

78  

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

87  

87  
    struct promise_type
88  
    struct promise_type
88  
    {
89  
    {
89  
        work_guard<Ex> wg_;
90  
        work_guard<Ex> wg_;
90  
        Handlers handlers_;
91  
        Handlers handlers_;
91  
        frame_memory_resource<Alloc> resource_;
92  
        frame_memory_resource<Alloc> resource_;
92  
        io_env env_;
93  
        io_env env_;
93  
        invoke_fn invoke_ = nullptr;
94  
        invoke_fn invoke_ = nullptr;
94  
        void* task_promise_ = nullptr;
95  
        void* task_promise_ = nullptr;
95  
        std::coroutine_handle<> task_h_;
96  
        std::coroutine_handle<> task_h_;
96  

97  

97  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
98  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
98  
            : wg_(std::move(ex))
99  
            : wg_(std::move(ex))
99  
            , handlers_(std::move(h))
100  
            , handlers_(std::move(h))
100  
            , resource_(std::move(a))
101  
            , resource_(std::move(a))
101  
        {
102  
        {
102  
        }
103  
        }
103  

104  

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

110  

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

115  

115  
            byte_alloc ba(std::move(a));
116  
            byte_alloc ba(std::move(a));
116  
            void* raw = ba.allocate(total);
117  
            void* raw = ba.allocate(total);
117  

118  

118  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
119  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
119  
                static_cast<char*>(raw) + padded);
120  
                static_cast<char*>(raw) + padded);
120  
            *fn_loc = &dealloc_impl<byte_alloc>;
121  
            *fn_loc = &dealloc_impl<byte_alloc>;
121  

122  

122  
            new (fn_loc + 1) byte_alloc(std::move(ba));
123  
            new (fn_loc + 1) byte_alloc(std::move(ba));
123  

124  

124  
            return raw;
125  
            return raw;
125  
        }
126  
        }
126  

127  

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

134  

134  
            auto* fn = reinterpret_cast<dealloc_fn*>(
135  
            auto* fn = reinterpret_cast<dealloc_fn*>(
135  
                static_cast<char*>(ptr) + padded);
136  
                static_cast<char*>(ptr) + padded);
136  
            (*fn)(ptr, total);
137  
            (*fn)(ptr, total);
137  
        }
138  
        }
138  

139  

139  
        std::pmr::memory_resource* get_resource() noexcept
140  
        std::pmr::memory_resource* get_resource() noexcept
140  
        {
141  
        {
141  
            return &resource_;
142  
            return &resource_;
142  
        }
143  
        }
143  

144  

144  
        run_async_trampoline get_return_object() noexcept
145  
        run_async_trampoline get_return_object() noexcept
145  
        {
146  
        {
146  
            return run_async_trampoline{
147  
            return run_async_trampoline{
147  
                std::coroutine_handle<promise_type>::from_promise(*this)};
148  
                std::coroutine_handle<promise_type>::from_promise(*this)};
148  
        }
149  
        }
149  

150  

150  
        std::suspend_always initial_suspend() noexcept
151  
        std::suspend_always initial_suspend() noexcept
151  
        {
152  
        {
152  
            return {};
153  
            return {};
153  
        }
154  
        }
154  

155  

155  
        std::suspend_never final_suspend() noexcept
156  
        std::suspend_never final_suspend() noexcept
156  
        {
157  
        {
157  
            return {};
158  
            return {};
158  
        }
159  
        }
159  

160  

160  
        void return_void() noexcept
161  
        void return_void() noexcept
161  
        {
162  
        {
162  
        }
163  
        }
163  

164  

164  
        void unhandled_exception() noexcept
165  
        void unhandled_exception() noexcept
165  
        {
166  
        {
166  
        }
167  
        }
167  
    };
168  
    };
168  

169  

169  
    std::coroutine_handle<promise_type> h_;
170  
    std::coroutine_handle<promise_type> h_;
170  

171  

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

185  

185  
/** Specialization for memory_resource* - stores pointer directly.
186  
/** Specialization for memory_resource* - stores pointer directly.
186  

187  

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

194  

194  
    struct promise_type
195  
    struct promise_type
195  
    {
196  
    {
196  
        work_guard<Ex> wg_;
197  
        work_guard<Ex> wg_;
197  
        Handlers handlers_;
198  
        Handlers handlers_;
198  
        std::pmr::memory_resource* mr_;
199  
        std::pmr::memory_resource* mr_;
199  
        io_env env_;
200  
        io_env env_;
200  
        invoke_fn invoke_ = nullptr;
201  
        invoke_fn invoke_ = nullptr;
201  
        void* task_promise_ = nullptr;
202  
        void* task_promise_ = nullptr;
202  
        std::coroutine_handle<> task_h_;
203  
        std::coroutine_handle<> task_h_;
203  

204  

204  
        promise_type(
205  
        promise_type(
205  
            Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
206  
            Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
206  
            : wg_(std::move(ex))
207  
            : wg_(std::move(ex))
207  
            , handlers_(std::move(h))
208  
            , handlers_(std::move(h))
208  
            , mr_(mr)
209  
            , mr_(mr)
209  
        {
210  
        {
210  
        }
211  
        }
211  

212  

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

222  

223  
        static void operator delete(void* ptr, std::size_t size)
223  
        static void operator delete(void* ptr, std::size_t size)
224  
        {
224  
        {
225 -
            auto* mr = *reinterpret_cast<std::pmr::memory_resource**>(
225 +
            std::pmr::memory_resource* mr;
226 -
                static_cast<char*>(ptr) + size);
226 +
            std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
227 -
            mr->deallocate(ptr, size + sizeof(mr), alignof(std::max_align_t));
227 +
            auto total = size + sizeof(mr);
 
228 +
            mr->deallocate(ptr, total, alignof(std::max_align_t));
228  
        }
229  
        }
229  

230  

230  
        std::pmr::memory_resource* get_resource() noexcept
231  
        std::pmr::memory_resource* get_resource() noexcept
231  
        {
232  
        {
232  
            return mr_;
233  
            return mr_;
233  
        }
234  
        }
234  

235  

235  
        run_async_trampoline get_return_object() noexcept
236  
        run_async_trampoline get_return_object() noexcept
236  
        {
237  
        {
237  
            return run_async_trampoline{
238  
            return run_async_trampoline{
238  
                std::coroutine_handle<promise_type>::from_promise(*this)};
239  
                std::coroutine_handle<promise_type>::from_promise(*this)};
239  
        }
240  
        }
240  

241  

241  
        std::suspend_always initial_suspend() noexcept
242  
        std::suspend_always initial_suspend() noexcept
242  
        {
243  
        {
243  
            return {};
244  
            return {};
244  
        }
245  
        }
245  

246  

246  
        std::suspend_never final_suspend() noexcept
247  
        std::suspend_never final_suspend() noexcept
247  
        {
248  
        {
248  
            return {};
249  
            return {};
249  
        }
250  
        }
250  

251  

251  
        void return_void() noexcept
252  
        void return_void() noexcept
252  
        {
253  
        {
253  
        }
254  
        }
254  

255  

255  
        void unhandled_exception() noexcept
256  
        void unhandled_exception() noexcept
256  
        {
257  
        {
257  
        }
258  
        }
258  
    };
259  
    };
259  

260  

260  
    std::coroutine_handle<promise_type> h_;
261  
    std::coroutine_handle<promise_type> h_;
261  

262  

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

276  

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

289  

289  
} // namespace detail
290  
} // namespace detail
290  

291  

291  
//----------------------------------------------------------
292  
//----------------------------------------------------------
292  
//
293  
//
293  
// run_async_wrapper
294  
// run_async_wrapper
294  
//
295  
//
295  
//----------------------------------------------------------
296  
//----------------------------------------------------------
296  

297  

297  
/** Wrapper returned by run_async that accepts a task for execution.
298  
/** Wrapper returned by run_async that accepts a task for execution.
298  

299  

299  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
300  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
300  
    and handlers. The run_async_trampoline is allocated when the wrapper is constructed
301  
    and handlers. The run_async_trampoline is allocated when the wrapper is constructed
301  
    (before the task due to C++17 postfix evaluation order).
302  
    (before the task due to C++17 postfix evaluation order).
302  

303  

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

306  

306  
    @tparam Ex The executor type satisfying the `Executor` concept.
307  
    @tparam Ex The executor type satisfying the `Executor` concept.
307  
    @tparam Handlers The handler type (default_handler or handler_pair).
308  
    @tparam Handlers The handler type (default_handler or handler_pair).
308  
    @tparam Alloc The allocator type (value type or memory_resource*).
309  
    @tparam Alloc The allocator type (value type or memory_resource*).
309  

310  

310  
    @par Thread Safety
311  
    @par Thread Safety
311  
    The wrapper itself should only be used from one thread. The handlers
312  
    The wrapper itself should only be used from one thread. The handlers
312  
    may be invoked from any thread where the executor schedules work.
313  
    may be invoked from any thread where the executor schedules work.
313  

314  

314  
    @par Example
315  
    @par Example
315  
    @code
316  
    @code
316  
    // Correct usage - wrapper is temporary
317  
    // Correct usage - wrapper is temporary
317  
    run_async(ex)(my_task());
318  
    run_async(ex)(my_task());
318  

319  

319  
    // Compile error - cannot call operator() on lvalue
320  
    // Compile error - cannot call operator() on lvalue
320  
    auto w = run_async(ex);
321  
    auto w = run_async(ex);
321  
    w(my_task());  // Error: operator() requires rvalue
322  
    w(my_task());  // Error: operator() requires rvalue
322  
    @endcode
323  
    @endcode
323  

324  

324  
    @see run_async
325  
    @see run_async
325  
*/
326  
*/
326  
template<Executor Ex, class Handlers, class Alloc>
327  
template<Executor Ex, class Handlers, class Alloc>
327  
class [[nodiscard]] run_async_wrapper
328  
class [[nodiscard]] run_async_wrapper
328  
{
329  
{
329  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
330  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
330  
    std::stop_token st_;
331  
    std::stop_token st_;
331  
    std::pmr::memory_resource* saved_tls_;
332  
    std::pmr::memory_resource* saved_tls_;
332  

333  

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

355  

355  
    ~run_async_wrapper()
356  
    ~run_async_wrapper()
356  
    {
357  
    {
357  
        // Restore TLS so stale pointer doesn't outlive
358  
        // Restore TLS so stale pointer doesn't outlive
358  
        // the execution context that owns the resource.
359  
        // the execution context that owns the resource.
359  
        current_frame_allocator() = saved_tls_;
360  
        current_frame_allocator() = saved_tls_;
360  
    }
361  
    }
361  

362  

362  
    // Non-copyable, non-movable (must be used immediately)
363  
    // Non-copyable, non-movable (must be used immediately)
363  
    run_async_wrapper(run_async_wrapper const&) = delete;
364  
    run_async_wrapper(run_async_wrapper const&) = delete;
364  
    run_async_wrapper(run_async_wrapper&&) = delete;
365  
    run_async_wrapper(run_async_wrapper&&) = delete;
365  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
366  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
366  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
367  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
367  

368  

368  
    /** Launch the task for execution.
369  
    /** Launch the task for execution.
369  

370  

370  
        This operator accepts a task and launches it on the executor.
371  
        This operator accepts a task and launches it on the executor.
371  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
372  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
372  
        correct LIFO destruction order.
373  
        correct LIFO destruction order.
373  

374  

374  
        The `io_env` constructed for the task is owned by the trampoline
375  
        The `io_env` constructed for the task is owned by the trampoline
375  
        coroutine and is guaranteed to outlive the task and all awaitables
376  
        coroutine and is guaranteed to outlive the task and all awaitables
376  
        in its chain. Awaitables may store `io_env const*` without concern
377  
        in its chain. Awaitables may store `io_env const*` without concern
377  
        for dangling references.
378  
        for dangling references.
378  

379  

379  
        @tparam Task The IoRunnable type.
380  
        @tparam Task The IoRunnable type.
380  

381  

381  
        @param t The task to execute. Ownership is transferred to the
382  
        @param t The task to execute. Ownership is transferred to the
382  
                 run_async_trampoline which will destroy it after completion.
383  
                 run_async_trampoline which will destroy it after completion.
383  
    */
384  
    */
384  
    template<IoRunnable Task>
385  
    template<IoRunnable Task>
385  
    void operator()(Task t) &&
386  
    void operator()(Task t) &&
386  
    {
387  
    {
387  
        auto task_h = t.handle();
388  
        auto task_h = t.handle();
388  
        auto& task_promise = task_h.promise();
389  
        auto& task_promise = task_h.promise();
389  
        t.release();
390  
        t.release();
390  

391  

391  
        auto& p = tr_.h_.promise();
392  
        auto& p = tr_.h_.promise();
392  

393  

393  
        // Inject Task-specific invoke function
394  
        // Inject Task-specific invoke function
394  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
395  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
395  
        p.task_promise_ = &task_promise;
396  
        p.task_promise_ = &task_promise;
396  
        p.task_h_ = task_h;
397  
        p.task_h_ = task_h;
397  

398  

398  
        // Setup task's continuation to return to run_async_trampoline
399  
        // Setup task's continuation to return to run_async_trampoline
399  
        task_promise.set_continuation(tr_.h_);
400  
        task_promise.set_continuation(tr_.h_);
400  
        p.env_ = {p.wg_.executor(), st_, p.get_resource()};
401  
        p.env_ = {p.wg_.executor(), st_, p.get_resource()};
401  
        task_promise.set_environment(&p.env_);
402  
        task_promise.set_environment(&p.env_);
402  

403  

403  
        // Start task through executor
404  
        // Start task through executor
404  
        p.wg_.executor().dispatch(task_h).resume();
405  
        p.wg_.executor().dispatch(task_h).resume();
405  
    }
406  
    }
406  
};
407  
};
407  

408  

408  
//----------------------------------------------------------
409  
//----------------------------------------------------------
409  
//
410  
//
410  
// run_async Overloads
411  
// run_async Overloads
411  
//
412  
//
412  
//----------------------------------------------------------
413  
//----------------------------------------------------------
413  

414  

414  
// Executor only (uses default recycling allocator)
415  
// Executor only (uses default recycling allocator)
415  

416  

416  
/** Asynchronously launch a lazy task on the given executor.
417  
/** Asynchronously launch a lazy task on the given executor.
417  

418  

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

422  

422  
    Uses the default recycling frame allocator for coroutine frames.
423  
    Uses the default recycling frame allocator for coroutine frames.
423  
    With no handlers, the result is discarded and exceptions are rethrown.
424  
    With no handlers, the result is discarded and exceptions are rethrown.
424  

425  

425  
    @par Thread Safety
426  
    @par Thread Safety
426  
    The wrapper and handlers may be called from any thread where the
427  
    The wrapper and handlers may be called from any thread where the
427  
    executor schedules work.
428  
    executor schedules work.
428  

429  

429  
    @par Example
430  
    @par Example
430  
    @code
431  
    @code
431  
    run_async(ioc.get_executor())(my_task());
432  
    run_async(ioc.get_executor())(my_task());
432  
    @endcode
433  
    @endcode
433  

434  

434  
    @param ex The executor to execute the task on.
435  
    @param ex The executor to execute the task on.
435  

436  

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

438  

438  
    @see task
439  
    @see task
439  
    @see executor
440  
    @see executor
440  
*/
441  
*/
441  
template<Executor Ex>
442  
template<Executor Ex>
442  
[[nodiscard]] auto
443  
[[nodiscard]] auto
443  
run_async(Ex ex)
444  
run_async(Ex ex)
444  
{
445  
{
445  
    auto* mr = ex.context().get_frame_allocator();
446  
    auto* mr = ex.context().get_frame_allocator();
446  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
447  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
447  
        std::move(ex),
448  
        std::move(ex),
448  
        std::stop_token{},
449  
        std::stop_token{},
449  
        detail::default_handler{},
450  
        detail::default_handler{},
450  
        mr);
451  
        mr);
451  
}
452  
}
452  

453  

453  
/** Asynchronously launch a lazy task with a result handler.
454  
/** Asynchronously launch a lazy task with a result handler.
454  

455  

455  
    The handler `h1` is called with the task's result on success. If `h1`
456  
    The handler `h1` is called with the task's result on success. If `h1`
456  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
457  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
457  
    Otherwise, exceptions are rethrown.
458  
    Otherwise, exceptions are rethrown.
458  

459  

459  
    @par Thread Safety
460  
    @par Thread Safety
460  
    The handler may be called from any thread where the executor
461  
    The handler may be called from any thread where the executor
461  
    schedules work.
462  
    schedules work.
462  

463  

463  
    @par Example
464  
    @par Example
464  
    @code
465  
    @code
465  
    // Handler for result only (exceptions rethrown)
466  
    // Handler for result only (exceptions rethrown)
466  
    run_async(ex, [](int result) {
467  
    run_async(ex, [](int result) {
467  
        std::cout << "Got: " << result << "\n";
468  
        std::cout << "Got: " << result << "\n";
468  
    })(compute_value());
469  
    })(compute_value());
469  

470  

470  
    // Overloaded handler for both result and exception
471  
    // Overloaded handler for both result and exception
471  
    run_async(ex, overloaded{
472  
    run_async(ex, overloaded{
472  
        [](int result) { std::cout << "Got: " << result << "\n"; },
473  
        [](int result) { std::cout << "Got: " << result << "\n"; },
473  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
474  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
474  
    })(compute_value());
475  
    })(compute_value());
475  
    @endcode
476  
    @endcode
476  

477  

477  
    @param ex The executor to execute the task on.
478  
    @param ex The executor to execute the task on.
478  
    @param h1 The handler to invoke with the result (and optionally exception).
479  
    @param h1 The handler to invoke with the result (and optionally exception).
479  

480  

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

482  

482  
    @see task
483  
    @see task
483  
    @see executor
484  
    @see executor
484  
*/
485  
*/
485  
template<Executor Ex, class H1>
486  
template<Executor Ex, class H1>
486  
[[nodiscard]] auto
487  
[[nodiscard]] auto
487  
run_async(Ex ex, H1 h1)
488  
run_async(Ex ex, H1 h1)
488  
{
489  
{
489  
    auto* mr = ex.context().get_frame_allocator();
490  
    auto* mr = ex.context().get_frame_allocator();
490  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
491  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
491  
        std::move(ex),
492  
        std::move(ex),
492  
        std::stop_token{},
493  
        std::stop_token{},
493  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
494  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
494  
        mr);
495  
        mr);
495  
}
496  
}
496  

497  

497  
/** Asynchronously launch a lazy task with separate result and error handlers.
498  
/** Asynchronously launch a lazy task with separate result and error handlers.
498  

499  

499  
    The handler `h1` is called with the task's result on success.
500  
    The handler `h1` is called with the task's result on success.
500  
    The handler `h2` is called with the exception_ptr on failure.
501  
    The handler `h2` is called with the exception_ptr on failure.
501  

502  

502  
    @par Thread Safety
503  
    @par Thread Safety
503  
    The handlers may be called from any thread where the executor
504  
    The handlers may be called from any thread where the executor
504  
    schedules work.
505  
    schedules work.
505  

506  

506  
    @par Example
507  
    @par Example
507  
    @code
508  
    @code
508  
    run_async(ex,
509  
    run_async(ex,
509  
        [](int result) { std::cout << "Got: " << result << "\n"; },
510  
        [](int result) { std::cout << "Got: " << result << "\n"; },
510  
        [](std::exception_ptr ep) {
511  
        [](std::exception_ptr ep) {
511  
            try { std::rethrow_exception(ep); }
512  
            try { std::rethrow_exception(ep); }
512  
            catch (std::exception const& e) {
513  
            catch (std::exception const& e) {
513  
                std::cout << "Error: " << e.what() << "\n";
514  
                std::cout << "Error: " << e.what() << "\n";
514  
            }
515  
            }
515  
        }
516  
        }
516  
    )(compute_value());
517  
    )(compute_value());
517  
    @endcode
518  
    @endcode
518  

519  

519  
    @param ex The executor to execute the task on.
520  
    @param ex The executor to execute the task on.
520  
    @param h1 The handler to invoke with the result on success.
521  
    @param h1 The handler to invoke with the result on success.
521  
    @param h2 The handler to invoke with the exception on failure.
522  
    @param h2 The handler to invoke with the exception on failure.
522  

523  

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

525  

525  
    @see task
526  
    @see task
526  
    @see executor
527  
    @see executor
527  
*/
528  
*/
528  
template<Executor Ex, class H1, class H2>
529  
template<Executor Ex, class H1, class H2>
529  
[[nodiscard]] auto
530  
[[nodiscard]] auto
530  
run_async(Ex ex, H1 h1, H2 h2)
531  
run_async(Ex ex, H1 h1, H2 h2)
531  
{
532  
{
532  
    auto* mr = ex.context().get_frame_allocator();
533  
    auto* mr = ex.context().get_frame_allocator();
533  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
534  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
534  
        std::move(ex),
535  
        std::move(ex),
535  
        std::stop_token{},
536  
        std::stop_token{},
536  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
537  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
537  
        mr);
538  
        mr);
538  
}
539  
}
539  

540  

540  
// Ex + stop_token
541  
// Ex + stop_token
541  

542  

542  
/** Asynchronously launch a lazy task with stop token support.
543  
/** Asynchronously launch a lazy task with stop token support.
543  

544  

544  
    The stop token is propagated to the task, enabling cooperative
545  
    The stop token is propagated to the task, enabling cooperative
545  
    cancellation. With no handlers, the result is discarded and
546  
    cancellation. With no handlers, the result is discarded and
546  
    exceptions are rethrown.
547  
    exceptions are rethrown.
547  

548  

548  
    @par Thread Safety
549  
    @par Thread Safety
549  
    The wrapper may be called from any thread where the executor
550  
    The wrapper may be called from any thread where the executor
550  
    schedules work.
551  
    schedules work.
551  

552  

552  
    @par Example
553  
    @par Example
553  
    @code
554  
    @code
554  
    std::stop_source source;
555  
    std::stop_source source;
555  
    run_async(ex, source.get_token())(cancellable_task());
556  
    run_async(ex, source.get_token())(cancellable_task());
556  
    // Later: source.request_stop();
557  
    // Later: source.request_stop();
557  
    @endcode
558  
    @endcode
558  

559  

559  
    @param ex The executor to execute the task on.
560  
    @param ex The executor to execute the task on.
560  
    @param st The stop token for cooperative cancellation.
561  
    @param st The stop token for cooperative cancellation.
561  

562  

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

564  

564  
    @see task
565  
    @see task
565  
    @see executor
566  
    @see executor
566  
*/
567  
*/
567  
template<Executor Ex>
568  
template<Executor Ex>
568  
[[nodiscard]] auto
569  
[[nodiscard]] auto
569  
run_async(Ex ex, std::stop_token st)
570  
run_async(Ex ex, std::stop_token st)
570  
{
571  
{
571  
    auto* mr = ex.context().get_frame_allocator();
572  
    auto* mr = ex.context().get_frame_allocator();
572  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
573  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
573  
        std::move(ex),
574  
        std::move(ex),
574  
        std::move(st),
575  
        std::move(st),
575  
        detail::default_handler{},
576  
        detail::default_handler{},
576  
        mr);
577  
        mr);
577  
}
578  
}
578  

579  

579  
/** Asynchronously launch a lazy task with stop token and result handler.
580  
/** Asynchronously launch a lazy task with stop token and result handler.
580  

581  

581  
    The stop token is propagated to the task for cooperative cancellation.
582  
    The stop token is propagated to the task for cooperative cancellation.
582  
    The handler `h1` is called with the result on success, and optionally
583  
    The handler `h1` is called with the result on success, and optionally
583  
    with exception_ptr if it accepts that type.
584  
    with exception_ptr if it accepts that type.
584  

585  

585  
    @param ex The executor to execute the task on.
586  
    @param ex The executor to execute the task on.
586  
    @param st The stop token for cooperative cancellation.
587  
    @param st The stop token for cooperative cancellation.
587  
    @param h1 The handler to invoke with the result (and optionally exception).
588  
    @param h1 The handler to invoke with the result (and optionally exception).
588  

589  

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

591  

591  
    @see task
592  
    @see task
592  
    @see executor
593  
    @see executor
593  
*/
594  
*/
594  
template<Executor Ex, class H1>
595  
template<Executor Ex, class H1>
595  
[[nodiscard]] auto
596  
[[nodiscard]] auto
596  
run_async(Ex ex, std::stop_token st, H1 h1)
597  
run_async(Ex ex, std::stop_token st, H1 h1)
597  
{
598  
{
598  
    auto* mr = ex.context().get_frame_allocator();
599  
    auto* mr = ex.context().get_frame_allocator();
599  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
600  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
600  
        std::move(ex),
601  
        std::move(ex),
601  
        std::move(st),
602  
        std::move(st),
602  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
603  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
603  
        mr);
604  
        mr);
604  
}
605  
}
605  

606  

606  
/** Asynchronously launch a lazy task with stop token and separate handlers.
607  
/** Asynchronously launch a lazy task with stop token and separate handlers.
607  

608  

608  
    The stop token is propagated to the task for cooperative cancellation.
609  
    The stop token is propagated to the task for cooperative cancellation.
609  
    The handler `h1` is called on success, `h2` on failure.
610  
    The handler `h1` is called on success, `h2` on failure.
610  

611  

611  
    @param ex The executor to execute the task on.
612  
    @param ex The executor to execute the task on.
612  
    @param st The stop token for cooperative cancellation.
613  
    @param st The stop token for cooperative cancellation.
613  
    @param h1 The handler to invoke with the result on success.
614  
    @param h1 The handler to invoke with the result on success.
614  
    @param h2 The handler to invoke with the exception on failure.
615  
    @param h2 The handler to invoke with the exception on failure.
615  

616  

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

618  

618  
    @see task
619  
    @see task
619  
    @see executor
620  
    @see executor
620  
*/
621  
*/
621  
template<Executor Ex, class H1, class H2>
622  
template<Executor Ex, class H1, class H2>
622  
[[nodiscard]] auto
623  
[[nodiscard]] auto
623  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
624  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
624  
{
625  
{
625  
    auto* mr = ex.context().get_frame_allocator();
626  
    auto* mr = ex.context().get_frame_allocator();
626  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
627  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
627  
        std::move(ex),
628  
        std::move(ex),
628  
        std::move(st),
629  
        std::move(st),
629  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
630  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
630  
        mr);
631  
        mr);
631  
}
632  
}
632  

633  

633  
// Ex + memory_resource*
634  
// Ex + memory_resource*
634  

635  

635  
/** Asynchronously launch a lazy task with custom memory resource.
636  
/** Asynchronously launch a lazy task with custom memory resource.
636  

637  

637  
    The memory resource is used for coroutine frame allocation. The caller
638  
    The memory resource is used for coroutine frame allocation. The caller
638  
    is responsible for ensuring the memory resource outlives all tasks.
639  
    is responsible for ensuring the memory resource outlives all tasks.
639  

640  

640  
    @param ex The executor to execute the task on.
641  
    @param ex The executor to execute the task on.
641  
    @param mr The memory resource for frame allocation.
642  
    @param mr The memory resource for frame allocation.
642  

643  

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

645  

645  
    @see task
646  
    @see task
646  
    @see executor
647  
    @see executor
647  
*/
648  
*/
648  
template<Executor Ex>
649  
template<Executor Ex>
649  
[[nodiscard]] auto
650  
[[nodiscard]] auto
650  
run_async(Ex ex, std::pmr::memory_resource* mr)
651  
run_async(Ex ex, std::pmr::memory_resource* mr)
651  
{
652  
{
652  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
653  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
653  
        std::move(ex),
654  
        std::move(ex),
654  
        std::stop_token{},
655  
        std::stop_token{},
655  
        detail::default_handler{},
656  
        detail::default_handler{},
656  
        mr);
657  
        mr);
657  
}
658  
}
658  

659  

659  
/** Asynchronously launch a lazy task with memory resource and handler.
660  
/** Asynchronously launch a lazy task with memory resource and handler.
660  

661  

661  
    @param ex The executor to execute the task on.
662  
    @param ex The executor to execute the task on.
662  
    @param mr The memory resource for frame allocation.
663  
    @param mr The memory resource for frame allocation.
663  
    @param h1 The handler to invoke with the result (and optionally exception).
664  
    @param h1 The handler to invoke with the result (and optionally exception).
664  

665  

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

667  

667  
    @see task
668  
    @see task
668  
    @see executor
669  
    @see executor
669  
*/
670  
*/
670  
template<Executor Ex, class H1>
671  
template<Executor Ex, class H1>
671  
[[nodiscard]] auto
672  
[[nodiscard]] auto
672  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
673  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
673  
{
674  
{
674  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
675  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
675  
        std::move(ex),
676  
        std::move(ex),
676  
        std::stop_token{},
677  
        std::stop_token{},
677  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
678  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
678  
        mr);
679  
        mr);
679  
}
680  
}
680  

681  

681  
/** Asynchronously launch a lazy task with memory resource and handlers.
682  
/** Asynchronously launch a lazy task with memory resource and handlers.
682  

683  

683  
    @param ex The executor to execute the task on.
684  
    @param ex The executor to execute the task on.
684  
    @param mr The memory resource for frame allocation.
685  
    @param mr The memory resource for frame allocation.
685  
    @param h1 The handler to invoke with the result on success.
686  
    @param h1 The handler to invoke with the result on success.
686  
    @param h2 The handler to invoke with the exception on failure.
687  
    @param h2 The handler to invoke with the exception on failure.
687  

688  

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

690  

690  
    @see task
691  
    @see task
691  
    @see executor
692  
    @see executor
692  
*/
693  
*/
693  
template<Executor Ex, class H1, class H2>
694  
template<Executor Ex, class H1, class H2>
694  
[[nodiscard]] auto
695  
[[nodiscard]] auto
695  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
696  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
696  
{
697  
{
697  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
698  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
698  
        std::move(ex),
699  
        std::move(ex),
699  
        std::stop_token{},
700  
        std::stop_token{},
700  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
701  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
701  
        mr);
702  
        mr);
702  
}
703  
}
703  

704  

704  
// Ex + stop_token + memory_resource*
705  
// Ex + stop_token + memory_resource*
705  

706  

706  
/** Asynchronously launch a lazy task with stop token and memory resource.
707  
/** Asynchronously launch a lazy task with stop token and memory resource.
707  

708  

708  
    @param ex The executor to execute the task on.
709  
    @param ex The executor to execute the task on.
709  
    @param st The stop token for cooperative cancellation.
710  
    @param st The stop token for cooperative cancellation.
710  
    @param mr The memory resource for frame allocation.
711  
    @param mr The memory resource for frame allocation.
711  

712  

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

714  

714  
    @see task
715  
    @see task
715  
    @see executor
716  
    @see executor
716  
*/
717  
*/
717  
template<Executor Ex>
718  
template<Executor Ex>
718  
[[nodiscard]] auto
719  
[[nodiscard]] auto
719  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
720  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
720  
{
721  
{
721  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
722  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
722  
        std::move(ex),
723  
        std::move(ex),
723  
        std::move(st),
724  
        std::move(st),
724  
        detail::default_handler{},
725  
        detail::default_handler{},
725  
        mr);
726  
        mr);
726  
}
727  
}
727  

728  

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

730  

730  
    @param ex The executor to execute the task on.
731  
    @param ex The executor to execute the task on.
731  
    @param st The stop token for cooperative cancellation.
732  
    @param st The stop token for cooperative cancellation.
732  
    @param mr The memory resource for frame allocation.
733  
    @param mr The memory resource for frame allocation.
733  
    @param h1 The handler to invoke with the result (and optionally exception).
734  
    @param h1 The handler to invoke with the result (and optionally exception).
734  

735  

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

737  

737  
    @see task
738  
    @see task
738  
    @see executor
739  
    @see executor
739  
*/
740  
*/
740  
template<Executor Ex, class H1>
741  
template<Executor Ex, class H1>
741  
[[nodiscard]] auto
742  
[[nodiscard]] auto
742  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
743  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
743  
{
744  
{
744  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
745  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
745  
        std::move(ex),
746  
        std::move(ex),
746  
        std::move(st),
747  
        std::move(st),
747  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
748  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
748  
        mr);
749  
        mr);
749  
}
750  
}
750  

751  

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

753  

753  
    @param ex The executor to execute the task on.
754  
    @param ex The executor to execute the task on.
754  
    @param st The stop token for cooperative cancellation.
755  
    @param st The stop token for cooperative cancellation.
755  
    @param mr The memory resource for frame allocation.
756  
    @param mr The memory resource for frame allocation.
756  
    @param h1 The handler to invoke with the result on success.
757  
    @param h1 The handler to invoke with the result on success.
757  
    @param h2 The handler to invoke with the exception on failure.
758  
    @param h2 The handler to invoke with the exception on failure.
758  

759  

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

761  

761  
    @see task
762  
    @see task
762  
    @see executor
763  
    @see executor
763  
*/
764  
*/
764  
template<Executor Ex, class H1, class H2>
765  
template<Executor Ex, class H1, class H2>
765  
[[nodiscard]] auto
766  
[[nodiscard]] auto
766  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
767  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
767  
{
768  
{
768  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
769  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
769  
        std::move(ex),
770  
        std::move(ex),
770  
        std::move(st),
771  
        std::move(st),
771  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
772  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
772  
        mr);
773  
        mr);
773  
}
774  
}
774  

775  

775  
// Ex + standard Allocator (value type)
776  
// Ex + standard Allocator (value type)
776  

777  

777  
/** Asynchronously launch a lazy task with custom allocator.
778  
/** Asynchronously launch a lazy task with custom allocator.
778  

779  

779  
    The allocator is wrapped in a frame_memory_resource and stored in the
780  
    The allocator is wrapped in a frame_memory_resource and stored in the
780  
    run_async_trampoline, ensuring it outlives all coroutine frames.
781  
    run_async_trampoline, ensuring it outlives all coroutine frames.
781  

782  

782  
    @param ex The executor to execute the task on.
783  
    @param ex The executor to execute the task on.
783  
    @param alloc The allocator for frame allocation (copied and stored).
784  
    @param alloc The allocator for frame allocation (copied and stored).
784  

785  

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

787  

787  
    @see task
788  
    @see task
788  
    @see executor
789  
    @see executor
789  
*/
790  
*/
790  
template<Executor Ex, detail::Allocator Alloc>
791  
template<Executor Ex, detail::Allocator Alloc>
791  
[[nodiscard]] auto
792  
[[nodiscard]] auto
792  
run_async(Ex ex, Alloc alloc)
793  
run_async(Ex ex, Alloc alloc)
793  
{
794  
{
794  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
795  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
795  
        std::move(ex),
796  
        std::move(ex),
796  
        std::stop_token{},
797  
        std::stop_token{},
797  
        detail::default_handler{},
798  
        detail::default_handler{},
798  
        std::move(alloc));
799  
        std::move(alloc));
799  
}
800  
}
800  

801  

801  
/** Asynchronously launch a lazy task with allocator and handler.
802  
/** Asynchronously launch a lazy task with allocator and handler.
802  

803  

803  
    @param ex The executor to execute the task on.
804  
    @param ex The executor to execute the task on.
804  
    @param alloc The allocator for frame allocation (copied and stored).
805  
    @param alloc The allocator for frame allocation (copied and stored).
805  
    @param h1 The handler to invoke with the result (and optionally exception).
806  
    @param h1 The handler to invoke with the result (and optionally exception).
806  

807  

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

809  

809  
    @see task
810  
    @see task
810  
    @see executor
811  
    @see executor
811  
*/
812  
*/
812  
template<Executor Ex, detail::Allocator Alloc, class H1>
813  
template<Executor Ex, detail::Allocator Alloc, class H1>
813  
[[nodiscard]] auto
814  
[[nodiscard]] auto
814  
run_async(Ex ex, Alloc alloc, H1 h1)
815  
run_async(Ex ex, Alloc alloc, H1 h1)
815  
{
816  
{
816  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
817  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
817  
        std::move(ex),
818  
        std::move(ex),
818  
        std::stop_token{},
819  
        std::stop_token{},
819  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
820  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
820  
        std::move(alloc));
821  
        std::move(alloc));
821  
}
822  
}
822  

823  

823  
/** Asynchronously launch a lazy task with allocator and handlers.
824  
/** Asynchronously launch a lazy task with allocator and handlers.
824  

825  

825  
    @param ex The executor to execute the task on.
826  
    @param ex The executor to execute the task on.
826  
    @param alloc The allocator for frame allocation (copied and stored).
827  
    @param alloc The allocator for frame allocation (copied and stored).
827  
    @param h1 The handler to invoke with the result on success.
828  
    @param h1 The handler to invoke with the result on success.
828  
    @param h2 The handler to invoke with the exception on failure.
829  
    @param h2 The handler to invoke with the exception on failure.
829  

830  

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

832  

832  
    @see task
833  
    @see task
833  
    @see executor
834  
    @see executor
834  
*/
835  
*/
835  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
836  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
836  
[[nodiscard]] auto
837  
[[nodiscard]] auto
837  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
838  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
838  
{
839  
{
839  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
840  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
840  
        std::move(ex),
841  
        std::move(ex),
841  
        std::stop_token{},
842  
        std::stop_token{},
842  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
843  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
843  
        std::move(alloc));
844  
        std::move(alloc));
844  
}
845  
}
845  

846  

846  
// Ex + stop_token + standard Allocator
847  
// Ex + stop_token + standard Allocator
847  

848  

848  
/** Asynchronously launch a lazy task with stop token and allocator.
849  
/** Asynchronously launch a lazy task with stop token and allocator.
849  

850  

850  
    @param ex The executor to execute the task on.
851  
    @param ex The executor to execute the task on.
851  
    @param st The stop token for cooperative cancellation.
852  
    @param st The stop token for cooperative cancellation.
852  
    @param alloc The allocator for frame allocation (copied and stored).
853  
    @param alloc The allocator for frame allocation (copied and stored).
853  

854  

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

856  

856  
    @see task
857  
    @see task
857  
    @see executor
858  
    @see executor
858  
*/
859  
*/
859  
template<Executor Ex, detail::Allocator Alloc>
860  
template<Executor Ex, detail::Allocator Alloc>
860  
[[nodiscard]] auto
861  
[[nodiscard]] auto
861  
run_async(Ex ex, std::stop_token st, Alloc alloc)
862  
run_async(Ex ex, std::stop_token st, Alloc alloc)
862  
{
863  
{
863  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
864  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
864  
        std::move(ex),
865  
        std::move(ex),
865  
        std::move(st),
866  
        std::move(st),
866  
        detail::default_handler{},
867  
        detail::default_handler{},
867  
        std::move(alloc));
868  
        std::move(alloc));
868  
}
869  
}
869  

870  

870  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
871  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
871  

872  

872  
    @param ex The executor to execute the task on.
873  
    @param ex The executor to execute the task on.
873  
    @param st The stop token for cooperative cancellation.
874  
    @param st The stop token for cooperative cancellation.
874  
    @param alloc The allocator for frame allocation (copied and stored).
875  
    @param alloc The allocator for frame allocation (copied and stored).
875  
    @param h1 The handler to invoke with the result (and optionally exception).
876  
    @param h1 The handler to invoke with the result (and optionally exception).
876  

877  

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

879  

879  
    @see task
880  
    @see task
880  
    @see executor
881  
    @see executor
881  
*/
882  
*/
882  
template<Executor Ex, detail::Allocator Alloc, class H1>
883  
template<Executor Ex, detail::Allocator Alloc, class H1>
883  
[[nodiscard]] auto
884  
[[nodiscard]] auto
884  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
885  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
885  
{
886  
{
886  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
887  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
887  
        std::move(ex),
888  
        std::move(ex),
888  
        std::move(st),
889  
        std::move(st),
889  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
890  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
890  
        std::move(alloc));
891  
        std::move(alloc));
891  
}
892  
}
892  

893  

893  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
894  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
894  

895  

895  
    @param ex The executor to execute the task on.
896  
    @param ex The executor to execute the task on.
896  
    @param st The stop token for cooperative cancellation.
897  
    @param st The stop token for cooperative cancellation.
897  
    @param alloc The allocator for frame allocation (copied and stored).
898  
    @param alloc The allocator for frame allocation (copied and stored).
898  
    @param h1 The handler to invoke with the result on success.
899  
    @param h1 The handler to invoke with the result on success.
899  
    @param h2 The handler to invoke with the exception on failure.
900  
    @param h2 The handler to invoke with the exception on failure.
900  

901  

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

903  

903  
    @see task
904  
    @see task
904  
    @see executor
905  
    @see executor
905  
*/
906  
*/
906  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
907  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
907  
[[nodiscard]] auto
908  
[[nodiscard]] auto
908  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
909  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
909  
{
910  
{
910  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
911  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
911  
        std::move(ex),
912  
        std::move(ex),
912  
        std::move(st),
913  
        std::move(st),
913  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
914  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
914  
        std::move(alloc));
915  
        std::move(alloc));
915  
}
916  
}
916  

917  

917  
} // namespace capy
918  
} // namespace capy
918  
} // namespace boost
919  
} // namespace boost
919  

920  

920  
#endif
921  
#endif