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_launchable_task.hpp>
17  
#include <boost/capy/concept/io_launchable_task.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/recycling_memory_resource.hpp>
20  
#include <boost/capy/ex/recycling_memory_resource.hpp>
21  
#include <boost/capy/ex/work_guard.hpp>
21  
#include <boost/capy/ex/work_guard.hpp>
22  

22  

23  
#include <coroutine>
23  
#include <coroutine>
24  
#include <memory_resource>
24  
#include <memory_resource>
25  
#include <new>
25  
#include <new>
26  
#include <stop_token>
26  
#include <stop_token>
27  
#include <type_traits>
27  
#include <type_traits>
28  

28  

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

32  

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

35  

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

47  

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

53  

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

55  

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

61  

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

67  

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

69  

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

73  

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

76  

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

85  

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

94  

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

101  

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

107  

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

112  

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

115  

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

119  

120  
            new (fn_loc + 1) byte_alloc(std::move(ba));
120  
            new (fn_loc + 1) byte_alloc(std::move(ba));
121  

121  

122  
            return raw;
122  
            return raw;
123  
        }
123  
        }
124  

124  

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

131  

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

136  

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

141  

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

147  

148  
        std::suspend_always initial_suspend() noexcept
148  
        std::suspend_always initial_suspend() noexcept
149  
        {
149  
        {
150  
            return {};
150  
            return {};
151  
        }
151  
        }
152  

152  

153  
        std::suspend_never final_suspend() noexcept
153  
        std::suspend_never final_suspend() noexcept
154  
        {
154  
        {
155  
            return {};
155  
            return {};
156  
        }
156  
        }
157  

157  

158  
        void return_void() noexcept
158  
        void return_void() noexcept
159  
        {
159  
        {
160  
        }
160  
        }
161  

161  

162  
        void unhandled_exception() noexcept
162  
        void unhandled_exception() noexcept
163  
        {
163  
        {
164  
        }
164  
        }
165  
    };
165  
    };
166  

166  

167  
    std::coroutine_handle<promise_type> h_;
167  
    std::coroutine_handle<promise_type> h_;
168  

168  

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

182  

