1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_RUN_ASYNC_HPP
10  
#ifndef BOOST_CAPY_RUN_ASYNC_HPP
11  
#define BOOST_CAPY_RUN_ASYNC_HPP
11  
#define BOOST_CAPY_RUN_ASYNC_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/run.hpp>
14  
#include <boost/capy/detail/run.hpp>
15  
#include <boost/capy/detail/run_callbacks.hpp>
15  
#include <boost/capy/detail/run_callbacks.hpp>
16  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/io_runnable.hpp>
17  
#include <boost/capy/concept/io_runnable.hpp>
18  
#include <boost/capy/ex/execution_context.hpp>
18  
#include <boost/capy/ex/execution_context.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
21  
#include <boost/capy/ex/recycling_memory_resource.hpp>
21  
#include <boost/capy/ex/recycling_memory_resource.hpp>
22  
#include <boost/capy/ex/work_guard.hpp>
22  
#include <boost/capy/ex/work_guard.hpp>
23  

23  

24  
#include <algorithm>
24  
#include <algorithm>
25  
#include <coroutine>
25  
#include <coroutine>
26  
#include <cstring>
26  
#include <cstring>
27  
#include <memory_resource>
27  
#include <memory_resource>
28  
#include <new>
28  
#include <new>
29  
#include <stop_token>
29  
#include <stop_token>
30  
#include <type_traits>
30  
#include <type_traits>
31  

31  

