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 <coroutine>
24  
#include <coroutine>
25  
#include <memory_resource>
25  
#include <memory_resource>
26  
#include <new>
26  
#include <new>
27  
#include <stop_token>
27  
#include <stop_token>
28  
#include <type_traits>
28  
#include <type_traits>
29  

29  

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

33  

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

36  

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

48  

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

54  

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

56  

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

62  

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

68  

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

70  

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

74  

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

77  

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

86  

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

96  

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

103  

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

109  

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

114  

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

117  

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

121  

122  
            new (fn_loc + 1) byte_alloc(std::move(ba));
122  
            new (fn_loc + 1) byte_alloc(std::move(ba));
123  

123  

124  
            return raw;
124  
            return raw;
125  
        }
125  
        }
126  

126  

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

133  

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

138  

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

143  

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

149  

150  
        std::suspend_always initial_suspend() noexcept
150  
        std::suspend_always initial_suspend() noexcept
151  
        {
151  
        {
152  
            return {};
152  
            return {};
153  
        }
153  
        }
154  

154  

155  
        std::suspend_never final_suspend() noexcept
155  
        std::suspend_never final_suspend() noexcept
156  
        {
156  
        {
157  
            return {};
157  
            return {};
158  
        }
158  
        }
159  

159  

160  
        void return_void() noexcept
160  
        void return_void() noexcept
161  
        {
161  
        {
162  
        }
162  
        }
163  

163  

164  
        void unhandled_exception() noexcept
164  
        void unhandled_exception() noexcept
165  
        {
165  
        {
166  
        }
166  
        }
167  
    };
167  
    };
168  

168  

169  
    std::coroutine_handle<promise_type> h_;
169  
    std::coroutine_handle<promise_type> h_;
170  

170  

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

184  

