1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_RUN_ASYNC_HPP
10  
#ifndef BOOST_CAPY_RUN_ASYNC_HPP
11  
#define BOOST_CAPY_RUN_ASYNC_HPP
11  
#define BOOST_CAPY_RUN_ASYNC_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/run.hpp>
14  
#include <boost/capy/detail/run.hpp>
15  
#include <boost/capy/detail/run_callbacks.hpp>
15  
#include <boost/capy/detail/run_callbacks.hpp>
16  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/io_runnable.hpp>
17  
#include <boost/capy/concept/io_runnable.hpp>
18  
#include <boost/capy/ex/execution_context.hpp>
18  
#include <boost/capy/ex/execution_context.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
21  
#include <boost/capy/ex/recycling_memory_resource.hpp>
21  
#include <boost/capy/ex/recycling_memory_resource.hpp>
22  
#include <boost/capy/ex/work_guard.hpp>
22  
#include <boost/capy/ex/work_guard.hpp>
23  

23  

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

31  

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

35  

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

38  

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

50  

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

56  

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

58  

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

64  

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

70  

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

72  

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

76  

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

79  

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

88  

89  
    struct promise_type
89  
    struct promise_type
90  
    {
90  
    {
91  
        work_guard<Ex> wg_;
91  
        work_guard<Ex> wg_;
92  
        Handlers handlers_;
92  
        Handlers handlers_;
93  
        frame_memory_resource<Alloc> resource_;
93  
        frame_memory_resource<Alloc> resource_;
94  
        io_env env_;
94  
        io_env env_;
95  
        invoke_fn invoke_ = nullptr;
95  
        invoke_fn invoke_ = nullptr;
96  
        void* task_promise_ = nullptr;
96  
        void* task_promise_ = nullptr;
97  
        // task_h_: raw handle for frame_guard cleanup in make_trampoline.
97  
        // task_h_: raw handle for frame_guard cleanup in make_trampoline.
98  
        // task_cont_: continuation wrapping the same handle for executor dispatch.
98  
        // task_cont_: continuation wrapping the same handle for executor dispatch.
99  
        // Both must reference the same coroutine and be kept in sync.
99  
        // Both must reference the same coroutine and be kept in sync.
100  
        std::coroutine_handle<> task_h_;
100  
        std::coroutine_handle<> task_h_;
101  
        continuation task_cont_;
101  
        continuation task_cont_;
102  

102  

103  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
103  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
104  
            : wg_(std::move(ex))
104  
            : wg_(std::move(ex))
105  
            , handlers_(std::move(h))
105  
            , handlers_(std::move(h))
106  
            , resource_(std::move(a))
106  
            , resource_(std::move(a))
107  
        {
107  
        {
108  
        }
108  
        }
109  

109  

110  
        static void* operator new(
110  
        static void* operator new(
111  
            std::size_t size, Ex const&, Handlers const&, Alloc a)
111  
            std::size_t size, Ex const&, Handlers const&, Alloc a)
112  
        {
112  
        {
113  
            using byte_alloc = typename std::allocator_traits<Alloc>
113  
            using byte_alloc = typename std::allocator_traits<Alloc>
114  
                ::template rebind_alloc<std::byte>;
114  
                ::template rebind_alloc<std::byte>;
115  

115  

116  
            constexpr auto footer_align =
116  
            constexpr auto footer_align =
117  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
117  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
118  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
118  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
119  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
119  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
120  

120  

121  
            byte_alloc ba(std::move(a));
121  
            byte_alloc ba(std::move(a));
122  
            void* raw = ba.allocate(total);
122  
            void* raw = ba.allocate(total);
123  

123  

124  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
124  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
125  
                static_cast<char*>(raw) + padded);
125  
                static_cast<char*>(raw) + padded);
126  
            *fn_loc = &dealloc_impl<byte_alloc>;
126  
            *fn_loc = &dealloc_impl<byte_alloc>;
127  

127  

128  
            new (fn_loc + 1) byte_alloc(std::move(ba));
128  
            new (fn_loc + 1) byte_alloc(std::move(ba));
129  

129  

130  
            return raw;
130  
            return raw;
131  
        }
131  
        }
132  

132  

133  
        static void operator delete(void* ptr, std::size_t size)
133  
        static void operator delete(void* ptr, std::size_t size)
134  
        {
134  
        {
135  
            constexpr auto footer_align =
135  
            constexpr auto footer_align =
136  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
136  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
137  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
137  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
138  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
138  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
139  

139  

140  
            auto* fn = reinterpret_cast<dealloc_fn*>(
140  
            auto* fn = reinterpret_cast<dealloc_fn*>(
141  
                static_cast<char*>(ptr) + padded);
141  
                static_cast<char*>(ptr) + padded);
142  
            (*fn)(ptr, total);
142  
            (*fn)(ptr, total);
143  
        }
143  
        }
144  

144  

145  
        std::pmr::memory_resource* get_resource() noexcept
145  
        std::pmr::memory_resource* get_resource() noexcept
146  
        {
146  
        {
147  
            return &resource_;
147  
            return &resource_;
148  
        }
148  
        }
149  

149  

150  
        run_async_trampoline get_return_object() noexcept
150  
        run_async_trampoline get_return_object() noexcept
151  
        {
151  
        {
152  
            return run_async_trampoline{
152  
            return run_async_trampoline{
153  
                std::coroutine_handle<promise_type>::from_promise(*this)};
153  
                std::coroutine_handle<promise_type>::from_promise(*this)};
154  
        }
154  
        }
155  

155  

156  
        std::suspend_always initial_suspend() noexcept
156  
        std::suspend_always initial_suspend() noexcept
157  
        {
157  
        {
158  
            return {};
158  
            return {};
159  
        }
159  
        }
160  

160  

161  
        std::suspend_never final_suspend() noexcept
161  
        std::suspend_never final_suspend() noexcept
162  
        {
162  
        {
163  
            return {};
163  
            return {};
164  
        }
164  
        }
165  

165  

166  
        void return_void() noexcept
166  
        void return_void() noexcept
167  
        {
167  
        {
168  
        }
168  
        }