183  
/** Specialization for memory_resource* - stores pointer directly.
183  
/** Specialization for memory_resource* - stores pointer directly.
184  

184  

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

191  

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

200  

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

208  

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

219  

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

226  

227  
        std::pmr::memory_resource* get_resource() noexcept
227  
        std::pmr::memory_resource* get_resource() noexcept
228  
        {
228  
        {
229  
            return mr_;
229  
            return mr_;
230  
        }
230  
        }
231  

231  

232  
        run_async_trampoline get_return_object() noexcept
232  
        run_async_trampoline get_return_object() noexcept
233  
        {
233  
        {
234  
            return run_async_trampoline{
234  
            return run_async_trampoline{
235  
                std::coroutine_handle<promise_type>::from_promise(*this)};
235  
                std::coroutine_handle<promise_type>::from_promise(*this)};
236  
        }
236  
        }
237  

237  

238  
        std::suspend_always initial_suspend() noexcept
238  
        std::suspend_always initial_suspend() noexcept
239  
        {
239  
        {
240  
            return {};
240  
            return {};
241  
        }
241  
        }
242  

242  

243  
        std::suspend_never final_suspend() noexcept
243  
        std::suspend_never final_suspend() noexcept
244  
        {
244  
        {
245  
            return {};
245  
            return {};
246  
        }
246  
        }
247  

247  

248  
        void return_void() noexcept
248  
        void return_void() noexcept
249  
        {
249  
        {
250  
        }
250  
        }
251  

251  

252  
        void unhandled_exception() noexcept
252  
        void unhandled_exception() noexcept
253  
        {
253  
        {
254  
        }
254  
        }
255  
    };
255  
    };
256  

256  

257  
    std::coroutine_handle<promise_type> h_;
257  
    std::coroutine_handle<promise_type> h_;
258  

258  

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

272  

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

285  

286  
} // namespace detail
286  
} // namespace detail
287  

287  

288  
//----------------------------------------------------------
288  
//----------------------------------------------------------
289  
//
289  
//
290  
// run_async_wrapper
290  
// run_async_wrapper
291  
//
291  
//
292  
//----------------------------------------------------------
292  
//----------------------------------------------------------
293  

293  

294  
/** Wrapper returned by run_async that accepts a task for execution.
294  
/** Wrapper returned by run_async that accepts a task for execution.
295  

295  

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

299  

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

302  

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

306  

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

310  

311  
    @par Example
311  
    @par Example
312  
    @code
312  
    @code
313  
    // Correct usage - wrapper is temporary
313  
    // Correct usage - wrapper is temporary
314  
    run_async(ex)(my_task());
314  
    run_async(ex)(my_task());
315  

315  

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

320  

321  
    @see run_async
321  
    @see run_async
322  
*/
322  
*/
323  
template<Executor Ex, class Handlers, class Alloc>
323  
template<Executor Ex, class Handlers, class Alloc>
324  
class [[nodiscard]] run_async_wrapper
324  
class [[nodiscard]] run_async_wrapper
325  
{
325  
{
326  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
326  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
327  
    std::stop_token st_;
327  
    std::stop_token st_;
328  

328  

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

349  

350  
    // Non-copyable, non-movable (must be used immediately)
350  
    // Non-copyable, non-movable (must be used immediately)
351  
    run_async_wrapper(run_async_wrapper const&) = delete;
351  
    run_async_wrapper(run_async_wrapper const&) = delete;
352  
    run_async_wrapper(run_async_wrapper&&) = delete;
352  
    run_async_wrapper(run_async_wrapper&&) = delete;
353  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
353  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
354  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
354  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
355  

355  

356  
    /** Launch the task for execution.
356  
    /** Launch the task for execution.
357  

357  

358  
        This operator accepts a task and launches it on the executor.
358  
        This operator accepts a task and launches it on the executor.
359  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
359  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
360  
        correct LIFO destruction order.
360  
        correct LIFO destruction order.
361  

361  

362  
        @tparam Task The IoLaunchableTask type.
362  
        @tparam Task The IoLaunchableTask type.
363  

363  

364  
        @param t The task to execute. Ownership is transferred to the
364  
        @param t The task to execute. Ownership is transferred to the
365  
                 run_async_trampoline which will destroy it after completion.
365  
                 run_async_trampoline which will destroy it after completion.
366  
    */
366  
    */
367  
    template<IoLaunchableTask Task>
367  
    template<IoLaunchableTask Task>
368  
    void operator()(Task t) &&
368  
    void operator()(Task t) &&
369  
    {
369  
    {
370  
        auto task_h = t.handle();
370  
        auto task_h = t.handle();
371  
        auto& task_promise = task_h.promise();
371  
        auto& task_promise = task_h.promise();
372  
        t.release();
372  
        t.release();
373  

373  

374  
        auto& p = tr_.h_.promise();
374  
        auto& p = tr_.h_.promise();
375  

375  

376  
        // Inject Task-specific invoke function
376  
        // Inject Task-specific invoke function
377  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
377  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
378  
        p.task_promise_ = &task_promise;
378  
        p.task_promise_ = &task_promise;
379  
        p.task_h_ = task_h;
379  
        p.task_h_ = task_h;
380  

380  

381  
        // Setup task's continuation to return to run_async_trampoline
381  
        // Setup task's continuation to return to run_async_trampoline
382  
        task_promise.set_continuation(tr_.h_, p.wg_.executor());
382  
        task_promise.set_continuation(tr_.h_, p.wg_.executor());
383  
        task_promise.set_executor(p.wg_.executor());
383  
        task_promise.set_executor(p.wg_.executor());
384  
        task_promise.set_stop_token(st_);
384  
        task_promise.set_stop_token(st_);
385  

385  

386  
        // Resume task through executor
386  
        // Resume task through executor
387  
        p.wg_.executor().dispatch(task_h);
387  
        p.wg_.executor().dispatch(task_h);
388  
    }
388  
    }
389  
};
389  
};
390  

