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  
        std::coroutine_handle<> task_h_;
97  
        std::coroutine_handle<> task_h_;
98  

98  

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

105  

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

111  

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

116  

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

119  

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

123  

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

125  

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

128  

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

135  

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

140  

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

145  

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

151  

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

156  

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

161  

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

165  

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

170  

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

172  

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

186  

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

188  

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

195  

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

205  

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

213  

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

223  

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

231  

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

236  

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

242  

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

247  

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

252  

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

256  

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

261  

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

263  

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

277  

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

290  

291  
} // namespace detail
291  
} // namespace detail
292 -
//----------------------------------------------------------
 
293 -
//
 
294 -
// run_async_wrapper
 
295 -
//
 
296 -
//----------------------------------------------------------
 
297 -

 
298  

292  

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

294  

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

298  

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

301  

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

305  

312  
    @par Thread Safety
306  
    @par Thread Safety
313  
    The wrapper itself should only be used from one thread. The handlers
307  
    The wrapper itself should only be used from one thread. The handlers
314  
    may be invoked from any thread where the executor schedules work.
308  
    may be invoked from any thread where the executor schedules work.
315  

309  

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

314  

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

319  

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

328  

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

350  

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

357  

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

363  

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

365  

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

369  

376  
        The `io_env` constructed for the task is owned by the trampoline
370  
        The `io_env` constructed for the task is owned by the trampoline
377  
        coroutine and is guaranteed to outlive the task and all awaitables
371  
        coroutine and is guaranteed to outlive the task and all awaitables
378  
        in its chain. Awaitables may store `io_env const*` without concern
372  
        in its chain. Awaitables may store `io_env const*` without concern
379  
        for dangling references.
373  
        for dangling references.
380  

374  

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

376  

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

386  

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

388  

395  
        // Inject Task-specific invoke function
389  
        // Inject Task-specific invoke function
396  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
390  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
397  
        p.task_promise_ = &task_promise;
391  
        p.task_promise_ = &task_promise;
398  
        p.task_h_ = task_h;
392  
        p.task_h_ = task_h;
399  

393  

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

398  

405  
        // Start task through executor
399  
        // Start task through executor
406  
        p.wg_.executor().dispatch(task_h).resume();
400  
        p.wg_.executor().dispatch(task_h).resume();
407  
    }
401  
    }
408 -

 
409 -
//----------------------------------------------------------
 
410 -
//
 
411 -
// run_async Overloads
 
412 -
//
 
413 -
//----------------------------------------------------------
 
414  
};
402  
};
415  

403  

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

405  

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

407  

420  
    Use this to start execution of a `task<T>` that was created lazily.
408  
    Use this to start execution of a `task<T>` that was created lazily.
421  
    The returned wrapper must be immediately invoked with the task;
409  
    The returned wrapper must be immediately invoked with the task;
422  
    storing the wrapper and calling it later violates LIFO ordering.
410  
    storing the wrapper and calling it later violates LIFO ordering.
423  

411  

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

414  

427  
    @par Thread Safety
415  
    @par Thread Safety
428  
    The wrapper and handlers may be called from any thread where the
416  
    The wrapper and handlers may be called from any thread where the
429  
    executor schedules work.
417  
    executor schedules work.
430  

418  

431  
    @par Example
419  
    @par Example
432  
    @code
420  
    @code
433  
    run_async(ioc.get_executor())(my_task());
421  
    run_async(ioc.get_executor())(my_task());
434  
    @endcode
422  
    @endcode
435  

423  

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

425  

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

427  

440  
    @see task
428  
    @see task
441  
    @see executor
429  
    @see executor
442  
*/
430  
*/
443  
template<Executor Ex>
431  
template<Executor Ex>
444  
[[nodiscard]] auto
432  
[[nodiscard]] auto
445  
run_async(Ex ex)
433  
run_async(Ex ex)
446  
{
434  
{
447  
    auto* mr = ex.context().get_frame_allocator();
435  
    auto* mr = ex.context().get_frame_allocator();
448  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
436  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
449  
        std::move(ex),
437  
        std::move(ex),
450  
        std::stop_token{},
438  
        std::stop_token{},
451  
        detail::default_handler{},
439  
        detail::default_handler{},
452  
        mr);
440  
        mr);
453  
}
441  
}
454  

442  

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

444  

457  
    The handler `h1` is called with the task's result on success. If `h1`
445  
    The handler `h1` is called with the task's result on success. If `h1`
458  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
446  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
459  
    Otherwise, exceptions are rethrown.
447  
    Otherwise, exceptions are rethrown.
460  

448  

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

452  

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

459  

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

466  

479  
    @param ex The executor to execute the task on.