169  

169  

170  
        void unhandled_exception() noexcept
170  
        void unhandled_exception() noexcept
171  
        {
171  
        {
172  
        }
172  
        }
173  
    };
173  
    };
174  

174  

175  
    std::coroutine_handle<promise_type> h_;
175  
    std::coroutine_handle<promise_type> h_;
176  

176  

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

190  

191  
/** Specialization for memory_resource* - stores pointer directly.
191  
/** Specialization for memory_resource* - stores pointer directly.
192  

192  

193  
    This avoids double indirection when the user passes a memory_resource*.
193  
    This avoids double indirection when the user passes a memory_resource*.
194  
*/
194  
*/
195  
template<class Ex, class Handlers>
195  
template<class Ex, class Handlers>
196 -
struct run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
196 +
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE
 
197 +
    run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
197  
{
198  
{
198  
    using invoke_fn = void(*)(void*, Handlers&);
199  
    using invoke_fn = void(*)(void*, Handlers&);
199  

200  

200  
    struct promise_type
201  
    struct promise_type
201  
    {
202  
    {
202  
        work_guard<Ex> wg_;
203  
        work_guard<Ex> wg_;
203  
        Handlers handlers_;
204  
        Handlers handlers_;
204  
        std::pmr::memory_resource* mr_;
205  
        std::pmr::memory_resource* mr_;
205  
        io_env env_;
206  
        io_env env_;
206  
        invoke_fn invoke_ = nullptr;
207  
        invoke_fn invoke_ = nullptr;
207  
        void* task_promise_ = nullptr;
208  
        void* task_promise_ = nullptr;
208  
        // task_h_: raw handle for frame_guard cleanup in make_trampoline.
209  
        // task_h_: raw handle for frame_guard cleanup in make_trampoline.
209  
        // task_cont_: continuation wrapping the same handle for executor dispatch.
210  
        // task_cont_: continuation wrapping the same handle for executor dispatch.
210  
        // Both must reference the same coroutine and be kept in sync.
211  
        // Both must reference the same coroutine and be kept in sync.
211  
        std::coroutine_handle<> task_h_;
212  
        std::coroutine_handle<> task_h_;
212  
        continuation task_cont_;
213  
        continuation task_cont_;
213  

214  

214  
        promise_type(
215  
        promise_type(
215  
            Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
216  
            Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
216  
            : wg_(std::move(ex))
217  
            : wg_(std::move(ex))
217  
            , handlers_(std::move(h))
218  
            , handlers_(std::move(h))
218  
            , mr_(mr)
219  
            , mr_(mr)
219  
        {
220  
        {
220  
        }
221  
        }
221  

222  

222  
        static void* operator new(
223  
        static void* operator new(
223  
            std::size_t size, Ex const&, Handlers const&,
224  
            std::size_t size, Ex const&, Handlers const&,
224  
            std::pmr::memory_resource* mr)
225  
            std::pmr::memory_resource* mr)
225  
        {
226  
        {
226  
            auto total = size + sizeof(mr);
227  
            auto total = size + sizeof(mr);
227  
            void* raw = mr->allocate(total, alignof(std::max_align_t));
228  
            void* raw = mr->allocate(total, alignof(std::max_align_t));
228  
            std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
229  
            std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
229  
            return raw;
230  
            return raw;
230  
        }
231  
        }
231  

232  

232  
        static void operator delete(void* ptr, std::size_t size)
233  
        static void operator delete(void* ptr, std::size_t size)
233  
        {
234  
        {
234  
            std::pmr::memory_resource* mr;
235  
            std::pmr::memory_resource* mr;
235  
            std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
236  
            std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
236  
            auto total = size + sizeof(mr);
237  
            auto total = size + sizeof(mr);
237  
            mr->deallocate(ptr, total, alignof(std::max_align_t));
238  
            mr->deallocate(ptr, total, alignof(std::max_align_t));
238  
        }
239  
        }
239  

240  

240  
        std::pmr::memory_resource* get_resource() noexcept
241  
        std::pmr::memory_resource* get_resource() noexcept
241  
        {
242  
        {
242  
            return mr_;
243  
            return mr_;
243  
        }
244  
        }
244  

245  

245  
        run_async_trampoline get_return_object() noexcept
246  
        run_async_trampoline get_return_object() noexcept
246  
        {
247  
        {
247  
            return run_async_trampoline{
248  
            return run_async_trampoline{
248  
                std::coroutine_handle<promise_type>::from_promise(*this)};
249  
                std::coroutine_handle<promise_type>::from_promise(*this)};
249  
        }
250  
        }
250  

251  

251  
        std::suspend_always initial_suspend() noexcept
252  
        std::suspend_always initial_suspend() noexcept
252  
        {
253  
        {
253  
            return {};
254  
            return {};
254  
        }
255  
        }
255  

256  

256  
        std::suspend_never final_suspend() noexcept
257  
        std::suspend_never final_suspend() noexcept
257  
        {
258  
        {
258  
            return {};
259  
            return {};
259  
        }
260  
        }
260  

261  

261  
        void return_void() noexcept
262  
        void return_void() noexcept
262  
        {
263  
        {
263  
        }
264  
        }
264  

265  

265  
        void unhandled_exception() noexcept
266  
        void unhandled_exception() noexcept
266  
        {
267  
        {
267  
        }
268  
        }
268  
    };
269  
    };
269  

270  

270  
    std::coroutine_handle<promise_type> h_;
271  
    std::coroutine_handle<promise_type> h_;
271  

272  

272  
    template<IoRunnable Task>
273  
    template<IoRunnable Task>
273  
    static void invoke_impl(void* p, Handlers& h)
274  
    static void invoke_impl(void* p, Handlers& h)
274  
    {
275  
    {
275  
        using R = decltype(std::declval<Task&>().await_resume());
276  
        using R = decltype(std::declval<Task&>().await_resume());
276  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
277  
        auto& promise = *static_cast<typename Task::promise_type*>(p);
277  
        if(promise.exception())
278  
        if(promise.exception())
278  
            h(promise.exception());
279  
            h(promise.exception());
279  
        else if constexpr(std::is_void_v<R>)
280  
        else if constexpr(std::is_void_v<R>)
280  
            h();
281  
            h();
281  
        else
282  
        else
282  
            h(std::move(promise.result()));
283  
            h(std::move(promise.result()));
283  
    }
284  
    }
284  
};
285  
};
285  

286  

286  
/// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
287  
/// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
287  
template<class Ex, class Handlers, class Alloc>
288  
template<class Ex, class Handlers, class Alloc>
288  
run_async_trampoline<Ex, Handlers, Alloc>
289  
run_async_trampoline<Ex, Handlers, Alloc>
289  
make_trampoline(Ex, Handlers, Alloc)
290  
make_trampoline(Ex, Handlers, Alloc)
290  
{
291  
{
291  
    // promise_type ctor steals the parameters
292  
    // promise_type ctor steals the parameters
292  
    auto& p = co_await get_promise_awaiter<
293  
    auto& p = co_await get_promise_awaiter<
293  
        typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
294  
        typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
294  

295  

295  
    // Guard ensures the task frame is destroyed even when invoke_
296  
    // Guard ensures the task frame is destroyed even when invoke_
296  
    // throws (e.g. default_handler rethrows an unhandled exception).
297  
    // throws (e.g. default_handler rethrows an unhandled exception).
297  
    struct frame_guard
298  
    struct frame_guard
298  
    {
299  
    {
299  
        std::coroutine_handle<>& h;
300  
        std::coroutine_handle<>& h;
300  
        ~frame_guard() { h.destroy(); }
301  
        ~frame_guard() { h.destroy(); }
301  
    } guard{p.task_h_};
302  
    } guard{p.task_h_};
302  

303  

303  
    p.invoke_(p.task_promise_, p.handlers_);
304  
    p.invoke_(p.task_promise_, p.handlers_);
304  
}
305  
}
305  

306  

306  
} // namespace detail
307  
} // namespace detail
307  