390  

391  
//----------------------------------------------------------
391  
//----------------------------------------------------------
392  
//
392  
//
393  
// run_async Overloads
393  
// run_async Overloads
394  
//
394  
//
395  
//----------------------------------------------------------
395  
//----------------------------------------------------------
396  

396  

397  
// Executor only (uses default recycling allocator)
397  
// Executor only (uses default recycling allocator)
398  

398  

399  
/** Asynchronously launch a lazy task on the given executor.
399  
/** Asynchronously launch a lazy task on the given executor.
400  

400  

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

404  

405  
    Uses the default recycling frame allocator for coroutine frames.
405  
    Uses the default recycling frame allocator for coroutine frames.
406  
    With no handlers, the result is discarded and exceptions are rethrown.
406  
    With no handlers, the result is discarded and exceptions are rethrown.
407  

407  

408  
    @par Thread Safety
408  
    @par Thread Safety
409  
    The wrapper and handlers may be called from any thread where the
409  
    The wrapper and handlers may be called from any thread where the
410  
    executor schedules work.
410  
    executor schedules work.
411  

411  

412  
    @par Example
412  
    @par Example
413  
    @code
413  
    @code
414  
    run_async(ioc.get_executor())(my_task());
414  
    run_async(ioc.get_executor())(my_task());
415  
    @endcode
415  
    @endcode
416  

416  

417  
    @param ex The executor to execute the task on.
417  
    @param ex The executor to execute the task on.
418  

418  

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

420  

421  
    @see task
421  
    @see task
422  
    @see executor
422  
    @see executor
423  
*/
423  
*/
424  
template<Executor Ex>
424  
template<Executor Ex>
425  
[[nodiscard]] auto
425  
[[nodiscard]] auto
426  
run_async(Ex ex)
426  
run_async(Ex ex)
427  
{
427  
{
428  
    auto* mr = ex.context().get_frame_allocator();
428  
    auto* mr = ex.context().get_frame_allocator();
429  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
429  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
430  
        std::move(ex),
430  
        std::move(ex),
431  
        std::stop_token{},
431  
        std::stop_token{},
432  
        detail::default_handler{},
432  
        detail::default_handler{},
433  
        mr);
433  
        mr);
434  
}
434  
}
435  

435  

436  
/** Asynchronously launch a lazy task with a result handler.
436  
/** Asynchronously launch a lazy task with a result handler.
437  

437  

438  
    The handler `h1` is called with the task's result on success. If `h1`
438  
    The handler `h1` is called with the task's result on success. If `h1`
439  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
439  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
440  
    Otherwise, exceptions are rethrown.
440  
    Otherwise, exceptions are rethrown.
441  

441  

442  
    @par Thread Safety
442  
    @par Thread Safety
443  
    The handler may be called from any thread where the executor
443  
    The handler may be called from any thread where the executor
444  
    schedules work.
444  
    schedules work.
445  

445  

446  
    @par Example
446  
    @par Example
447  
    @code
447  
    @code
448  
    // Handler for result only (exceptions rethrown)
448  
    // Handler for result only (exceptions rethrown)
449  
    run_async(ex, [](int result) {
449  
    run_async(ex, [](int result) {
450  
        std::cout << "Got: " << result << "\n";
450  
        std::cout << "Got: " << result << "\n";
451  
    })(compute_value());
451  
    })(compute_value());
452  

452  

453  
    // Overloaded handler for both result and exception
453  
    // Overloaded handler for both result and exception
454  
    run_async(ex, overloaded{
454  
    run_async(ex, overloaded{
455  
        [](int result) { std::cout << "Got: " << result << "\n"; },
455  
        [](int result) { std::cout << "Got: " << result << "\n"; },
456  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
456  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
457  
    })(compute_value());
457  
    })(compute_value());
458  
    @endcode
458  
    @endcode
459  

459  

460  
    @param ex The executor to execute the task on.
460  
    @param ex The executor to execute the task on.
461  
    @param h1 The handler to invoke with the result (and optionally exception).
461  
    @param h1 The handler to invoke with the result (and optionally exception).
462  

462  

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

464  

465  
    @see task
465  
    @see task
466  
    @see executor
466  
    @see executor
467  
*/
467  
*/
468  
template<Executor Ex, class H1>
468  
template<Executor Ex, class H1>
469  
[[nodiscard]] auto
469  
[[nodiscard]] auto
470  
run_async(Ex ex, H1 h1)
470  
run_async(Ex ex, H1 h1)
471  
{
471  
{
472  
    auto* mr = ex.context().get_frame_allocator();
472  
    auto* mr = ex.context().get_frame_allocator();
473  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
473  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
474  
        std::move(ex),
474  
        std::move(ex),
475  
        std::stop_token{},
475  
        std::stop_token{},
476  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
476  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
477  
        mr);
477  
        mr);