467  
    @param ex The executor to execute the task on.
480  
    @param h1 The handler to invoke with the result (and optionally exception).
468  
    @param h1 The handler to invoke with the result (and optionally exception).
481  

469  

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

471  

484  
    @see task
472  
    @see task
485  
    @see executor
473  
    @see executor
486  
*/
474  
*/
487  
template<Executor Ex, class H1>
475  
template<Executor Ex, class H1>
488  
[[nodiscard]] auto
476  
[[nodiscard]] auto
489  
run_async(Ex ex, H1 h1)
477  
run_async(Ex ex, H1 h1)
490  
{
478  
{
491  
    auto* mr = ex.context().get_frame_allocator();
479  
    auto* mr = ex.context().get_frame_allocator();
492  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
480  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
493  
        std::move(ex),
481  
        std::move(ex),
494  
        std::stop_token{},
482  
        std::stop_token{},
495  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
483  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
496  
        mr);
484  
        mr);
497  
}
485  
}
498  

486  

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

488  

501  
    The handler `h1` is called with the task's result on success.
489  
    The handler `h1` is called with the task's result on success.
502  
    The handler `h2` is called with the exception_ptr on failure.
490  
    The handler `h2` is called with the exception_ptr on failure.
503  

491  

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

495  

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

508  

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

512  

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

514  

527  
    @see task
515  
    @see task
528  
    @see executor
516  
    @see executor
529  
*/
517  
*/
530  
template<Executor Ex, class H1, class H2>
518  
template<Executor Ex, class H1, class H2>
531  
[[nodiscard]] auto
519  
[[nodiscard]] auto
532  
run_async(Ex ex, H1 h1, H2 h2)
520  
run_async(Ex ex, H1 h1, H2 h2)
533  
{
521  
{
534  
    auto* mr = ex.context().get_frame_allocator();
522  
    auto* mr = ex.context().get_frame_allocator();
535  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
523  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
536  
        std::move(ex),
524  
        std::move(ex),
537  
        std::stop_token{},
525  
        std::stop_token{},
538  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
526  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
539  
        mr);
527  
        mr);
540  
}
528  
}
541  

529  

542  
// Ex + stop_token
530  
// Ex + stop_token
543  

531  

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

533  

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

537  

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

541  

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

548  

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

551  

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

553  

566  
    @see task
554  
    @see task
567  
    @see executor
555  
    @see executor
568  
*/
556  
*/
569  
template<Executor Ex>
557  
template<Executor Ex>
570  
[[nodiscard]] auto
558  
[[nodiscard]] auto
571  
run_async(Ex ex, std::stop_token st)
559  
run_async(Ex ex, std::stop_token st)
572  
{
560  
{
573  
    auto* mr = ex.context().get_frame_allocator();
561  
    auto* mr = ex.context().get_frame_allocator();
574  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
562  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
575  
        std::move(ex),
563  
        std::move(ex),
576  
        std::move(st),
564  
        std::move(st),
577  
        detail::default_handler{},
565  
        detail::default_handler{},
578  
        mr);
566  
        mr);
579  
}
567  
}
580  

568  

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

570  

583  
    The stop token is propagated to the task for cooperative cancellation.
571  
    The stop token is propagated to the task for cooperative cancellation.
584  
    The handler `h1` is called with the result on success, and optionally
572  
    The handler `h1` is called with the result on success, and optionally
585  
    with exception_ptr if it accepts that type.
573  
    with exception_ptr if it accepts that type.
586  

574  

587  
    @param ex The executor to execute the task on.
575  
    @param ex The executor to execute the task on.
588  
    @param st The stop token for cooperative cancellation.
576  
    @param st The stop token for cooperative cancellation.
589  
    @param h1 The handler to invoke with the result (and optionally exception).
577  
    @param h1 The handler to invoke with the result (and optionally exception).
590  

578  

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

580  

593  
    @see task
581  
    @see task
594  
    @see executor
582  
    @see executor
595  
*/
583  
*/
596  
template<Executor Ex, class H1>
584  
template<Executor Ex, class H1>
597  
[[nodiscard]] auto
585  
[[nodiscard]] auto
598  
run_async(Ex ex, std::stop_token st, H1 h1)
586  
run_async(Ex ex, std::stop_token st, H1 h1)
599  
{
587  
{
600  
    auto* mr = ex.context().get_frame_allocator();
588  
    auto* mr = ex.context().get_frame_allocator();
601  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
589  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
602  
        std::move(ex),
590  
        std::move(ex),
603  
        std::move(st),
591  
        std::move(st),
604  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
592  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
605  
        mr);
593  
        mr);