308  

308  
/** Wrapper returned by run_async that accepts a task for execution.
309  
/** Wrapper returned by run_async that accepts a task for execution.
309  

310  

310  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
311  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
311  
    and handlers. The run_async_trampoline is allocated when the wrapper is constructed
312  
    and handlers. The run_async_trampoline is allocated when the wrapper is constructed
312  
    (before the task due to C++17 postfix evaluation order).
313  
    (before the task due to C++17 postfix evaluation order).
313  

314  

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

317  

317  
    @tparam Ex The executor type satisfying the `Executor` concept.
318  
    @tparam Ex The executor type satisfying the `Executor` concept.
318  
    @tparam Handlers The handler type (default_handler or handler_pair).
319  
    @tparam Handlers The handler type (default_handler or handler_pair).
319  
    @tparam Alloc The allocator type (value type or memory_resource*).
320  
    @tparam Alloc The allocator type (value type or memory_resource*).
320  

321  

321  
    @par Thread Safety
322  
    @par Thread Safety
322  
    The wrapper itself should only be used from one thread. The handlers
323  
    The wrapper itself should only be used from one thread. The handlers
323  
    may be invoked from any thread where the executor schedules work.
324  
    may be invoked from any thread where the executor schedules work.
324  

325  

325  
    @par Example
326  
    @par Example
326  
    @code
327  
    @code
327  
    // Correct usage - wrapper is temporary
328  
    // Correct usage - wrapper is temporary
328  
    run_async(ex)(my_task());
329  
    run_async(ex)(my_task());
329  

330  

330  
    // Compile error - cannot call operator() on lvalue
331  
    // Compile error - cannot call operator() on lvalue
331  
    auto w = run_async(ex);
332  
    auto w = run_async(ex);
332  
    w(my_task());  // Error: operator() requires rvalue
333  
    w(my_task());  // Error: operator() requires rvalue
333  
    @endcode
334  
    @endcode
334  

335  

335  
    @see run_async
336  
    @see run_async
336  
*/
337  
*/
337  
template<Executor Ex, class Handlers, class Alloc>
338  
template<Executor Ex, class Handlers, class Alloc>
338  
class [[nodiscard]] run_async_wrapper
339  
class [[nodiscard]] run_async_wrapper
339  
{
340  
{
340  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
341  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
341  
    std::stop_token st_;
342  
    std::stop_token st_;
342  
    std::pmr::memory_resource* saved_tls_;
343  
    std::pmr::memory_resource* saved_tls_;
343  

344  

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

366  

366  
    ~run_async_wrapper()
367  
    ~run_async_wrapper()
367  
    {
368  
    {
368  
        // Restore TLS so stale pointer doesn't outlive
369  
        // Restore TLS so stale pointer doesn't outlive
369  
        // the execution context that owns the resource.
370  
        // the execution context that owns the resource.
370  
        set_current_frame_allocator(saved_tls_);
371  
        set_current_frame_allocator(saved_tls_);
371  
    }
372  
    }
372  

373  

373  
    // Non-copyable, non-movable (must be used immediately)
374  
    // Non-copyable, non-movable (must be used immediately)
374  
    run_async_wrapper(run_async_wrapper const&) = delete;
375  
    run_async_wrapper(run_async_wrapper const&) = delete;
375  
    run_async_wrapper(run_async_wrapper&&) = delete;
376  
    run_async_wrapper(run_async_wrapper&&) = delete;
376  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
377  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
377  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
378  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
378  

379  

379  
    /** Launch the task for execution.
380  
    /** Launch the task for execution.
380  

381  

381  
        This operator accepts a task and launches it on the executor.
382  
        This operator accepts a task and launches it on the executor.
382  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
383  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
383  
        correct LIFO destruction order.
384  
        correct LIFO destruction order.
384  

385  

385  
        The `io_env` constructed for the task is owned by the trampoline
386  
        The `io_env` constructed for the task is owned by the trampoline
386  
        coroutine and is guaranteed to outlive the task and all awaitables
387  
        coroutine and is guaranteed to outlive the task and all awaitables
387  
        in its chain. Awaitables may store `io_env const*` without concern
388  
        in its chain. Awaitables may store `io_env const*` without concern
388  
        for dangling references.
389  
        for dangling references.
389  

390  

390  
        @tparam Task The IoRunnable type.
391  
        @tparam Task The IoRunnable type.
391  

392  

392  
        @param t The task to execute. Ownership is transferred to the
393  
        @param t The task to execute. Ownership is transferred to the
393  
                 run_async_trampoline which will destroy it after completion.
394  
                 run_async_trampoline which will destroy it after completion.
394  
    */
395  
    */
395  
    template<IoRunnable Task>
396  
    template<IoRunnable Task>
396  
    void operator()(Task t) &&
397  
    void operator()(Task t) &&
397  
    {
398  
    {
398  
        auto task_h = t.handle();
399  
        auto task_h = t.handle();
399  
        auto& task_promise = task_h.promise();
400  
        auto& task_promise = task_h.promise();
400  
        t.release();
401  
        t.release();
401  

402  

402  
        auto& p = tr_.h_.promise();
403  
        auto& p = tr_.h_.promise();
403  

404  

404  
        // Inject Task-specific invoke function
405  
        // Inject Task-specific invoke function
405  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
406  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
406  
        p.task_promise_ = &task_promise;
407  
        p.task_promise_ = &task_promise;
407  
        p.task_h_ = task_h;
408  
        p.task_h_ = task_h;
408  

409  

409  
        // Setup task's continuation to return to run_async_trampoline
410  
        // Setup task's continuation to return to run_async_trampoline
410  
        task_promise.set_continuation(tr_.h_);
411  
        task_promise.set_continuation(tr_.h_);
411  
        p.env_ = {p.wg_.executor(), st_, p.get_resource()};
412  
        p.env_ = {p.wg_.executor(), st_, p.get_resource()};
412  
        task_promise.set_environment(&p.env_);
413  
        task_promise.set_environment(&p.env_);
413  

414  

414  
        // Start task through executor.
415  
        // Start task through executor.
415  
        // safe_resume is not needed here: TLS is already saved in the
416  
        // safe_resume is not needed here: TLS is already saved in the
416  
        // constructor (saved_tls_) and restored in the destructor.
417  
        // constructor (saved_tls_) and restored in the destructor.
417  
        p.task_cont_.h = task_h;
418  
        p.task_cont_.h = task_h;
418  
        p.wg_.executor().dispatch(p.task_cont_).resume();
419  
        p.wg_.executor().dispatch(p.task_cont_).resume();
419  
    }
420  
    }
420  
};
421  
};
421  

