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 -
#include <algorithm>
 
24  

23  

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

30  

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

34  

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

37  

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

49  

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

55  

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

57  

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

63  

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

69  

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

71  

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

75  

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

78  

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

87  

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

97  

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

104  

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

110  

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

115  

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

118  

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

122  

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

124  

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

127  

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

134  

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

139  

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

144  

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

150  

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

155  

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

160  

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

164  

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

169  

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

171  

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

185  

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

187  

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

194  

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

204  

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

212  

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

222  

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

230  

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

235  

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

241  

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

246  

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

251  

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

255  

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

260  

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

262  

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

276  

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

289  

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

291  

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

297  

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

299  

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

303  

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

306  

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

310  

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

314  

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

319  

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

324  

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

333  

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

355  

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

362  

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

368  

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

370  

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

374  

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

379  

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

381  

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

391  

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

393  

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

398  

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

403  

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

408  

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

414  

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

416  

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

418  

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

422  

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

425  

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

429  

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

434  

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

436  

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

438  

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

453  

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

455  

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

459  

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

463  

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

470  

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

477  

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

480  

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

482  

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

497  

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

499  

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

502  

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

506  

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

519  

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

523  

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

525  

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

540  

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

542  

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

544  

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

548  

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

552  

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

559  

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

562  

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

564  

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

579  

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

581  

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

585  

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

589  

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

591  

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

606  

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

608  

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

611  

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

616  

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

618  

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

633  

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

635  

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

637  

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

640  

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

643  

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

645  

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

659  

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

661  

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

665  

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

667  

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

681  

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

683  

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

688  

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

690  

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

704  

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

706  

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

708  

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

712  

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

714  

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

728  

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

730  

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

735  

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

737  

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

751  

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

753  

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

759  

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

761  

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

775  

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

777  

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

779  

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

782  

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

785  

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

787  

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

801  

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

803  

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

807  

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

809  

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

823  

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

825  

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

830  

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

832  

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

846  

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

848  

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

850  

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

854  

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

856  

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

870  

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

872  

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

877  

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

879  

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

893  

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

895  

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

901  

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

903  

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

917  

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

920  

922  
#endif
921  
#endif