606  
}
594  
}
607  

595  

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

597  

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

600  

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

605  

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

607  

620  
    @see task
608  
    @see task
621  
    @see executor
609  
    @see executor
622  
*/
610  
*/
623  
template<Executor Ex, class H1, class H2>
611  
template<Executor Ex, class H1, class H2>
624  
[[nodiscard]] auto
612  
[[nodiscard]] auto
625  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
613  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
626  
{
614  
{
627  
    auto* mr = ex.context().get_frame_allocator();
615  
    auto* mr = ex.context().get_frame_allocator();
628  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
616  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
629  
        std::move(ex),
617  
        std::move(ex),
630  
        std::move(st),
618  
        std::move(st),
631  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
619  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
632  
        mr);
620  
        mr);
633  
}
621  
}
634  

622  

635  
// Ex + memory_resource*
623  
// Ex + memory_resource*
636  

624  

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

626  

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

629  

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

632  

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

634  

647  
    @see task
635  
    @see task
648  
    @see executor
636  
    @see executor
649  
*/
637  
*/
650  
template<Executor Ex>
638  
template<Executor Ex>
651  
[[nodiscard]] auto
639  
[[nodiscard]] auto
652  
run_async(Ex ex, std::pmr::memory_resource* mr)
640  
run_async(Ex ex, std::pmr::memory_resource* mr)
653  
{
641  
{
654  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
642  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
655  
        std::move(ex),
643  
        std::move(ex),
656  
        std::stop_token{},
644  
        std::stop_token{},
657  
        detail::default_handler{},
645  
        detail::default_handler{},
658  
        mr);
646  
        mr);
659  
}
647  
}
660  

648  

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

650  

663  
    @param ex The executor to execute the task on.
651  
    @param ex The executor to execute the task on.
664  
    @param mr The memory resource for frame allocation.
652  
    @param mr The memory resource for frame allocation.
665  
    @param h1 The handler to invoke with the result (and optionally exception).
653  
    @param h1 The handler to invoke with the result (and optionally exception).
666  

654  

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

656  

669  
    @see task
657  
    @see task
670  
    @see executor
658  
    @see executor
671  
*/
659  
*/
672  
template<Executor Ex, class H1>
660  
template<Executor Ex, class H1>
673  
[[nodiscard]] auto
661  
[[nodiscard]] auto
674  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
662  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
675  
{
663  
{
676  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
664  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
677  
        std::move(ex),
665  
        std::move(ex),
678  
        std::stop_token{},
666  
        std::stop_token{},
679  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
667  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
680  
        mr);
668  
        mr);
681  
}
669  
}
682  

670  

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

672  

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

677  

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

679  

692  
    @see task
680  
    @see task
693  
    @see executor
681  
    @see executor
694  
*/
682  
*/
695  
template<Executor Ex, class H1, class H2>
683  
template<Executor Ex, class H1, class H2>
696  
[[nodiscard]] auto
684  
[[nodiscard]] auto
697  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
685  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
698  
{
686  
{
699  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
687  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
700  
        std::move(ex),
688  
        std::move(ex),
701  
        std::stop_token{},
689  
        std::stop_token{},
702  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
690  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
703  
        mr);
691  
        mr);
704  
}
692  
}
705  

693  

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

695  

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

697  

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

701  

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

703  

716  
    @see task
704  
    @see task
717  
    @see executor
705  
    @see executor
718  
*/
706  
*/
719  
template<Executor Ex>
707  
template<Executor Ex>
720  
[[nodiscard]] auto
708  
[[nodiscard]] auto
721  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
709  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
722  
{
710  
{
723  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
711  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
724  
        std::move(ex),
712  
        std::move(ex),
725  
        std::move(st),
713  
        std::move(st),
726  
        detail::default_handler{},
714  
        detail::default_handler{},
727  
        mr);
715  
        mr);
728  
}
716  
}
729  

717  

730  
/** Asynchronously launch a lazy task with stop token, memory resource, and handler.
718  
/** Asynchronously launch a lazy task with stop token, memory resource, and handler.
731  

719  

732  
    @param ex The executor to execute the task on.
720  
    @param ex The executor to execute the task on.
733  
    @param st The stop token for cooperative cancellation.
721  
    @param st The stop token for cooperative cancellation.
734  
    @param mr The memory resource for frame allocation.
722  
    @param mr The memory resource for frame allocation.
735  
    @param h1 The handler to invoke with the result (and optionally exception).
723  
    @param h1 The handler to invoke with the result (and optionally exception).
736  

724  

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

726  

739  
    @see task
727  
    @see task
740  
    @see executor
728  
    @see executor
741  
*/
729  
*/
742  
template<Executor Ex, class H1>
730  
template<Executor Ex, class H1>
743  
[[nodiscard]] auto
731  
[[nodiscard]] auto
744  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
732  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
745  
{
733  
{
746  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
734  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
747  
        std::move(ex),
735  
        std::move(ex),
748  
        std::move(st),
736  
        std::move(st),
749  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
737  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
750  
        mr);
738  
        mr);
