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 <algorithm>
24  
#include <coroutine>
25  
#include <coroutine>
25  
#include <cstring>
26  
#include <cstring>
26  
#include <memory_resource>
27  
#include <memory_resource>
27  
#include <new>
28  
#include <new>
28  
#include <stop_token>
29  
#include <stop_token>
29  
#include <type_traits>
30  
#include <type_traits>
30  

31  

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

35  

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

38  

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

50  

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

56  

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

58  

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

64  

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

70  

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

72  

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

76  

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

79  

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

88  

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

98  

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

105  

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

111  

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

116  

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

119  

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

123  

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

125  

125  
            return raw;
126  
            return raw;
126  
        }
127  
        }
127  

128  

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

135  

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

140  

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

145  

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

151  

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

156  

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

161  

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

165  

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

170  

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

172  

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

186  

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

188  

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

195  

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

205  

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

213  

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

223  

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

231  

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

236  

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

242  

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

247  

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

252  

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

256  

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

261  

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

263  

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

277  

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

290  

290  
} // namespace detail
291  
} // namespace detail
291  

292  

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

298  

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

300  

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

304  

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

307  

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

311  

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

315  

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

320  

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

325  

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

334  

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

356  

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

363  

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

369  

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

371  

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

375  

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

380  

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

382  

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

392  

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

394  

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

399  

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

404  

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

409  

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

415  

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

417  

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

419  

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

423  

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

426  

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

430  

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

435  

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

437  

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

439  

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

454  

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

456  

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

460  

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

464  

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

471  

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

478  

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

481  

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

483  

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

498  

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

500  

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

503  

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

507  

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

520  

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

524  

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

526  

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

541  

541  
// Ex + stop_token
542  
// Ex + stop_token
542  

543  

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

545  

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

549  

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

553  

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

560  

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

563  

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

565  

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

580  

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

582  

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

586  

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

590  

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

592  

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

607  

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

609  

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

612  

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

617  

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

619  

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

634  

634  
// Ex + memory_resource*
635  
// Ex + memory_resource*
635  

636  

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

638  

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

641  

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

644  

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

646  

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

660  

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

662  

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

666  

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

668  

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

682  

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

684  

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

689  

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

691  

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

705  

705  
// Ex + stop_token + memory_resource*
706  
// Ex + stop_token + memory_resource*
706  

707  

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

709  

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

713  

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

715  

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

729  

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

731  

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

736  

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

738  

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

752  

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

754  

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

760  

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

762  

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

776  

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

778  

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

780  

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

783  

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

786  

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

788  

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

802  

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

804  

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

808  

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

810  

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

824  

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

826  

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

831  

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

833  

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

847  

847  
// Ex + stop_token + standard Allocator
848  
// Ex + stop_token + standard Allocator
848  

849  

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

851  

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

855  

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

857  

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

871  

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

873  

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

878  

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

880  

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

894  

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

896  

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

902  

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

904  

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

918  

918  
} // namespace capy
919  
} // namespace capy
919  
} // namespace boost
920  
} // namespace boost
920  

921  

921  
#endif
922  
#endif