185  
/** Specialization for memory_resource* - stores pointer directly.
185  
/** Specialization for memory_resource* - stores pointer directly.
186  

186  

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

193  

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

203  

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

211  

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

222  

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

229  

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

234  

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

240  

241  
        std::suspend_always initial_suspend() noexcept
241  
        std::suspend_always initial_suspend() noexcept
242  
        {
242  
        {
243  
            return {};
243  
            return {};
244  
        }
244  
        }
245  

245  

246  
        std::suspend_never final_suspend() noexcept
246  
        std::suspend_never final_suspend() noexcept
247  
        {
247  
        {
248  
            return {};
248  
            return {};
249  
        }
249  
        }
250  

250  

251  
        void return_void() noexcept
251  
        void return_void() noexcept
252  
        {
252  
        {
253  
        }
253  
        }
254  

254  

255  
        void unhandled_exception() noexcept
255  
        void unhandled_exception() noexcept
256  
        {
256  
        {
257  
        }
257  
        }
258  
    };
258  
    };
259  

259  

260  
    std::coroutine_handle<promise_type> h_;
260  
    std::coroutine_handle<promise_type> h_;
261  

261  

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

275  

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

288  

289  
} // namespace detail
289  
} // namespace detail
290  

290  

291  
//----------------------------------------------------------
291  
//----------------------------------------------------------
292  
//
292  
//
293  
// run_async_wrapper
293  
// run_async_wrapper
294  
//
294  
//
295  
//----------------------------------------------------------
295  
//----------------------------------------------------------
296  

296  

297  
/** Wrapper returned by run_async that accepts a task for execution.
297  
/** Wrapper returned by run_async that accepts a task for execution.
298  

298  

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

302  

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

305  

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

309  

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

313  

314  
    @par Example
314  
    @par Example
315  
    @code
315  
    @code
316  
    // Correct usage - wrapper is temporary
316  
    // Correct usage - wrapper is temporary
317  
    run_async(ex)(my_task());
317  
    run_async(ex)(my_task());
318  

318  

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

323  

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

332  

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

 
355 +
    ~run_async_wrapper()
 
356 +
    {
 
357 +
        // Restore TLS so stale pointer doesn't outlive
 
358 +
        // the execution context that owns the resource.
 
359 +
        current_frame_allocator() = saved_tls_;
351  
    }
360  
    }
352  

361  

353  
    // Non-copyable, non-movable (must be used immediately)
362  
    // Non-copyable, non-movable (must be used immediately)
354  
    run_async_wrapper(run_async_wrapper const&) = delete;
363  
    run_async_wrapper(run_async_wrapper const&) = delete;
355  
    run_async_wrapper(run_async_wrapper&&) = delete;
364  
    run_async_wrapper(run_async_wrapper&&) = delete;
356  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
365  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
357  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
366  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
358  

367  

359  
    /** Launch the task for execution.
368  
    /** Launch the task for execution.
360  

369  

361  
        This operator accepts a task and launches it on the executor.
370  
        This operator accepts a task and launches it on the executor.
362  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
371  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
363  
        correct LIFO destruction order.
372  
        correct LIFO destruction order.
364  

373  

365  
        The `io_env` constructed for the task is owned by the trampoline
374  
        The `io_env` constructed for the task is owned by the trampoline
366  
        coroutine and is guaranteed to outlive the task and all awaitables
375  
        coroutine and is guaranteed to outlive the task and all awaitables
367  
        in its chain. Awaitables may store `io_env const*` without concern
376  
        in its chain. Awaitables may store `io_env const*` without concern
368  
        for dangling references.
377  
        for dangling references.
369  

378  

370  
        @tparam Task The IoRunnable type.
379  
        @tparam Task The IoRunnable type.
371  

380  

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

390  

382  
        auto& p = tr_.h_.promise();
391  
        auto& p = tr_.h_.promise();
383  

392  

384  
        // Inject Task-specific invoke function
393  
        // Inject Task-specific invoke function
385  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
394  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
386  
        p.task_promise_ = &task_promise;
395  
        p.task_promise_ = &task_promise;
387  
        p.task_h_ = task_h;
396  
        p.task_h_ = task_h;
388  

397  

389  
        // Setup task's continuation to return to run_async_trampoline
398  
        // Setup task's continuation to return to run_async_trampoline
390  
        task_promise.set_continuation(tr_.h_);
399  
        task_promise.set_continuation(tr_.h_);
391  
        p.env_ = {p.wg_.executor(), st_, p.get_resource()};
400  
        p.env_ = {p.wg_.executor(), st_, p.get_resource()};
392  
        task_promise.set_environment(&p.env_);
401  
        task_promise.set_environment(&p.env_);
393  

402  

394  
        // Start task through executor
403  
        // Start task through executor
395  
        p.wg_.executor().dispatch(task_h).resume();
404  
        p.wg_.executor().dispatch(task_h).resume();
396  
    }
405  
    }
397  
};
406  
};
398  

407  

399  
//----------------------------------------------------------
408  
//----------------------------------------------------------
400  
//
409  
//
401  
// run_async Overloads
410  
// run_async Overloads
402  
//
411  
//
403  
//----------------------------------------------------------
412  
//----------------------------------------------------------
404  

413  

405  
// Executor only (uses default recycling allocator)
414  
// Executor only (uses default recycling allocator)
406  

415  

407  
/** Asynchronously launch a lazy task on the given executor.
416  
/** Asynchronously launch a lazy task on the given executor.
408  

417  

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

421  

413  
    Uses the default recycling frame allocator for coroutine frames.
422  
    Uses the default recycling frame allocator for coroutine frames.
414  
    With no handlers, the result is discarded and exceptions are rethrown.
423  
    With no handlers, the result is discarded and exceptions are rethrown.
415  

424  

416  
    @par Thread Safety
425  
    @par Thread Safety
417  
    The wrapper and handlers may be called from any thread where the
426  
    The wrapper and handlers may be called from any thread where the
418  
    executor schedules work.
427  
    executor schedules work.
419  

428  

420  
    @par Example
429  
    @par Example
421  
    @code
430  
    @code
422  
    run_async(ioc.get_executor())(my_task());
431  
    run_async(ioc.get_executor())(my_task());
423  
    @endcode
432  
    @endcode
424  

433  

425  
    @param ex The executor to execute the task on.
434  
    @param ex The executor to execute the task on.
426  

435  

427  
    @return A wrapper that accepts a `task<T>` for immediate execution.
436  
    @return A wrapper that accepts a `task<T>` for immediate execution.
428  

437  

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

452  

444  
/** Asynchronously launch a lazy task with a result handler.
453  
/** Asynchronously launch a lazy task with a result handler.
445  

454  

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

458  

450  
    @par Thread Safety
459  
    @par Thread Safety
451  
    The handler may be called from any thread where the executor
460  
    The handler may be called from any thread where the executor
452  
    schedules work.
461  
    schedules work.
453  

462  

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

469  

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

476  

468  
    @param ex The executor to execute the task on.
477  
    @param ex The executor to execute the task on.
469  
    @param h1 The handler to invoke with the result (and optionally exception).
478  
    @param h1 The handler to invoke with the result (and optionally exception).
470  

479  

471  
    @return A wrapper that accepts a `task<T>` for immediate execution.
480  
    @return A wrapper that accepts a `task<T>` for immediate execution.
472  

481  

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

496  

488  
/** Asynchronously launch a lazy task with separate result and error handlers.
497  
/** Asynchronously launch a lazy task with separate result and error handlers.
489  

498  

490  
    The handler `h1` is called with the task's result on success.
499  
    The handler `h1` is called with the task's result on success.
491  
    The handler `h2` is called with the exception_ptr on failure.
500  
    The handler `h2` is called with the exception_ptr on failure.
492  

501  

493  
    @par Thread Safety
502  
    @par Thread Safety
494  
    The handlers may be called from any thread where the executor
503  
    The handlers may be called from any thread where the executor
495  
    schedules work.
504  
    schedules work.
496  

505  

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

518  

510  
    @param ex The executor to execute the task on.
519  
    @param ex The executor to execute the task on.
511  
    @param h1 The handler to invoke with the result on success.
520  
    @param h1 The handler to invoke with the result on success.
512  
    @param h2 The handler to invoke with the exception on failure.
521  
    @param h2 The handler to invoke with the exception on failure.
513  

522  

514  
    @return A wrapper that accepts a `task<T>` for immediate execution.
523  
    @return A wrapper that accepts a `task<T>` for immediate execution.
515  

524  

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

539  

531  
// Ex + stop_token
540  
// Ex + stop_token
532  

541  

533  
/** Asynchronously launch a lazy task with stop token support.
542  
/** Asynchronously launch a lazy task with stop token support.
534  

543  

535  
    The stop token is propagated to the task, enabling cooperative
544  
    The stop token is propagated to the task, enabling cooperative
536  
    cancellation. With no handlers, the result is discarded and
545  
    cancellation. With no handlers, the result is discarded and
537  
    exceptions are rethrown.
546  
    exceptions are rethrown.
538  

547  

539  
    @par Thread Safety
548  
    @par Thread Safety
540  
    The wrapper may be called from any thread where the executor
549  
    The wrapper may be called from any thread where the executor
541  
    schedules work.
550  
    schedules work.
542  

551  

543  
    @par Example
552  
    @par Example
544  
    @code
553  
    @code
545  
    std::stop_source source;
554  
    std::stop_source source;
546  
    run_async(ex, source.get_token())(cancellable_task());
555  
    run_async(ex, source.get_token())(cancellable_task());
547  
    // Later: source.request_stop();
556  
    // Later: source.request_stop();
548  
    @endcode
557  
    @endcode
549  

558  

550  
    @param ex The executor to execute the task on.
559  
    @param ex The executor to execute the task on.
551  
    @param st The stop token for cooperative cancellation.
560  
    @param st The stop token for cooperative cancellation.
552  

561  

553  
    @return A wrapper that accepts a `task<T>` for immediate execution.
562  
    @return A wrapper that accepts a `task<T>` for immediate execution.
554  

563  

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

578  

570  
/** Asynchronously launch a lazy task with stop token and result handler.
579  
/** Asynchronously launch a lazy task with stop token and result handler.
571  

580  

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

584  

576  
    @param ex The executor to execute the task on.
585  
    @param ex The executor to execute the task on.
577  
    @param st The stop token for cooperative cancellation.
586  
    @param st The stop token for cooperative cancellation.
578  
    @param h1 The handler to invoke with the result (and optionally exception).
587  
    @param h1 The handler to invoke with the result (and optionally exception).
579  

588  

580  
    @return A wrapper that accepts a `task<T>` for immediate execution.
589  
    @return A wrapper that accepts a `task<T>` for immediate execution.
581  

590  

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

605  

597  
/** Asynchronously launch a lazy task with stop token and separate handlers.
606  
/** Asynchronously launch a lazy task with stop token and separate handlers.
598  

607  

599  
    The stop token is propagated to the task for cooperative cancellation.
608  
    The stop token is propagated to the task for cooperative cancellation.
600  
    The handler `h1` is called on success, `h2` on failure.
609  
    The handler `h1` is called on success, `h2` on failure.
601  

610  

602  
    @param ex The executor to execute the task on.
611  
    @param ex The executor to execute the task on.
603  
    @param st The stop token for cooperative cancellation.
612  
    @param st The stop token for cooperative cancellation.
604  
    @param h1 The handler to invoke with the result on success.
613  
    @param h1 The handler to invoke with the result on success.
605  
    @param h2 The handler to invoke with the exception on failure.
614  
    @param h2 The handler to invoke with the exception on failure.
606  

615  

607  
    @return A wrapper that accepts a `task<T>` for immediate execution.
616  
    @return A wrapper that accepts a `task<T>` for immediate execution.
608  

617  

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

632  

624  
// Ex + memory_resource*
633  
// Ex + memory_resource*
625  

634  

626  
/** Asynchronously launch a lazy task with custom memory resource.
635  
/** Asynchronously launch a lazy task with custom memory resource.
627  

636  

628  
    The memory resource is used for coroutine frame allocation. The caller
637  
    The memory resource is used for coroutine frame allocation. The caller
629  
    is responsible for ensuring the memory resource outlives all tasks.
638  
    is responsible for ensuring the memory resource outlives all tasks.
630  

639  

631  
    @param ex The executor to execute the task on.
640  
    @param ex The executor to execute the task on.
632  
    @param mr The memory resource for frame allocation.
641  
    @param mr The memory resource for frame allocation.
633  

642  

634  
    @return A wrapper that accepts a `task<T>` for immediate execution.
643  
    @return A wrapper that accepts a `task<T>` for immediate execution.
635  

644  

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

658  

650  
/** Asynchronously launch a lazy task with memory resource and handler.
659  
/** Asynchronously launch a lazy task with memory resource and handler.
651  

660  

652  
    @param ex The executor to execute the task on.
661  
    @param ex The executor to execute the task on.
653  
    @param mr The memory resource for frame allocation.
662  
    @param mr The memory resource for frame allocation.
654  
    @param h1 The handler to invoke with the result (and optionally exception).
663  
    @param h1 The handler to invoke with the result (and optionally exception).
655  

664  

656  
    @return A wrapper that accepts a `task<T>` for immediate execution.
665  
    @return A wrapper that accepts a `task<T>` for immediate execution.
657  

666  

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

680  

672  
/** Asynchronously launch a lazy task with memory resource and handlers.
681  
/** Asynchronously launch a lazy task with memory resource and handlers.
673  

682  

674  
    @param ex The executor to execute the task on.
683  
    @param ex The executor to execute the task on.
675  
    @param mr The memory resource for frame allocation.
684  
    @param mr The memory resource for frame allocation.
676  
    @param h1 The handler to invoke with the result on success.
685  
    @param h1 The handler to invoke with the result on success.
677  
    @param h2 The handler to invoke with the exception on failure.
686  
    @param h2 The handler to invoke with the exception on failure.
678  

687  

679  
    @return A wrapper that accepts a `task<T>` for immediate execution.
688  
    @return A wrapper that accepts a `task<T>` for immediate execution.
680  

689  

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

703  

695  
// Ex + stop_token + memory_resource*
704  
// Ex + stop_token + memory_resource*
696  

705  

697  
/** Asynchronously launch a lazy task with stop token and memory resource.
706  
/** Asynchronously launch a lazy task with stop token and memory resource.
698  

707  

699  
    @param ex The executor to execute the task on.
708  
    @param ex The executor to execute the task on.
700  
    @param st The stop token for cooperative cancellation.
709  
    @param st The stop token for cooperative cancellation.
701  
    @param mr The memory resource for frame allocation.
710  
    @param mr The memory resource for frame allocation.
702  

711  

703  
    @return A wrapper that accepts a `task<T>` for immediate execution.
712  
    @return A wrapper that accepts a `task<T>` for immediate execution.
704  

713  

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

727  

719  
/** Asynchronously launch a lazy task with stop token, memory resource, and handler.
728  
/** Asynchronously launch a lazy task with stop token, memory resource, and handler.
720  

729  

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

734  

726  
    @return A wrapper that accepts a `task<T>` for immediate execution.
735  
    @return A wrapper that accepts a `task<T>` for immediate execution.
727  

736  

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

750  

742  
/** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
751  
/** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
743  

752  

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

758  

750  
    @return A wrapper that accepts a `task<T>` for immediate execution.
759  
    @return A wrapper that accepts a `task<T>` for immediate execution.
751  

760  

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

774  

766  
// Ex + standard Allocator (value type)
775  
// Ex + standard Allocator (value type)
767  

776  

768  
/** Asynchronously launch a lazy task with custom allocator.
777  
/** Asynchronously launch a lazy task with custom allocator.
769  

778  

770  
    The allocator is wrapped in a frame_memory_resource and stored in the
779  
    The allocator is wrapped in a frame_memory_resource and stored in the
771  
    run_async_trampoline, ensuring it outlives all coroutine frames.
780  
    run_async_trampoline, ensuring it outlives all coroutine frames.
772  

781  

773  
    @param ex The executor to execute the task on.
782  
    @param ex The executor to execute the task on.
774  
    @param alloc The allocator for frame allocation (copied and stored).
783  
    @param alloc The allocator for frame allocation (copied and stored).
775  

784  

776  
    @return A wrapper that accepts a `task<T>` for immediate execution.
785  
    @return A wrapper that accepts a `task<T>` for immediate execution.
777  

786  

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

800  

792  
/** Asynchronously launch a lazy task with allocator and handler.
801  
/** Asynchronously launch a lazy task with allocator and handler.
793  

802  

794  
    @param ex The executor to execute the task on.
803  
    @param ex The executor to execute the task on.
795  
    @param alloc The allocator for frame allocation (copied and stored).
804  
    @param alloc The allocator for frame allocation (copied and stored).
796  
    @param h1 The handler to invoke with the result (and optionally exception).
805  
    @param h1 The handler to invoke with the result (and optionally exception).
797  

806  

798  
    @return A wrapper that accepts a `task<T>` for immediate execution.
807  
    @return A wrapper that accepts a `task<T>` for immediate execution.
799  

808  

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

822  

814  
/** Asynchronously launch a lazy task with allocator and handlers.
823  
/** Asynchronously launch a lazy task with allocator and handlers.
815  

824  

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

829  

821  
    @return A wrapper that accepts a `task<T>` for immediate execution.
830  
    @return A wrapper that accepts a `task<T>` for immediate execution.
822  

831  

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

845  

837  
// Ex + stop_token + standard Allocator
846  
// Ex + stop_token + standard Allocator
838  

847  

839  
/** Asynchronously launch a lazy task with stop token and allocator.
848  
/** Asynchronously launch a lazy task with stop token and allocator.
840  

849  

841  
    @param ex The executor to execute the task on.
850  
    @param ex The executor to execute the task on.
842  
    @param st The stop token for cooperative cancellation.
851  
    @param st The stop token for cooperative cancellation.
843  
    @param alloc The allocator for frame allocation (copied and stored).
852  
    @param alloc The allocator for frame allocation (copied and stored).
844  

853  

845  
    @return A wrapper that accepts a `task<T>` for immediate execution.
854  
    @return A wrapper that accepts a `task<T>` for immediate execution.
846  

855  

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

869  

861  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
870  
/** Asynchronously launch a lazy task with stop token, allocator, and handler.
862  

871  

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

876  

868  
    @return A wrapper that accepts a `task<T>` for immediate execution.
877  
    @return A wrapper that accepts a `task<T>` for immediate execution.
869  

878  

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

892  

884  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
893  
/** Asynchronously launch a lazy task with stop token, allocator, and handlers.
885  

894  

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

900  

892  
    @return A wrapper that accepts a `task<T>` for immediate execution.
901  
    @return A wrapper that accepts a `task<T>` for immediate execution.
893  

902  

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

916  

908  
} // namespace capy
917  
} // namespace capy
909  
} // namespace boost
918  
} // namespace boost
910  

919  

911  
#endif
920  
#endif