751  
}
739  
}
752  

740  

753  
/** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
741  
/** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
754  

742  

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

748  

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

750  

763  
    @see task
751  
    @see task
764  
    @see executor
752  
    @see executor
765  
*/
753  
*/
766  
template<Executor Ex, class H1, class H2>
754  
template<Executor Ex, class H1, class H2>
767  
[[nodiscard]] auto
755  
[[nodiscard]] auto
768  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
756  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
769  
{
757  
{
770  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
758  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
771  
        std::move(ex),
759  
        std::move(ex),
772  
        std::move(st),
760  
        std::move(st),
773  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
761  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
774  
        mr);
762  
        mr);
775  
}
763  
}
776  

764  

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

766  

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

768  

781  
    The allocator is wrapped in a frame_memory_resource and stored in the
769  
    The allocator is wrapped in a frame_memory_resource and stored in the
782  
    run_async_trampoline, ensuring it outlives all coroutine frames.
770  
    run_async_trampoline, ensuring it outlives all coroutine frames.
783  

771  

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

774  

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

776  

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

790  

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

792  

805  
    @param ex The executor to execute the task on.
793  
    @param ex The executor to execute the task on.
806  
    @param alloc The allocator for frame allocation (copied and stored).
794  
    @param alloc The allocator for frame allocation (copied and stored).
807  
    @param h1 The handler to invoke with the result (and optionally exception).
795  
    @param h1 The handler to invoke with the result (and optionally exception).
808  

796  

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

798  

811  
    @see task
799  
    @see task
812  
    @see executor
800  
    @see executor
813  
*/
801  
*/
814  
template<Executor Ex, detail::Allocator Alloc, class H1>
802  
template<Executor Ex, detail::Allocator Alloc, class H1>
815  
[[nodiscard]] auto
803  
[[nodiscard]] auto
816  
run_async(Ex ex, Alloc alloc, H1 h1)
804  
run_async(Ex ex, Alloc alloc, H1 h1)
817  
{
805  
{
818  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
806  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
819  
        std::move(ex),
807  
        std::move(ex),
820  
        std::stop_token{},
808  
        std::stop_token{},
821  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
809  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
822  
        std::move(alloc));
810  
        std::move(alloc));
823  
}
811  
}
824  

812  

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

814  

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

819  

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

821  

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

835  

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

837  

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

839  

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

843  

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

845  

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

859  

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

861  

874  
    @param ex The executor to execute the task on.
862  
    @param ex The executor to execute the task on.
875  
    @param st The stop token for cooperative cancellation.
863  
    @param st The stop token for cooperative cancellation.
876  
    @param alloc The allocator for frame allocation (copied and stored).
864  
    @param alloc The allocator for frame allocation (copied and stored).
877  
    @param h1 The handler to invoke with the result (and optionally exception).
865  
    @param h1 The handler to invoke with the result (and optionally exception).
878  

866  

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

868  

881  
    @see task
869  
    @see task
882  
    @see executor
870  
    @see executor
883  
*/
871  
*/
884  
template<Executor Ex, detail::Allocator Alloc, class H1>
872  
template<Executor Ex, detail::Allocator Alloc, class H1>
885  
[[nodiscard]] auto
873  
[[nodiscard]] auto
886  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
874  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
887  
{
875  
{
888  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
876  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
889  
        std::move(ex),
877  
        std::move(ex),
890  
        std::move(st),
878  
        std::move(st),
891  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
879  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
892  
        std::move(alloc));
880  
        std::move(alloc));
893  
}
881  
}
894  

882  

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

884  

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

890  

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

892  

905  
    @see task
893  
    @see task
906  
    @see executor
894  
    @see executor
907  
*/
895  
*/
908  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
896  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
909  
[[nodiscard]] auto
897  
[[nodiscard]] auto
910  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
898  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
911  
{
899  
{
912  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
900  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
913  
        std::move(ex),
901  
        std::move(ex),
914  
        std::move(st),
902  
        std::move(st),
915  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
903  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
916  
        std::move(alloc));
904  
        std::move(alloc));
917  
}
905  
}
918  

906  

919  
} // namespace capy
907  
} // namespace capy
920  
} // namespace boost
908  
} // namespace boost
921  

909  

922  
#endif
910  
#endif