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

29  

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

33  

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

36  

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

48  

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

54  

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

56  

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

62  

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

68  

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

70  

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

74  

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

77  

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

86  

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

96  

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

103  

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

109  

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

114  

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

117  

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

121  

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

123  

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

126  

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

133  

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

138  

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

143  

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

149  

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

154  

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

159  

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

163  

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

168  

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

170  

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

184  

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

186  

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

193  

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

203  

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

211  

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

222  

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

229  

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

234  

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

240  

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

245  

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

250  

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

254  

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

259  

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

261  

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

275  

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

288  

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

290  

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

296  

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

298  

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

302  

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

305  

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

309  

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

313  

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

318  

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

323  

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

332  

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

354  

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

361  

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

367  

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

369  

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

373  

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

378  

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

380  

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

390  

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

392  

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

397  

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

402  

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

407  

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

413  

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

415  

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

417  

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

421  

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

424  

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

428  

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

433  

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

435  

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

437  

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

452  

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

454  

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

458  

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

462  

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

469  

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

476  

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

479  

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

481  

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

496  

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

498  

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

501  

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

505  

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

518  

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

522  

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

524  

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

539  

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

541  

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

543  

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

547  

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

551  

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

558  

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

561  

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

563  

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

578  

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

580  

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

584  

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

588  

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

590  

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

605  

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

607  

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

610  

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

615  

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

617  

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

632  

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

634  

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

636  

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

639  

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

642  

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

644  

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

658  

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

660  

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

664  

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

666  

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

680  

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

682  

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

687  

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

689  

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

703  

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

705  

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

707  

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

711  

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

713  

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

727  

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

729  

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

734  

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

736  

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

750  

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

752  

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

758  

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

760  

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

774  

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

776  

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

778  

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

781  

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

784  

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

786  

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

800  

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

802  

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

806  

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

808  

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

822  

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

824  

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

829  

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

831  

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

845  

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

847  

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

849  

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

853  

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

855  

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

869  

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

871  

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

876  

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

878  

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

892  

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

894  

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

900  

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

902  

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

916  

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

919  

920  
#endif
920  
#endif