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

30  

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

34  

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

37  

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

49  

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

55  

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

57  

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

63  

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

69  

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

71  

72  
    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
73  
    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,
74  
    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.
75  

75  

76  
    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
77  
    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.
78  

78  

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

87  

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

97  

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

104  

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

110  

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

115  

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

118  

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

122  

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

124  

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

127  

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

134  

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

139  

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

144  

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

150  

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

155  

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

160  

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

164  

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

169  

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

171  

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

185  

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

187  

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

194  

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

204  

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

212  

213  
        static void* operator new(
213  
        static void* operator new(
214  
            std::size_t size, Ex const&, Handlers const&,
214  
            std::size_t size, Ex const&, Handlers const&,
215  
            std::pmr::memory_resource* mr)
215  
            std::pmr::memory_resource* mr)
216  
        {
216  
        {
217  
            auto total = size + sizeof(mr);
217  
            auto total = size + sizeof(mr);
218  
            void* raw = mr->allocate(total, alignof(std::max_align_t));
218  
            void* raw = mr->allocate(total, alignof(std::max_align_t));
219  
            std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
219  
            std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(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  
            std::pmr::memory_resource* mr;
225  
            std::pmr::memory_resource* mr;
226  
            std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
226  
            std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
227  
            auto total = size + sizeof(mr);
227  
            auto total = size + sizeof(mr);
228  
            mr->deallocate(ptr, total, alignof(std::max_align_t));
228  
            mr->deallocate(ptr, total, alignof(std::max_align_t));
229  
        }
229  
        }
230  

230  

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

235  

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

241  

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

246  

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

251  

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

255  

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

260  

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

262  

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

276  

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

289  

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

291  

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

297  

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

299  

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

303  

304  
    The rvalue ref-qualifier on `operator()` ensures the wrapper can only
304  
    The rvalue ref-qualifier on `operator()` ensures the wrapper can only
305  
    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.
306  

306  

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

310  

311  
    @par Thread Safety
311  
    @par Thread Safety
312  
    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
313  
    may be invoked from any thread where the executor schedules work.
313  
    may be invoked from any thread where the executor schedules work.
314  

314  

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

319  

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

324  

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

333  

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

355  

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

362  

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

368  

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

370  

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

374  

375  
        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
376  
        coroutine and is guaranteed to outlive the task and all awaitables
376  
        coroutine and is guaranteed to outlive the task and all awaitables
377  
        in its chain. Awaitables may store `io_env const*` without concern
377  
        in its chain. Awaitables may store `io_env const*` without concern
378  
        for dangling references.
378  
        for dangling references.
379  

379  

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

381  

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

391  

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

393  

394  
        // Inject Task-specific invoke function
394  
        // Inject Task-specific invoke function
395  
        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>;
396  
        p.task_promise_ = &task_promise;
396  
        p.task_promise_ = &task_promise;
397  
        p.task_h_ = task_h;
397  
        p.task_h_ = task_h;
398  

398  

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

403  

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

408  

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

414  

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

416  

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

418  

419  
    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.
420  
    The returned wrapper must be immediately invoked with the task;
420  
    The returned wrapper must be immediately invoked with the task;
421  
    storing the wrapper and calling it later violates LIFO ordering.
421  
    storing the wrapper and calling it later violates LIFO ordering.
422  

422  

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

425  

426  
    @par Thread Safety
426  
    @par Thread Safety
427  
    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
428  
    executor schedules work.
428  
    executor schedules work.
429  

429  

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

434  

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

436  

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

438  

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

453  

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

455  

456  
    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`
457  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
457  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
458  
    Otherwise, exceptions are rethrown.
458  
    Otherwise, exceptions are rethrown.
459  

459  

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

463  

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

470  

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

477  

478  
    @param ex The executor to execute the task on.
478  
    @param ex The executor to execute the task on.
479  
    @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).
480  

480  

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

482  

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

497  

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

499  

500  
    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.
501  
    The handler `h2` is called with the exception_ptr on failure.
501  
    The handler `h2` is called with the exception_ptr on failure.
502  

502  

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

506  

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

519  

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

523  

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

525  

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

540  

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

542  

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

544  

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

548  

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

552  

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

559  

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

562  

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

564  

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

579  

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

581  

582  
    The stop token is propagated to the task for cooperative cancellation.
582  
    The stop token is propagated to the task for cooperative cancellation.
583  
    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
584  
    with exception_ptr if it accepts that type.
584  
    with exception_ptr if it accepts that type.
585  

585  

586  
    @param ex The executor to execute the task on.
586  
    @param ex The executor to execute the task on.
587  
    @param st The stop token for cooperative cancellation.
587  
    @param st The stop token for cooperative cancellation.
588  
    @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).
589  

589  

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

591  

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

606  

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

608  

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

611  

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

616  

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

618  

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

633  

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

635  

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

637  

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

640  

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

643  

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

645  

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

659  

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

661  

662  
    @param ex The executor to execute the task on.
662  
    @param ex The executor to execute the task on.
663  
    @param mr The memory resource for frame allocation.
663  
    @param mr The memory resource for frame allocation.
664  
    @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).
665  

665  

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

667  

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

681  

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

683  

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

688  

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

690  

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

704  

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

706  

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

708  

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

712  

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

714  

715  
    @see task
715  
    @see task
716  
    @see executor
716  
    @see executor
717  
*/
717  
*/
718  
template<Executor Ex>
718  
template<Executor Ex>
719  
[[nodiscard]] auto
719  
[[nodiscard]] auto
720  
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)
721  
{
721  
{
722  
    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*>(
723  
        std::move(ex),
723  
        std::move(ex),
724  
        std::move(st),
724  
        std::move(st),
725  
        detail::default_handler{},
725  
        detail::default_handler{},
726  
        mr);
726  
        mr);
727  
}
727  
}
728  

728  

729  
/** 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.
730  

730  

731  
    @param ex The executor to execute the task on.
731  
    @param ex The executor to execute the task on.
732  
    @param st The stop token for cooperative cancellation.
732  
    @param st The stop token for cooperative cancellation.
733  
    @param mr The memory resource for frame allocation.
733  
    @param mr The memory resource for frame allocation.
734  
    @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).
735  

735  

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

737  

738  
    @see task
738  
    @see task
739  
    @see executor
739  
    @see executor
740  
*/
740  
*/
741  
template<Executor Ex, class H1>
741  
template<Executor Ex, class H1>
742  
[[nodiscard]] auto
742  
[[nodiscard]] auto
743  
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)
744  
{
744  
{
745  
    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*>(
746  
        std::move(ex),
746  
        std::move(ex),
747  
        std::move(st),
747  
        std::move(st),
748  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
748  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
749  
        mr);
749  
        mr);
750  
}
750  
}
751  

751  

752  
/** 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.
753  

753  

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

759  

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

761  

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

775  

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

777  

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

779  

780  
    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
781  
    run_async_trampoline, ensuring it outlives all coroutine frames.
781  
    run_async_trampoline, ensuring it outlives all coroutine frames.
782  

782  

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

785  

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

787  

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

801  

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

803  

804  
    @param ex The executor to execute the task on.
804  
    @param ex The executor to execute the task on.
805  
    @param alloc The allocator for frame allocation (copied and stored).
805  
    @param alloc The allocator for frame allocation (copied and stored).
806  
    @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).
807  

807  

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

809  

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

823  

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

825  

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

830  

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

832  

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

846  

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

848  

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

850  

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

854  

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

856  

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

870  

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

872  

873  
    @param ex The executor to execute the task on.
873  
    @param ex The executor to execute the task on.
874  
    @param st The stop token for cooperative cancellation.
874  
    @param st The stop token for cooperative cancellation.
875  
    @param alloc The allocator for frame allocation (copied and stored).
875  
    @param alloc The allocator for frame allocation (copied and stored).
876  
    @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).
877  

877  

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

879  

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

893  

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

895  

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

901  

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

903  

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

917  

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

920  

921  
#endif
921  
#endif