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

199  

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

213  

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

221  

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

231  

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

239  

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

244  

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

250  

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

255  

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

260  

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

264  

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

269  

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

271  

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

285  

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

294  

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

302  

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

305  

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

307  

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

309  

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

313  

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

316  

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

320  

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

324  

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

329  

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

334  

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

343  

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

365  

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

372  

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

378  

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

380  

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

384  

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

389  

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

391  

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

401  

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

403  

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

408  

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

413  

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

421  

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

423  

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

425  

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

429  

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

432  

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

436  

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

441  

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

443  

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

445  

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

460  

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

462  

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

466  

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

470  

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

477  

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

484  

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

487  

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

489  

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

504  

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

506  

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

509  

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

513  

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

526  

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

530  

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

532  

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

547  

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

549  

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

551  

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

555  

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

559  

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

566  

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

569  

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

571  

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

586  

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

588  

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

592  

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

596  

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

598  

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

613  

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

615  

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

618  

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

623  

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

625  

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

640  

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

642  

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

644  

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

647  

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

650  

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

652  

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

666  

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

668  

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

672  

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

674  

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

688  

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

690  

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

695  

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

697  

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

711  

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

713  

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

715  

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

719  

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

721  

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

735  

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

737  

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

742  

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

744  

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

758  

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

760  

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

766  

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

768  

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

782  

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

784  

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

786  

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

789  

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

792  

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

794  

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

808  

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

810  

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

814  

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

816  

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

830  

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

832  

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

837  

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

839  

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

853  

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

855  

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

857  

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

861  

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

863  

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

877  

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

879  

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

884  

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

886  

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

900  

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

902  

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

908  

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

910  

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

924  

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

927  

928  
#endif
928  
#endif