422  

422  
// Executor only (uses default recycling allocator)
423  
// Executor only (uses default recycling allocator)
423  

424  

424  
/** Asynchronously launch a lazy task on the given executor.
425  
/** Asynchronously launch a lazy task on the given executor.
425  

426  

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

430  

430  
    Uses the default recycling frame allocator for coroutine frames.
431  
    Uses the default recycling frame allocator for coroutine frames.
431  
    With no handlers, the result is discarded and exceptions are rethrown.
432  
    With no handlers, the result is discarded and exceptions are rethrown.
432  

433  

433  
    @par Thread Safety
434  
    @par Thread Safety
434  
    The wrapper and handlers may be called from any thread where the
435  
    The wrapper and handlers may be called from any thread where the
435  
    executor schedules work.
436  
    executor schedules work.
436  

437  

437  
    @par Example
438  
    @par Example
438  
    @code
439  
    @code
439  
    run_async(ioc.get_executor())(my_task());
440  
    run_async(ioc.get_executor())(my_task());
440  
    @endcode
441  
    @endcode
441  

442  

442  
    @param ex The executor to execute the task on.
443  
    @param ex The executor to execute the task on.
443  

444  

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

446  

446  
    @see task
447  
    @see task
447  
    @see executor
448  
    @see executor
448  
*/
449  
*/
449  
template<Executor Ex>
450  
template<Executor Ex>
450  
[[nodiscard]] auto
451  
[[nodiscard]] auto
451  
run_async(Ex ex)
452  
run_async(Ex ex)
452  
{
453  
{
453  
    auto* mr = ex.context().get_frame_allocator();
454  
    auto* mr = ex.context().get_frame_allocator();
454  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
455  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
455  
        std::move(ex),
456  
        std::move(ex),
456  
        std::stop_token{},
457  
        std::stop_token{},
457  
        detail::default_handler{},
458  
        detail::default_handler{},
458  
        mr);
459  
        mr);
459  
}
460  
}
460  

461  

461  
/** Asynchronously launch a lazy task with a result handler.
462  
/** Asynchronously launch a lazy task with a result handler.
462  

463  

463  
    The handler `h1` is called with the task's result on success. If `h1`
464  
    The handler `h1` is called with the task's result on success. If `h1`
464  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
465  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
465  
    Otherwise, exceptions are rethrown.
466  
    Otherwise, exceptions are rethrown.
466  

467  

467  
    @par Thread Safety
468  
    @par Thread Safety
468  
    The handler may be called from any thread where the executor
469  
    The handler may be called from any thread where the executor
469  
    schedules work.
470  
    schedules work.
470  

471  

471  
    @par Example
472  
    @par Example
472  
    @code
473  
    @code
473  
    // Handler for result only (exceptions rethrown)
474  
    // Handler for result only (exceptions rethrown)
474  
    run_async(ex, [](int result) {
475  
    run_async(ex, [](int result) {
475  
        std::cout << "Got: " << result << "\n";
476  
        std::cout << "Got: " << result << "\n";
476  
    })(compute_value());
477  
    })(compute_value());
477  

478  

478  
    // Overloaded handler for both result and exception
479  
    // Overloaded handler for both result and exception
479  
    run_async(ex, overloaded{
480  
    run_async(ex, overloaded{
480  
        [](int result) { std::cout << "Got: " << result << "\n"; },
481  
        [](int result) { std::cout << "Got: " << result << "\n"; },
481  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
482  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
482  
    })(compute_value());
483  
    })(compute_value());
483  
    @endcode
484  
    @endcode
484  

485  

485  
    @param ex The executor to execute the task on.
486  
    @param ex The executor to execute the task on.
486  
    @param h1 The handler to invoke with the result (and optionally exception).
487  
    @param h1 The handler to invoke with the result (and optionally exception).
487  

488  

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

490  

490  
    @see task
491  
    @see task
491  
    @see executor
492  
    @see executor
492  
*/
493  
*/
493  
template<Executor Ex, class H1>
494  
template<Executor Ex, class H1>
494  
[[nodiscard]] auto
495  
[[nodiscard]] auto
495  
run_async(Ex ex, H1 h1)
496  
run_async(Ex ex, H1 h1)
496  
{
497  
{
497  
    auto* mr = ex.context().get_frame_allocator();
498  
    auto* mr = ex.context().get_frame_allocator();
498  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
499  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
499  
        std::move(ex),
500  
        std::move(ex),
500  
        std::stop_token{},
501  
        std::stop_token{},
501  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
502  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
502  
        mr);
503  
        mr);
