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  
        p.task_cont_.h = task_h;
415  
        p.task_cont_.h = task_h;
416  
        p.wg_.executor().dispatch(p.task_cont_).resume();
416  
        p.wg_.executor().dispatch(p.task_cont_).resume();
417  
    }
417  
    }
418  
};
418  
};
419  

419  

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

421  

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

423  

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

427  

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

430  

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

434  

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

439  

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

441  

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

443  

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

458  

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

460  

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

464  

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

468  

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

475  

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

482  

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

485  

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

487  

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

502  

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

504  

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

507  

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

511  

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

524  

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

528  

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

530  

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

545  

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

547  

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

549  

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

553  

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

557  

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

564  

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

567  

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

569  

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

584  

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

586  

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

590  

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

594  

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

596  

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

611  

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

613  

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

616  

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

621  

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

623  

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

638  

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

640  

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

642  

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

645  

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

648  

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

650  

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

664  

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

666  

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

670  

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

672  

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

686  

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

688  

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

693  

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

695  

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

709  

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

711  

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

713  

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

717  

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

719  

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

733  

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

735  

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

740  

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

742  

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

756  

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

758  

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

764  

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

766  

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

780  

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

782  

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

784  

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

787  

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

790  

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

792  

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

806  

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

808  

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

812  

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

814  

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

828  

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

830  

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

835  

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

837  

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

851  

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

853  

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

855  

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

859  

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

861  

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

875  

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

877  

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

882  

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

884  

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

898  

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

900  

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

906  

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

908  

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

922  

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

925  

926  
#endif
926  
#endif