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 BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE 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 BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE
196  
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE
197  
    run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
197  
    run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
198  
{
198  
{
199  
    using invoke_fn = void(*)(void*, Handlers&);
199  
    using invoke_fn = void(*)(void*, Handlers&);
200  

200  

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

214  

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

222  

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

232  

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

240  

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

245  

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

251  

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

256  

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

261  

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

265  

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

270  

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

272  

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

286  

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

295  

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

303  

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

306  

307  
} // namespace detail
307  
} // namespace detail
308  

308  

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

310  

311  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
311  
    This wrapper holds the run_async_trampoline coroutine, executor, stop token,
312  
    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
313  
    (before the task due to C++17 postfix evaluation order).
313  
    (before the task due to C++17 postfix evaluation order).
314  

314  

315  
    The rvalue ref-qualifier on `operator()` ensures the wrapper can only
315  
    The rvalue ref-qualifier on `operator()` ensures the wrapper can only
316  
    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.
317  

317  

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

321  

322  
    @par Thread Safety
322  
    @par Thread Safety
323  
    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
324  
    may be invoked from any thread where the executor schedules work.
324  
    may be invoked from any thread where the executor schedules work.
325  

325  

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

330  

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

335  

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

344  

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

366  

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

373  

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

379  

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

381  

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

385  

386  
        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
387  
        coroutine and is guaranteed to outlive the task and all awaitables
387  
        coroutine and is guaranteed to outlive the task and all awaitables
388  
        in its chain. Awaitables may store `io_env const*` without concern
388  
        in its chain. Awaitables may store `io_env const*` without concern
389  
        for dangling references.
389  
        for dangling references.
390  

390  

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

392  

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

402  

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

404  

405  
        // Inject Task-specific invoke function
405  
        // Inject Task-specific invoke function
406  
        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>;
407  
        p.task_promise_ = &task_promise;
407  
        p.task_promise_ = &task_promise;
408  
        p.task_h_ = task_h;
408  
        p.task_h_ = task_h;
409  

409  

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

414  

415  
        // Start task through executor.
415  
        // Start task through executor.
416  
        // 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
417  
        // constructor (saved_tls_) and restored in the destructor.
417  
        // constructor (saved_tls_) and restored in the destructor.
418  
        p.task_cont_.h = task_h;
418  
        p.task_cont_.h = task_h;
419  
        p.wg_.executor().dispatch(p.task_cont_).resume();
419  
        p.wg_.executor().dispatch(p.task_cont_).resume();
420  
    }
420  
    }
421  
};
421  
};
422  

422  

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

424  

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

426  

427  
    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.
428  
    The returned wrapper must be immediately invoked with the task;
428  
    The returned wrapper must be immediately invoked with the task;
429  
    storing the wrapper and calling it later violates LIFO ordering.
429  
    storing the wrapper and calling it later violates LIFO ordering.
430  

430  

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

433  

434  
    @par Thread Safety
434  
    @par Thread Safety
435  
    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
436  
    executor schedules work.
436  
    executor schedules work.
437  

437  

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

442  

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

444  

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

446  

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

461  

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

463  