503  
}
504  
}
504  

505  

505  
/** Asynchronously launch a lazy task with separate result and error handlers.
506  
/** Asynchronously launch a lazy task with separate result and error handlers.
506  

507  

507  
    The handler `h1` is called with the task's result on success.
508  
    The handler `h1` is called with the task's result on success.
508  
    The handler `h2` is called with the exception_ptr on failure.
509  
    The handler `h2` is called with the exception_ptr on failure.
509  

510  

510  
    @par Thread Safety
511  
    @par Thread Safety
511  
    The handlers may be called from any thread where the executor
512  
    The handlers may be called from any thread where the executor
512  
    schedules work.
513  
    schedules work.
513  

514  

514  
    @par Example
515  
    @par Example
515  
    @code
516  
    @code
516  
    run_async(ex,
517  
    run_async(ex,
517  
        [](int result) { std::cout << "Got: " << result << "\n"; },
518  
        [](int result) { std::cout << "Got: " << result << "\n"; },
518  
        [](std::exception_ptr ep) {
519  
        [](std::exception_ptr ep) {
519  
            try { std::rethrow_exception(ep); }
520  
            try { std::rethrow_exception(ep); }
520  
            catch (std::exception const& e) {
521  
            catch (std::exception const& e) {
521  
                std::cout << "Error: " << e.what() << "\n";
522  
                std::cout << "Error: " << e.what() << "\n";
522  
            }
523  
            }
523  
        }
524  
        }
524  
    )(compute_value());
525  
    )(compute_value());
525  
    @endcode
526  
    @endcode
526  

527  

527  
    @param ex The executor to execute the task on.
528  
    @param ex The executor to execute the task on.
528  
    @param h1 The handler to invoke with the result on success.
529  
    @param h1 The handler to invoke with the result on success.
529  
    @param h2 The handler to invoke with the exception on failure.
530  
    @param h2 The handler to invoke with the exception on failure.
530  

531  

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

533  

533  
    @see task
534  
    @see task
534  
    @see executor
535  
    @see executor
535  
*/
536  
*/
536  
template<Executor Ex, class H1, class H2>
537  
template<Executor Ex, class H1, class H2>
537  
[[nodiscard]] auto
538  
[[nodiscard]] auto
538  
run_async(Ex ex, H1 h1, H2 h2)
539  
run_async(Ex ex, H1 h1, H2 h2)
539  
{
540  
{
540  
    auto* mr = ex.context().get_frame_allocator();
541  
    auto* mr = ex.context().get_frame_allocator();
541  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
542  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
542  
        std::move(ex),
543  
        std::move(ex),
543  
        std::stop_token{},
544  
        std::stop_token{},
544  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
545  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
545  
        mr);
546  
        mr);
546  
}
547  
}
547  

548  

548  
// Ex + stop_token
549  
// Ex + stop_token
549  

550  

550  
/** Asynchronously launch a lazy task with stop token support.
551  
/** Asynchronously launch a lazy task with stop token support.
551  

552  

552  
    The stop token is propagated to the task, enabling cooperative
553  
    The stop token is propagated to the task, enabling cooperative
553  
    cancellation. With no handlers, the result is discarded and
554  
    cancellation. With no handlers, the result is discarded and
554  
    exceptions are rethrown.
555  
    exceptions are rethrown.
555  

556  

556  
    @par Thread Safety
557  
    @par Thread Safety
557  
    The wrapper may be called from any thread where the executor
558  
    The wrapper may be called from any thread where the executor
558  
    schedules work.
559  
    schedules work.
559  

560  

560  
    @par Example
561  
    @par Example
561  
    @code
562  
    @code
562  
    std::stop_source source;
563  
    std::stop_source source;
563  
    run_async(ex, source.get_token())(cancellable_task());
564  
    run_async(ex, source.get_token())(cancellable_task());
564  
    // Later: source.request_stop();
565  
    // Later: source.request_stop();
565  
    @endcode
566  
    @endcode
566  

567  

567  
    @param ex The executor to execute the task on.
568  
    @param ex The executor to execute the task on.
568  
    @param st The stop token for cooperative cancellation.
569  
    @param st The stop token for cooperative cancellation.
569  

570  

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

572  

572  
    @see task
573  
    @see task
573  
    @see executor
574  
    @see executor
574  
*/
575  
*/
575  
template<Executor Ex>
576  
template<Executor Ex>
576  
[[nodiscard]] auto
577  
[[nodiscard]] auto
577  
run_async(Ex ex, std::stop_token st)
578  
run_async(Ex ex, std::stop_token st)
578  
{
579  
{
579  
    auto* mr = ex.context().get_frame_allocator();
580  
    auto* mr = ex.context().get_frame_allocator();
580  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
581  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
581  
        std::move(ex),
582  
        std::move(ex),
582  
        std::move(st),
583  
        std::move(st),
583  
        detail::default_handler{},
584  
        detail::default_handler{},
584  
        mr);
585  
        mr);
585  
}
586  
}
586  

587  