32  
namespace boost {
32  
namespace boost {
33  
namespace capy {
33  
namespace capy {
34  
namespace detail {
34  
namespace detail {
35  

35  

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

38  

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

50  

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

56  

57  
    bool await_ready() const noexcept { return false; }
57  
    bool await_ready() const noexcept { return false; }
58  

58  

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

64  

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

70  

71  
/** Internal run_async_trampoline coroutine for run_async.
71  
/** Internal run_async_trampoline coroutine for run_async.
72  

72  

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

76  

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

79  

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

88  

89  
    struct promise_type
89  
    struct promise_type
90  
    {
90  
    {
91  
        work_guard<Ex> wg_;
91  
        work_guard<Ex> wg_;
92  
        Handlers handlers_;
92  
        Handlers handlers_;
93  
        frame_memory_resource<Alloc> resource_;
93  
        frame_memory_resource<Alloc> resource_;
94  
        io_env env_;
94  
        io_env env_;
95  
        invoke_fn invoke_ = nullptr;
95  
        invoke_fn invoke_ = nullptr;
96  
        void* task_promise_ = nullptr;
96  
        void* task_promise_ = nullptr;
97  
        // task_h_: raw handle for frame_guard cleanup in make_trampoline.
97  
        // task_h_: raw handle for frame_guard cleanup in make_trampoline.
98  
        // task_cont_: continuation wrapping the same handle for executor dispatch.
98  
        // task_cont_: continuation wrapping the same handle for executor dispatch.
99  
        // Both must reference the same coroutine and be kept in sync.
99  
        // Both must reference the same coroutine and be kept in sync.
100  
        std::coroutine_handle<> task_h_;
100  
        std::coroutine_handle<> task_h_;
101  
        continuation task_cont_;
101  
        continuation task_cont_;
102  

102  

103  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
103  
        promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
104  
            : wg_(std::move(ex))
104  
            : wg_(std::move(ex))
105  
            , handlers_(std::move(h))
105  
            , handlers_(std::move(h))
106  
            , resource_(std::move(a))
106  
            , resource_(std::move(a))
107  
        {
107  
        {
108  
        }
108  
        }
109  

109  

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

115  

116  
            constexpr auto footer_align =
116  
            constexpr auto footer_align =
117  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
117  
                (std::max)(alignof(dealloc_fn), alignof(Alloc));
118  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
118  
            auto padded = (size + footer_align - 1) & ~(footer_align - 1);
119  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
119  
            auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
120  

120  

121  
            byte_alloc ba(std::move(a));
121  
            byte_alloc ba(std::move(a));
122  
            void* raw = ba.allocate(total);
122  
            void* raw = ba.allocate(total);
123  

123  

124  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
124  
            auto* fn_loc = reinterpret_cast<dealloc_fn*>(
125  
                static_cast<char*>(raw) + padded);
125  
                static_cast<char*>(raw) + padded);
126  
            *fn_loc = &dealloc_impl<byte_alloc>;
126  
            *fn_loc = &dealloc_impl<byte_alloc>;
127  

127  

128  
            new (fn_loc + 1) byte_alloc(std::move(ba));
128  
            new (fn_loc + 1) byte_alloc(std::move(ba));
129  

129  

130  
            return raw;
130  
            return raw;
131  
        }
131  
        }
132  

132  

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

139  

140  
            auto* fn = reinterpret_cast<dealloc_fn*>(
140  
            auto* fn = reinterpret_cast<dealloc_fn*>(
141  
                static_cast<char*>(ptr) + padded);
141  
                static_cast<char*>(ptr) + padded);
142  
            (*fn)(ptr, total);
142  
            (*fn)(ptr, total);
143  
        }
143  
        }
144  

144  

145  
        std::pmr::memory_resource* get_resource() noexcept
145  
        std::pmr::memory_resource* get_resource() noexcept
146  
        {
146  
        {
147  
            return &resource_;
147  
            return &resource_;
148  
        }
148  
        }
149  

149  

150  
        run_async_trampoline get_return_object() noexcept
150  
        run_async_trampoline get_return_object() noexcept
151  
        {
151  
        {
152  
            return run_async_trampoline{
152  
            return run_async_trampoline{
153  
                std::coroutine_handle<promise_type>::from_promise(*this)};
153  
                std::coroutine_handle<promise_type>::from_promise(*this)};
154  
        }
154  
        }
155  

155  

156  
        std::suspend_always initial_suspend() noexcept
156  
        std::suspend_always initial_suspend() noexcept
157  
        {
157  
        {
158  
            return {};
158  
            return {};
159  
        }
159  
        }
160  

160  

161  
        std::suspend_never final_suspend() noexcept
161  
        std::suspend_never final_suspend() noexcept
162  
        {
162  
        {
163  
            return {};
163  
            return {};
164  
        }
164  
        }
165  

165  

166  
        void return_void() noexcept
166  
        void return_void() noexcept
167  
        {
167  
        {
168  
        }
168  
        }
169  

169  

170  
        void unhandled_exception() noexcept
170  
        void unhandled_exception() noexcept
171  
        {
171  
        {
172  
        }
172  
        }
173  
    };
173  
    };
174  

174  

175  
    std::coroutine_handle<promise_type> h_;
175  
    std::coroutine_handle<promise_type> h_;
176  

176  

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

190  

191  
/** Specialization for memory_resource* - stores pointer directly.
191  
/** Specialization for memory_resource* - stores pointer directly.
192  

192  

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

199  

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

213  

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

221  

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

231  

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

239  

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

244  

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

250  

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

255  

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

260  

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

264  

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

269  

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

271  

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

285  

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

294  

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

302  

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

305  

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

307  

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

309  

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

313  

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

316  

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

320  

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

324  

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

329  

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

334  

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

343  

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

365  

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

372  

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

378  

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

380  

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

384  

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

389  

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

391  

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

401  

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

403  

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

408  

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

413  

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

421  

420  
// Executor only (uses default recycling allocator)
422  
// Executor only (uses default recycling allocator)
421  

423  

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

425  

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

429  

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

432  

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

436  

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

441  

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

443  

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

445  

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

460  

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

462  

461  
    The handler `h1` is called with the task's result on success. If `h1`
463  
    The handler `h1` is called with the task's result on success. If `h1`
462  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
464  
    is also invocable with `std::exception_ptr`, it handles exceptions too.
463  
    Otherwise, exceptions are rethrown.
465  
    Otherwise, exceptions are rethrown.
464  

466  

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

470  

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

477  

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

484  

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

487  

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

489  

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

504  

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

506  

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

509  

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

513  

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

526  

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

530  

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

532  

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

547  

546  
// Ex + stop_token
548  
// Ex + stop_token
547  

549  

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

551  

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

555  

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

559  

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

566  

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

569  

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

571  

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

586  

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

588  

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

592  

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

596  

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

598  

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

613  

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

615  

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

618  

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

623  

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

625  

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

640  

639  
// Ex + memory_resource*
641  
// Ex + memory_resource*
640  

642  

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

644  

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

647  

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

650  

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

652  

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

666  

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

668  

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

672  

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

674  

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

688  

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

690  

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

695  

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

697  

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

711  

710  
// Ex + stop_token + memory_resource*
712  
// Ex + stop_token + memory_resource*
711  

713  

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

715  

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

719  

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

721  

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

735  

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

737  

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

742  

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

744  

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

758  

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

760  

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

766  

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

768  

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

782  

781  
// Ex + standard Allocator (value type)
783  
// Ex + standard Allocator (value type)
782  

784  

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

786  

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

789  

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

792  

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

794  

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

808  

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

810  

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

814  

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

816  

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

830  

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

832  

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

837  

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

839  

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

853  

852  
// Ex + stop_token + standard Allocator
854  
// Ex + stop_token + standard Allocator
853  

855  

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

857  

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

861  

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

863  

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

877  

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

879  

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

884  

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

886  

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

900  

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

902  

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

908  

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

910  

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

924  

923  
} // namespace capy
925  
} // namespace capy
924  
} // namespace boost
926  
} // namespace boost
925  

927  

926  
#endif
928  
#endif