478  
}
478  
}
479  

479  

480  
/** Asynchronously launch a lazy task with separate result and error handlers.
480  
/** Asynchronously launch a lazy task with separate result and error handlers.
481  

481  

482  
    The handler `h1` is called with the task's result on success.
482  
    The handler `h1` is called with the task's result on success.
483  
    The handler `h2` is called with the exception_ptr on failure.
483  
    The handler `h2` is called with the exception_ptr on failure.
484  

484  

485  
    @par Thread Safety
485  
    @par Thread Safety
486  
    The handlers may be called from any thread where the executor
486  
    The handlers may be called from any thread where the executor
487  
    schedules work.
487  
    schedules work.
488  

488  

489  
    @par Example
489  
    @par Example
490  
    @code
490  
    @code
491  
    run_async(ex,
491  
    run_async(ex,
492  
        [](int result) { std::cout << "Got: " << result << "\n"; },
492  
        [](int result) { std::cout << "Got: " << result << "\n"; },
493  
        [](std::exception_ptr ep) {
493  
        [](std::exception_ptr ep) {
494  
            try { std::rethrow_exception(ep); }
494  
            try { std::rethrow_exception(ep); }
495  
            catch (std::exception const& e) {
495  
            catch (std::exception const& e) {
496  
                std::cout << "Error: " << e.what() << "\n";
496  
                std::cout << "Error: " << e.what() << "\n";
497  
            }
497  
            }
498  
        }
498  
        }
499  
    )(compute_value());
499  
    )(compute_value());
500  
    @endcode
500  
    @endcode
501  

501  

502  
    @param ex The executor to execute the task on.
502  
    @param ex The executor to execute the task on.
503  
    @param h1 The handler to invoke with the result on success.
503  
    @param h1 The handler to invoke with the result on success.
504  
    @param h2 The handler to invoke with the exception on failure.
504  
    @param h2 The handler to invoke with the exception on failure.
505  

505  

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

507  

508  
    @see task
508  
    @see task
509  
    @see executor
509  
    @see executor
510  
*/
510  
*/
511  
template<Executor Ex, class H1, class H2>
511  
template<Executor Ex, class H1, class H2>
512  
[[nodiscard]] auto
512  
[[nodiscard]] auto
513  
run_async(Ex ex, H1 h1, H2 h2)
513  
run_async(Ex ex, H1 h1, H2 h2)
514  
{
514  
{
515  
    auto* mr = ex.context().get_frame_allocator();
515  
    auto* mr = ex.context().get_frame_allocator();
516  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
516  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
517  
        std::move(ex),
517  
        std::move(ex),
518  
        std::stop_token{},
518  
        std::stop_token{},
519  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
519  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
520  
        mr);
520  
        mr);
521  
}
521  
}
522  

522  