587  
/** Asynchronously launch a lazy task with stop token and result handler.
588  
/** Asynchronously launch a lazy task with stop token and result handler.
588  

589  

589  
    The stop token is propagated to the task for cooperative cancellation.
590  
    The stop token is propagated to the task for cooperative cancellation.
590  
    The handler `h1` is called with the result on success, and optionally
591  
    The handler `h1` is called with the result on success, and optionally
591  
    with exception_ptr if it accepts that type.
592  
    with exception_ptr if it accepts that type.
592  

593  

593  
    @param ex The executor to execute the task on.
594  
    @param ex The executor to execute the task on.
594  
    @param st The stop token for cooperative cancellation.
595  
    @param st The stop token for cooperative cancellation.
595  
    @param h1 The handler to invoke with the result (and optionally exception).
596  
    @param h1 The handler to invoke with the result (and optionally exception).
596  

597  

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

599  

599  
    @see task
600  
    @see task
600  
    @see executor
601  
    @see executor
601  
*/
602  
*/
602  
template<Executor Ex, class H1>
603  
template<Executor Ex, class H1>
603  
[[nodiscard]] auto
604  
[[nodiscard]] auto
604  
run_async(Ex ex, std::stop_token st, H1 h1)
605  
run_async(Ex ex, std::stop_token st, H1 h1)
605  
{
606  
{
606  
    auto* mr = ex.context().get_frame_allocator();
607  
    auto* mr = ex.context().get_frame_allocator();
607  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
608  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
608  
        std::move(ex),
609  
        std::move(ex),
609  
        std::move(st),
610  
        std::move(st),
610  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
611  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
611  
        mr);
612  
        mr);
612  
}
613  
}
613  

614  

614  
/** Asynchronously launch a lazy task with stop token and separate handlers.
615  
/** Asynchronously launch a lazy task with stop token and separate handlers.
615  

616  

616  
    The stop token is propagated to the task for cooperative cancellation.
617  
    The stop token is propagated to the task for cooperative cancellation.
617  
    The handler `h1` is called on success, `h2` on failure.
618  
    The handler `h1` is called on success, `h2` on failure.
618  

619  

619  
    @param ex The executor to execute the task on.
620  
    @param ex The executor to execute the task on.
620  
    @param st The stop token for cooperative cancellation.
621  
    @param st The stop token for cooperative cancellation.
621  
    @param h1 The handler to invoke with the result on success.
622  
    @param h1 The handler to invoke with the result on success.
622  
    @param h2 The handler to invoke with the exception on failure.
623  
    @param h2 The handler to invoke with the exception on failure.
623  

624  

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

626  

626  
    @see task
627  
    @see task
627  
    @see executor
628  
    @see executor
628  
*/
629  
*/
629  
template<Executor Ex, class H1, class H2>
630  
template<Executor Ex, class H1, class H2>
630  
[[nodiscard]] auto
631  
[[nodiscard]] auto
631  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
632  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
632  
{
633  
{
633  
    auto* mr = ex.context().get_frame_allocator();
634  
    auto* mr = ex.context().get_frame_allocator();
634  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
635  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
635  
        std::move(ex),
636  
        std::move(ex),
636  
        std::move(st),
637  
        std::move(st),
637  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
638  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
638  
        mr);
639  
        mr);
639  
}
640  
}
640  

641  

641  
// Ex + memory_resource*
642  
// Ex + memory_resource*
642  

643  

643  
/** Asynchronously launch a lazy task with custom memory resource.
644  
/** Asynchronously launch a lazy task with custom memory resource.
644  

645  

645  
    The memory resource is used for coroutine frame allocation. The caller
646  
    The memory resource is used for coroutine frame allocation. The caller
646  
    is responsible for ensuring the memory resource outlives all tasks.
647  
    is responsible for ensuring the memory resource outlives all tasks.
647  

648  

648  
    @param ex The executor to execute the task on.
649  
    @param ex The executor to execute the task on.
649  
    @param mr The memory resource for frame allocation.
650  
    @param mr The memory resource for frame allocation.
650  

651  

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

653  

653  
    @see task
654  
    @see task
654  
    @see executor
655  
    @see executor
655  
*/
656  
*/
656  
template<Executor Ex>
657  
template<Executor Ex>
657  
[[nodiscard]] auto
658  
[[nodiscard]] auto
658  
run_async(Ex ex, std::pmr::memory_resource* mr)
659  
run_async(Ex ex, std::pmr::memory_resource* mr)
659  
{
660  
{
660  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
661  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
661  
        std::move(ex),
662  
        std::move(ex),
662  
        std::stop_token{},
663  
        std::stop_token{},
663  
        detail::default_handler{},
664  
        detail::default_handler{},
664  
        mr);
665  
        mr);
665  
}
666  
}
666  

667  

667  
/** Asynchronously launch a lazy task with memory resource and handler.
668  
/** Asynchronously launch a lazy task with memory resource and handler.
668  

669  

669  
    @param ex The executor to execute the task on.
670  
    @param ex The executor to execute the task on.
670  
    @param mr The memory resource for frame allocation.
671  
    @param mr The memory resource for frame allocation.
671  
    @param h1 The handler to invoke with the result (and optionally exception).
672  
    @param h1 The handler to invoke with the result (and optionally exception).
672  

673  

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

675  

675  
    @see task
676  
    @see task
676  
    @see executor
677  
    @see executor
677  
*/
678  
*/
678  
template<Executor Ex, class H1>
679  
template<Executor Ex, class H1>
679  
[[nodiscard]] auto
680  
[[nodiscard]] auto
680  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
681  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
681  
{
682  
{
682  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
683  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
683  
        std::move(ex),
684  
        std::move(ex),
684  
        std::stop_token{},
685  
        std::stop_token{},
685  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
686  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
686  
        mr);
687  
        mr);
687  
}
688  
}
688  

689  