464  
    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`
465  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
465  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
466  
    Otherwise, exceptions are rethrown.
466  
    Otherwise, exceptions are rethrown.
467  

467  

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

471  

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

478  

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

485  

486  
    @param ex The executor to execute the task on.
486  
    @param ex The executor to execute the task on.
487  
    @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).
488  

488  

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

490  

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

505  

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

507  

508  
    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.
509  
    The handler `h2` is called with the exception_ptr on failure.
509  
    The handler `h2` is called with the exception_ptr on failure.
510  

510  

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

514  

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

527  

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

531  

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

533  

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

548  

549  
// Ex + stop_token
549  
// Ex + stop_token
550  

550  

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

552  

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

556  

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

560  

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

567  

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

570  

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

572  

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

587  

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

589  

590  
    The stop token is propagated to the task for cooperative cancellation.
590  
    The stop token is propagated to the task for cooperative cancellation.
591  
    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
592  
    with exception_ptr if it accepts that type.
592  
    with exception_ptr if it accepts that type.
593  

593  

594  
    @param ex The executor to execute the task on.
594  
    @param ex The executor to execute the task on.
595  
    @param st The stop token for cooperative cancellation.
595  
    @param st The stop token for cooperative cancellation.
596  
    @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).
597  

597  

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

599  

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

614  

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

616  

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

619  

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

624  

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

626  

627  
    @see task
627  
    @see task
628  
    @see executor
628  
    @see executor
629  
*/
629  
*/
630  
template<Executor Ex, class H1, class H2>
630  
template<Executor Ex, class H1, class H2>
631  
[[nodiscard]] auto
631  
[[nodiscard]] auto
632  
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)
633  
{
633  
{
634  
    auto* mr = ex.context().get_frame_allocator();
634  
    auto* mr = ex.context().get_frame_allocator();
635  
    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*>(
636  
        std::move(ex),
636  
        std::move(ex),
637  
        std::move(st),
637  
        std::move(st),
638  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
638  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
639  
        mr);
639  
        mr);
640  
}
640  
}
641  

641  

642  
// Ex + memory_resource*
642  
// Ex + memory_resource*
643  

643  

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

645  

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

648  

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

651  

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

653  

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

667  

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

669  

670  
    @param ex The executor to execute the task on.
670  
    @param ex The executor to execute the task on.
671  
    @param mr The memory resource for frame allocation.
671  
    @param mr The memory resource for frame allocation.
672  
    @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).
673  

673  

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

675  

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

689  

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

691  

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

696  

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

698  

699  
    @see task
699  
    @see task
700  
    @see executor
700  
    @see executor
701  
*/
701  
*/
702  
template<Executor Ex, class H1, class H2>
702  
template<Executor Ex, class H1, class H2>
703  
[[nodiscard]] auto
703  
[[nodiscard]] auto
704  
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)
705  
{
705  
{
706  
    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*>(
707  
        std::move(ex),
707  
        std::move(ex),
708  
        std::stop_token{},
708  
        std::stop_token{},
709  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
709  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
710  
        mr);
710  
        mr);
711  
}
711  
}
712  

712  

713  
// Ex + stop_token + memory_resource*
713  
// Ex + stop_token + memory_resource*
714  

714  

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

716  

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

720  

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

722  

723  
    @see task
723  
    @see task
724  
    @see executor
724  
    @see executor
725  
*/
725  
*/
726  
template<Executor Ex>
726  
template<Executor Ex>
727  
[[nodiscard]] auto
727  
[[nodiscard]] auto
728  
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)
729  
{
729  
{
730  
    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*>(
731  
        std::move(ex),
731  
        std::move(ex),
732  
        std::move(st),
732  
        std::move(st),
733  
        detail::default_handler{},
733  
        detail::default_handler{},
734  
        mr);
734  
        mr);
735  
}
735  
}
736  

736  

737  
/** 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.
738  

738  

739  
    @param ex The executor to execute the task on.
739  
    @param ex The executor to execute the task on.
740  
    @param st The stop token for cooperative cancellation.
740  
    @param st The stop token for cooperative cancellation.
741  
    @param mr The memory resource for frame allocation.
741  
    @param mr The memory resource for frame allocation.
742  
    @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).
743  

743  

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

745  

746  
    @see task
746  
    @see task
747  
    @see executor
747  
    @see executor
748  
*/
748  
*/
749  
template<Executor Ex, class H1>
749  
template<Executor Ex, class H1>
750  
[[nodiscard]] auto
750  
[[nodiscard]] auto
751  
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)
752  
{
752  
{
753  
    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*>(
754  
        std::move(ex),
754  
        std::move(ex),
755  
        std::move(st),
755  
        std::move(st),
756  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
756  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
757  
        mr);
757  
        mr);
758  
}
758  
}
759  

759  

760  
/** 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.
761  

761  

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

767  

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

769  

770  
    @see task
770  
    @see task
771  
    @see executor
771  
    @see executor
772  
*/
772  
*/
773  
template<Executor Ex, class H1, class H2>
773  
template<Executor Ex, class H1, class H2>
774  
[[nodiscard]] auto
774  
[[nodiscard]] auto
775  
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)
776  
{
776  
{
777  
    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*>(
778  
        std::move(ex),
778  
        std::move(ex),
779  
        std::move(st),
779  
        std::move(st),
780  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
780  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
781  
        mr);
781  
        mr);
782  
}
782  
}
783  

783  

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

785  

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

787  

788  
    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
789  
    run_async_trampoline, ensuring it outlives all coroutine frames.
789  
    run_async_trampoline, ensuring it outlives all coroutine frames.
790  

790  

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

793  

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

795  

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

809  

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

811  

812  
    @param ex The executor to execute the task on.
812  
    @param ex The executor to execute the task on.
813  
    @param alloc The allocator for frame allocation (copied and stored).
813  
    @param alloc The allocator for frame allocation (copied and stored).
814  
    @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).
815  

815  

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

817  

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

831  

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

833  

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

838  

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

840  

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

854  

855  
// Ex + stop_token + standard Allocator
855  
// Ex + stop_token + standard Allocator
856  

856  

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

858  

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

862  

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

864  

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

878  

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

880  

881  
    @param ex The executor to execute the task on.
881  
    @param ex The executor to execute the task on.
882  
    @param st The stop token for cooperative cancellation.
882  
    @param st The stop token for cooperative cancellation.
883  
    @param alloc The allocator for frame allocation (copied and stored).
883  
    @param alloc The allocator for frame allocation (copied and stored).
884  
    @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).
885  

885  

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

887  

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

901  

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

903  

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

909  

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

911  

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

925  

926  
} // namespace capy
926  
} // namespace capy
927  
} // namespace boost
927  
} // namespace boost
928  

928  

929  
#endif
929  
#endif