523  
// Ex + stop_token
523  
// Ex + stop_token
524  

524  

525  
/** Asynchronously launch a lazy task with stop token support.
525  
/** Asynchronously launch a lazy task with stop token support.
526  

526  

527  
    The stop token is propagated to the task, enabling cooperative
527  
    The stop token is propagated to the task, enabling cooperative
528  
    cancellation. With no handlers, the result is discarded and
528  
    cancellation. With no handlers, the result is discarded and
529  
    exceptions are rethrown.
529  
    exceptions are rethrown.
530  

530  

531  
    @par Thread Safety
531  
    @par Thread Safety
532  
    The wrapper may be called from any thread where the executor
532  
    The wrapper may be called from any thread where the executor
533  
    schedules work.
533  
    schedules work.
534  

534  

535  
    @par Example
535  
    @par Example
536  
    @code
536  
    @code
537  
    std::stop_source source;
537  
    std::stop_source source;
538  
    run_async(ex, source.get_token())(cancellable_task());
538  
    run_async(ex, source.get_token())(cancellable_task());
539  
    // Later: source.request_stop();
539  
    // Later: source.request_stop();
540  
    @endcode
540  
    @endcode
541  

541  

542  
    @param ex The executor to execute the task on.
542  
    @param ex The executor to execute the task on.
543  
    @param st The stop token for cooperative cancellation.
543  
    @param st The stop token for cooperative cancellation.
544  

544  

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

546  

547  
    @see task
547  
    @see task
548  
    @see executor
548  
    @see executor
549  
*/
549  
*/
550  
template<Executor Ex>
550  
template<Executor Ex>
551  
[[nodiscard]] auto
551  
[[nodiscard]] auto
552  
run_async(Ex ex, std::stop_token st)
552  
run_async(Ex ex, std::stop_token st)
553  
{
553  
{
554  
    auto* mr = ex.context().get_frame_allocator();
554  
    auto* mr = ex.context().get_frame_allocator();
555  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
555  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
556  
        std::move(ex),
556  
        std::move(ex),
557  
        std::move(st),
557  
        std::move(st),
558  
        detail::default_handler{},
558  
        detail::default_handler{},
559  
        mr);
559  
        mr);
560  
}
560  
}
561  

561  

562  
/** Asynchronously launch a lazy task with stop token and result handler.
562  
/** Asynchronously launch a lazy task with stop token and result handler.
563  

563  

564  
    The stop token is propagated to the task for cooperative cancellation.
564  
    The stop token is propagated to the task for cooperative cancellation.
565  
    The handler `h1` is called with the result on success, and optionally
565  
    The handler `h1` is called with the result on success, and optionally
566  
    with exception_ptr if it accepts that type.
566  
    with exception_ptr if it accepts that type.
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  
    @param h1 The handler to invoke with the result (and optionally exception).
570  
    @param h1 The handler to invoke with the result (and optionally exception).
571  

571  

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

573  

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

588  

589  
/** Asynchronously launch a lazy task with stop token and separate handlers.
589  
/** Asynchronously launch a lazy task with stop token and separate handlers.
590  

590  

591  
    The stop token is propagated to the task for cooperative cancellation.
591  
    The stop token is propagated to the task for cooperative cancellation.
592  
    The handler `h1` is called on success, `h2` on failure.
592  
    The handler `h1` is called on success, `h2` on failure.
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 on success.
596  
    @param h1 The handler to invoke with the result on success.
597  
    @param h2 The handler to invoke with the exception on failure.
597  
    @param h2 The handler to invoke with the exception on failure.
598  

598  

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

600  

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

615  

616  
// Ex + memory_resource*
616  
// Ex + memory_resource*
617  

617  

618  
/** Asynchronously launch a lazy task with custom memory resource.
618  
/** Asynchronously launch a lazy task with custom memory resource.
619  

619  

620  
    The memory resource is used for coroutine frame allocation. The caller
620  
    The memory resource is used for coroutine frame allocation. The caller
621  
    is responsible for ensuring the memory resource outlives all tasks.
621  
    is responsible for ensuring the memory resource outlives all tasks.
622  

622  

623  
    @param ex The executor to execute the task on.
623  
    @param ex The executor to execute the task on.
624  
    @param mr The memory resource for frame allocation.
624  
    @param mr The memory resource for frame allocation.
625  

625  

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

627  

628  
    @see task
628  
    @see task
629  
    @see executor
629  
    @see executor
630  
*/
630  
*/
631  
template<Executor Ex>
631  
template<Executor Ex>
632  
[[nodiscard]] auto
632  
[[nodiscard]] auto
633  
run_async(Ex ex, std::pmr::memory_resource* mr)
633  
run_async(Ex ex, std::pmr::memory_resource* mr)
634  
{
634  
{
635  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
635  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
636  
        std::move(ex),
636  
        std::move(ex),
637  
        std::stop_token{},
637  
        std::stop_token{},
638  
        detail::default_handler{},
638  
        detail::default_handler{},
639  
        mr);
639  
        mr);