689  
/** Asynchronously launch a lazy task with memory resource and handlers.
690  
/** Asynchronously launch a lazy task with memory resource and handlers.
690  

691  

691  
    @param ex The executor to execute the task on.
692  
    @param ex The executor to execute the task on.
692  
    @param mr The memory resource for frame allocation.
693  
    @param mr The memory resource for frame allocation.
693  
    @param h1 The handler to invoke with the result on success.
694  
    @param h1 The handler to invoke with the result on success.
694  
    @param h2 The handler to invoke with the exception on failure.
695  
    @param h2 The handler to invoke with the exception on failure.
695  

696  

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

698  

698  
    @see task
699  
    @see task
699  
    @see executor
700  
    @see executor
700  
*/
701  
*/
701  
template<Executor Ex, class H1, class H2>
702  
template<Executor Ex, class H1, class H2>
702  
[[nodiscard]] auto
703  
[[nodiscard]] auto
703  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
704  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
704  
{
705  
{
705  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
706  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
706  
        std::move(ex),
707  
        std::move(ex),
707  
        std::stop_token{},
708  
        std::stop_token{},
708  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
709  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
709  
        mr);
710  
        mr);
710  
}
711  
}
711  

712  

712  
// Ex + stop_token + memory_resource*
713  
// Ex + stop_token + memory_resource*
713  

714  

714  
/** Asynchronously launch a lazy task with stop token and memory resource.
715  
/** Asynchronously launch a lazy task with stop token and memory resource.
715  

716  

716  
    @param ex The executor to execute the task on.
717  
    @param ex The executor to execute the task on.
717  
    @param st The stop token for cooperative cancellation.
718  
    @param st The stop token for cooperative cancellation.
718  
    @param mr The memory resource for frame allocation.
719  
    @param mr The memory resource for frame allocation.
719  

720  

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

722  

722  
    @see task
723  
    @see task
723  
    @see executor
724  
    @see executor
724  
*/
725  
*/
725  
template<Executor Ex>
726  
template<Executor Ex>
726  
[[nodiscard]] auto
727  
[[nodiscard]] auto
727  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
728  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
728  
{
729  
{
729  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
730  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
730  
        std::move(ex),
731  
        std::move(ex),
731  
        std::move(st),
732  
        std::move(st),
732  
        detail::default_handler{},
733  
        detail::default_handler{},
733  
        mr);
734  
        mr);
734  
}
735  
}
735  

736  

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

738  

738  
    @param ex The executor to execute the task on.
739  
    @param ex The executor to execute the task on.
739  
    @param st The stop token for cooperative cancellation.
740  
    @param st The stop token for cooperative cancellation.
740  
    @param mr The memory resource for frame allocation.
741  
    @param mr The memory resource for frame allocation.
741  
    @param h1 The handler to invoke with the result (and optionally exception).
742  
    @param h1 The handler to invoke with the result (and optionally exception).
742  

743  

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

745  

745  
    @see task
746  
    @see task
746  
    @see executor
747  
    @see executor
747  
*/
748  
*/
748  
template<Executor Ex, class H1>
749  
template<Executor Ex, class H1>
749  
[[nodiscard]] auto
750  
[[nodiscard]] auto
750  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
751  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
751  
{
752  
{
752  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
753  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
753  
        std::move(ex),
754  
        std::move(ex),
754  
        std::move(st),
755  
        std::move(st),
755  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
756  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
756  
        mr);
757  
        mr);
757  
}
758  
}
758  

759  

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

761  

761  
    @param ex The executor to execute the task on.
762  
    @param ex The executor to execute the task on.
762  
    @param st The stop token for cooperative cancellation.
763  
    @param st The stop token for cooperative cancellation.
763  
    @param mr The memory resource for frame allocation.
764  
    @param mr The memory resource for frame allocation.
764  
    @param h1 The handler to invoke with the result on success.
765  
    @param h1 The handler to invoke with the result on success.
765  
    @param h2 The handler to invoke with the exception on failure.
766  
    @param h2 The handler to invoke with the exception on failure.
766  

767  

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

769  

769  
    @see task
770  
    @see task
770  
    @see executor
771  
    @see executor
771  
*/
772  
*/
772  
template<Executor Ex, class H1, class H2>
773  
template<Executor Ex, class H1, class H2>
773  
[[nodiscard]] auto
774  
[[nodiscard]] auto
774  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
775  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
775  
{
776  
{
776  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
777  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
777  
        std::move(ex),
778  
        std::move(ex),
778  
        std::move(st),
779  
        std::move(st),
779  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
780  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
780  
        mr);
781  
        mr);
781  
}
782  
}
782  

783  

783  
// Ex + standard Allocator (value type)
784  
// Ex + standard Allocator (value type)
784  

785  

785  
/** Asynchronously launch a lazy task with custom allocator.
786  
/** Asynchronously launch a lazy task with custom allocator.
786  

787  

787  
    The allocator is wrapped in a frame_memory_resource and stored in the
788  
    The allocator is wrapped in a frame_memory_resource and stored in the
788  
    run_async_trampoline, ensuring it outlives all coroutine frames.
789  
    run_async_trampoline, ensuring it outlives all coroutine frames.
789  

790  

790  
    @param ex The executor to execute the task on.
791  
    @param ex The executor to execute the task on.
791  
    @param alloc The allocator for frame allocation (copied and stored).
792  
    @param alloc The allocator for frame allocation (copied and stored).
792  

793  

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

795  

795  
    @see task
796  
    @see task
796  
    @see executor
797  
    @see executor
797  
*/
798  
*/
798  
template<Executor Ex, detail::Allocator Alloc>
799  
template<Executor Ex, detail::Allocator Alloc>
799  
[[nodiscard]] auto
800  
[[nodiscard]] auto
800  
run_async(Ex ex, Alloc alloc)
801  
run_async(Ex ex, Alloc alloc)
801  
{
802  
{
802  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
803  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
803  
        std::move(ex),
804  
        std::move(ex),
804  
        std::stop_token{},
805  
        std::stop_token{},
805  
        detail::default_handler{},
806  
        detail::default_handler{},
806  
        std::move(alloc));
807  
        std::move(alloc));
807  
}
808  
}
808  

809  

809  
/** Asynchronously launch a lazy task with allocator and handler.
810  
/** Asynchronously launch a lazy task with allocator and handler.
810  

811  

811  
    @param ex The executor to execute the task on.
812  
    @param ex The executor to execute the task on.
812  
    @param alloc The allocator for frame allocation (copied and stored).
813  
    @param alloc The allocator for frame allocation (copied and stored).
813  
    @param h1 The handler to invoke with the result (and optionally exception).
814  
    @param h1 The handler to invoke with the result (and optionally exception).
814  

815  

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

817  

817  
    @see task
818  
    @see task
818  
    @see executor
819  
    @see executor
819  
*/
820  
*/
820  
template<Executor Ex, detail::Allocator Alloc, class H1>
821  
template<Executor Ex, detail::Allocator Alloc, class H1>
821  
[[nodiscard]] auto
822  
[[nodiscard]] auto
822  
run_async(Ex ex, Alloc alloc, H1 h1)
823  
run_async(Ex ex, Alloc alloc, H1 h1)
823  
{
824  
{
824  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
825  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
825  
        std::move(ex),
826  
        std::move(ex),
826  
        std::stop_token{},
827  
        std::stop_token{},
827  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
828  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
828  
        std::move(alloc));
829  
        std::move(alloc));
829  
}
830  
}
830  

831  

831  
/** Asynchronously launch a lazy task with allocator and handlers.
832  
/** Asynchronously launch a lazy task with allocator and handlers.
832  

833  

833  
    @param ex The executor to execute the task on.
834  
    @param ex The executor to execute the task on.
834  
    @param alloc The allocator for frame allocation (copied and stored).
835  
    @param alloc The allocator for frame allocation (copied and stored).
835  
    @param h1 The handler to invoke with the result on success.
836  
    @param h1 The handler to invoke with the result on success.
836  
    @param h2 The handler to invoke with the exception on failure.
837  
    @param h2 The handler to invoke with the exception on failure.
837  

838  

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

840  

840  
    @see task
841  
    @see task
841  
    @see executor
842  
    @see executor
842  
*/
843  
*/
843  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
844  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
844  
[[nodiscard]] auto
845  
[[nodiscard]] auto
845  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
846  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
846  
{
847  
{
847  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
848  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
848  
        std::move(ex),
849  
        std::move(ex),
849  
        std::stop_token{},
850  
        std::stop_token{},
850  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
851  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
851  
        std::move(alloc));
852  
        std::move(alloc));
852  
}
853  
}
853  

854  

854  
// Ex + stop_token + standard Allocator
855  
// Ex + stop_token + standard Allocator
855  

856  

856  
/** Asynchronously launch a lazy task with stop token and allocator.
857  
/** Asynchronously launch a lazy task with stop token and allocator.
857  

858  

858  
    @param ex The executor to execute the task on.
859  
    @param ex The executor to execute the task on.
859  
    @param st The stop token for cooperative cancellation.
860  
    @param st The stop token for cooperative cancellation.
860  
    @param alloc The allocator for frame allocation (copied and stored).
861  
    @param alloc The allocator for frame allocation (copied and stored).
861  

862  

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

864  

864  
    @see task
865  
    @see task
865  
    @see executor
866  
    @see executor
866  
*/
867  
*/
867  
template<Executor Ex, detail::Allocator Alloc>
868  
template<Executor Ex, detail::Allocator Alloc>
868  
[[nodiscard]] auto
869  
[[nodiscard]] auto
869  
run_async(Ex ex, std::stop_token st, Alloc alloc)
870  
run_async(Ex ex, std::stop_token st, Alloc alloc)
870  
{
871  
{
871  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
872  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
872  
        std::move(ex),
873  
        std::move(ex),
873  
        std::move(st),
874  
        std::move(st),
874  
        detail::default_handler{},
875  
        detail::default_handler{},
875  
        std::move(alloc));
876  
        std::move(alloc));
876  
}
877  
}
877  

878  

878  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
879  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
879  

880  

880  
    @param ex The executor to execute the task on.
881  
    @param ex The executor to execute the task on.
881  
    @param st The stop token for cooperative cancellation.
882  
    @param st The stop token for cooperative cancellation.
882  
    @param alloc The allocator for frame allocation (copied and stored).
883  
    @param alloc The allocator for frame allocation (copied and stored).
883  
    @param h1 The handler to invoke with the result (and optionally exception).
884  
    @param h1 The handler to invoke with the result (and optionally exception).
884  

885  

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

887  

887  
    @see task
888  
    @see task
888  
    @see executor
889  
    @see executor
889  
*/
890  
*/
890  
template<Executor Ex, detail::Allocator Alloc, class H1>
891  
template<Executor Ex, detail::Allocator Alloc, class H1>
891  
[[nodiscard]] auto
892  
[[nodiscard]] auto
892  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
893  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
893  
{
894  
{
894  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
895  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
895  
        std::move(ex),
896  
        std::move(ex),
896  
        std::move(st),
897  
        std::move(st),
897  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
898  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
898  
        std::move(alloc));
899  
        std::move(alloc));
899  
}
900  
}
900  

901  

901  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
902  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
902  

903  

903  
    @param ex The executor to execute the task on.
904  
    @param ex The executor to execute the task on.
904  
    @param st The stop token for cooperative cancellation.
905  
    @param st The stop token for cooperative cancellation.
905  
    @param alloc The allocator for frame allocation (copied and stored).
906  
    @param alloc The allocator for frame allocation (copied and stored).
906  
    @param h1 The handler to invoke with the result on success.
907  
    @param h1 The handler to invoke with the result on success.
907  
    @param h2 The handler to invoke with the exception on failure.
908  
    @param h2 The handler to invoke with the exception on failure.
908  

909  

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

911  

911  
    @see task
912  
    @see task
912  
    @see executor
913  
    @see executor
913  
*/
914  
*/
914  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
915  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
915  
[[nodiscard]] auto
916  
[[nodiscard]] auto
916  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
917  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
917  
{
918  
{
918  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
919  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
919  
        std::move(ex),
920  
        std::move(ex),
920  
        std::move(st),
921  
        std::move(st),
921  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
922  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
922  
        std::move(alloc));
923  
        std::move(alloc));
923  
}
924  
}
924  

925  

925  
} // namespace capy
926  
} // namespace capy
926  
} // namespace boost
927  
} // namespace boost
927  

928  

928  
#endif
929  
#endif