640  
}
640  
}
641  

641  

642  
/** Asynchronously launch a lazy task with memory resource and handler.
642  
/** Asynchronously launch a lazy task with memory resource and handler.
643  

643  

644  
    @param ex The executor to execute the task on.
644  
    @param ex The executor to execute the task on.
645  
    @param mr The memory resource for frame allocation.
645  
    @param mr The memory resource for frame allocation.
646  
    @param h1 The handler to invoke with the result (and optionally exception).
646  
    @param h1 The handler to invoke with the result (and optionally exception).
647  

647  

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

649  

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

663  

664  
/** Asynchronously launch a lazy task with memory resource and handlers.
664  
/** Asynchronously launch a lazy task with memory resource and handlers.
665  

665  

666  
    @param ex The executor to execute the task on.
666  
    @param ex The executor to execute the task on.
667  
    @param mr The memory resource for frame allocation.
667  
    @param mr The memory resource for frame allocation.
668  
    @param h1 The handler to invoke with the result on success.
668  
    @param h1 The handler to invoke with the result on success.
669  
    @param h2 The handler to invoke with the exception on failure.
669  
    @param h2 The handler to invoke with the exception on failure.
670  

670  

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

672  

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

686  

687  
// Ex + stop_token + memory_resource*
687  
// Ex + stop_token + memory_resource*
688  

688  

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

690  

691  
    @param ex The executor to execute the task on.
691  
    @param ex The executor to execute the task on.
692  
    @param st The stop token for cooperative cancellation.
692  
    @param st The stop token for cooperative cancellation.
693  
    @param mr The memory resource for frame allocation.
693  
    @param mr The memory resource for frame allocation.
694  

694  

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

696  

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

710  

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

712  

713  
    @param ex The executor to execute the task on.
713  
    @param ex The executor to execute the task on.
714  
    @param st The stop token for cooperative cancellation.
714  
    @param st The stop token for cooperative cancellation.
715  
    @param mr The memory resource for frame allocation.
715  
    @param mr The memory resource for frame allocation.
716  
    @param h1 The handler to invoke with the result (and optionally exception).
716  
    @param h1 The handler to invoke with the result (and optionally exception).
717  

717  

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

719  

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

733  

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

735  

736  
    @param ex The executor to execute the task on.
736  
    @param ex The executor to execute the task on.
737  
    @param st The stop token for cooperative cancellation.
737  
    @param st The stop token for cooperative cancellation.
738  
    @param mr The memory resource for frame allocation.
738  
    @param mr The memory resource for frame allocation.
739  
    @param h1 The handler to invoke with the result on success.
739  
    @param h1 The handler to invoke with the result on success.
740  
    @param h2 The handler to invoke with the exception on failure.
740  
    @param h2 The handler to invoke with the exception on failure.
741  

741  

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

743  

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

757  

758  
// Ex + standard Allocator (value type)
758  
// Ex + standard Allocator (value type)
759  

759  

760  
/** Asynchronously launch a lazy task with custom allocator.
760  
/** Asynchronously launch a lazy task with custom allocator.
761  

761  

762  
    The allocator is wrapped in a frame_memory_resource and stored in the
762  
    The allocator is wrapped in a frame_memory_resource and stored in the
763  
    run_async_trampoline, ensuring it outlives all coroutine frames.
763  
    run_async_trampoline, ensuring it outlives all coroutine frames.
764  

764  

765  
    @param ex The executor to execute the task on.
765  
    @param ex The executor to execute the task on.
766  
    @param alloc The allocator for frame allocation (copied and stored).
766  
    @param alloc The allocator for frame allocation (copied and stored).
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, detail::Allocator Alloc>
773  
template<Executor Ex, detail::Allocator Alloc>
774  
[[nodiscard]] auto
774  
[[nodiscard]] auto
775  
run_async(Ex ex, Alloc alloc)
775  
run_async(Ex ex, Alloc alloc)
776  
{
776  
{
777  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
777  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
778  
        std::move(ex),
778  
        std::move(ex),
779  
        std::stop_token{},
779  
        std::stop_token{},
780  
        detail::default_handler{},
780  
        detail::default_handler{},
781  
        std::move(alloc));
781  
        std::move(alloc));
782  
}
782  
}
783  

783  

784  
/** Asynchronously launch a lazy task with allocator and handler.
784  
/** Asynchronously launch a lazy task with allocator and handler.
785  

785  

786  
    @param ex The executor to execute the task on.
786  
    @param ex The executor to execute the task on.
787  
    @param alloc The allocator for frame allocation (copied and stored).
787  
    @param alloc The allocator for frame allocation (copied and stored).
788  
    @param h1 The handler to invoke with the result (and optionally exception).
788  
    @param h1 The handler to invoke with the result (and optionally exception).
789  

789  

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

791  

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

805  

806  
/** Asynchronously launch a lazy task with allocator and handlers.
806  
/** Asynchronously launch a lazy task with allocator and handlers.
807  

807  

808  
    @param ex The executor to execute the task on.
808  
    @param ex The executor to execute the task on.
809  
    @param alloc The allocator for frame allocation (copied and stored).
809  
    @param alloc The allocator for frame allocation (copied and stored).
810  
    @param h1 The handler to invoke with the result on success.
810  
    @param h1 The handler to invoke with the result on success.
811  
    @param h2 The handler to invoke with the exception on failure.
811  
    @param h2 The handler to invoke with the exception on failure.
812  

812  

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

814  

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

828  

829  
// Ex + stop_token + standard Allocator
829  
// Ex + stop_token + standard Allocator
830  

830  

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

832  

833  
    @param ex The executor to execute the task on.
833  
    @param ex The executor to execute the task on.
834  
    @param st The stop token for cooperative cancellation.
834  
    @param st The stop token for cooperative cancellation.
835  
    @param alloc The allocator for frame allocation (copied and stored).
835  
    @param alloc The allocator for frame allocation (copied and stored).
836  

836  

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

838  

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

852  

853  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
853  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
854  

854  

855  
    @param ex The executor to execute the task on.
855  
    @param ex The executor to execute the task on.
856  
    @param st The stop token for cooperative cancellation.
856  
    @param st The stop token for cooperative cancellation.
857  
    @param alloc The allocator for frame allocation (copied and stored).
857  
    @param alloc The allocator for frame allocation (copied and stored).
858  
    @param h1 The handler to invoke with the result (and optionally exception).
858  
    @param h1 The handler to invoke with the result (and optionally exception).
859  

859  

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

861  

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

875  

876  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
876  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
877  

877  

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

883  

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

885  

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

899  

900  
} // namespace capy
900  
} // namespace capy
901  
} // namespace boost
901  
} // namespace boost
902  

902  

903  
#endif
903  
#endif