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

9  

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

12  

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

23  

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

31  

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

35  

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

38  

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

50  

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

56  

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

58  

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

64  

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

70  

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

72  

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

76  

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

79  

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

88  

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

98  

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

105  

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

111  

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

116  

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

119  

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

123  

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

125  

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

128  

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

135  

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

140  

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

145  

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

151  

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

156  

157 -
        std::suspend_never final_suspend() noexcept
157 +
        auto final_suspend() noexcept
158  
        {
158  
        {
159 -
            return {};
159 +
            // Use an explicit destroyer awaiter instead of suspend_never.
 
160 +
            // MSVC <= 19.39 misallocates the await_suspend return buffer
 
161 +
            // inside the coroutine frame; destroying the frame from
 
162 +
            // await_suspend then causes use-after-free. A void-returning
 
163 +
            // await_suspend avoids the problem. See:
 
164 +
            // https://developercommunity.visualstudio.com/t/destroy-coroutine-from-final_suspend-r/10096047
 
165 +
            struct destroyer
 
166 +
            {
 
167 +
                bool await_ready() noexcept { return false; }
 
168 +
                void await_suspend(
 
169 +
                    std::coroutine_handle<> h) noexcept
 
170 +
                {
 
171 +
                    h.destroy();
 
172 +
                }
 
173 +
                void await_resume() noexcept {}
 
174 +
            };
 
175 +
            return destroyer{};
160  
        }
176  
        }
161  

177  

162  
        void return_void() noexcept
178  
        void return_void() noexcept
163  
        {
179  
        {
164  
        }
180  
        }
165  

181  

166  
        void unhandled_exception() noexcept
182  
        void unhandled_exception() noexcept
167  
        {
183  
        {
168  
        }
184  
        }
169  
    };
185  
    };
170  

186  

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

188  

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

202  

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

204  

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

211  

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

221  

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

229  

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

239  

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

247  

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

252  

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

258  

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

263  

248 -
        std::suspend_never final_suspend() noexcept
264 +
        auto final_suspend() noexcept
249  
        {
265  
        {
250 -
            return {};
266 +
            // Use an explicit destroyer awaiter instead of suspend_never.
 
267 +
            // MSVC <= 19.39 misallocates the await_suspend return buffer
 
268 +
            // inside the coroutine frame; destroying the frame from
 
269 +
            // await_suspend then causes use-after-free. A void-returning
 
270 +
            // await_suspend avoids the problem. See:
 
271 +
            // https://developercommunity.visualstudio.com/t/destroy-coroutine-from-final_suspend-r/10096047
 
272 +
            struct destroyer
 
273 +
            {
 
274 +
                bool await_ready() noexcept { return false; }
 
275 +
                void await_suspend(
 
276 +
                    std::coroutine_handle<> h) noexcept
 
277 +
                {
 
278 +
                    h.destroy();
 
279 +
                }
 
280 +
                void await_resume() noexcept {}
 
281 +
            };
 
282 +
            return destroyer{};
251  
        }
283  
        }
252  

284  

253  
        void return_void() noexcept
285  
        void return_void() noexcept
254  
        {
286  
        {
255  
        }
287  
        }
256  

288  

257  
        void unhandled_exception() noexcept
289  
        void unhandled_exception() noexcept
258  
        {
290  
        {
259  
        }
291  
        }
260  
    };
292  
    };
261  

293  

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

295  

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

309  

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

322  

291  
} // namespace detail
323  
} // namespace detail
292  

324  

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

326  

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

330  

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

333  

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

337  

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

341  

310  
    @par Example
342  
    @par Example
311  
    @code
343  
    @code
312  
    // Correct usage - wrapper is temporary
344  
    // Correct usage - wrapper is temporary
313  
    run_async(ex)(my_task());
345  
    run_async(ex)(my_task());
314  

346  

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

351  

320  
    @see run_async
352  
    @see run_async
321  
*/
353  
*/
322  
template<Executor Ex, class Handlers, class Alloc>
354  
template<Executor Ex, class Handlers, class Alloc>
323  
class [[nodiscard]] run_async_wrapper
355  
class [[nodiscard]] run_async_wrapper
324  
{
356  
{
325  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
357  
    detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
326  
    std::stop_token st_;
358  
    std::stop_token st_;
327  
    std::pmr::memory_resource* saved_tls_;
359  
    std::pmr::memory_resource* saved_tls_;
328  

360  

329  
public:
361  
public:
330  
    /// Construct wrapper with executor, stop token, handlers, and allocator.
362  
    /// Construct wrapper with executor, stop token, handlers, and allocator.
331  
    run_async_wrapper(
363  
    run_async_wrapper(
332  
        Ex ex,
364  
        Ex ex,
333  
        std::stop_token st,
365  
        std::stop_token st,
334  
        Handlers h,
366  
        Handlers h,
335  
        Alloc a) noexcept
367  
        Alloc a) noexcept
336  
        : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
368  
        : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
337  
            std::move(ex), std::move(h), std::move(a)))
369  
            std::move(ex), std::move(h), std::move(a)))
338  
        , st_(std::move(st))
370  
        , st_(std::move(st))
339  
        , saved_tls_(get_current_frame_allocator())
371  
        , saved_tls_(get_current_frame_allocator())
340  
    {
372  
    {
341  
        if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
373  
        if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
342  
        {
374  
        {
343  
            static_assert(
375  
            static_assert(
344  
                std::is_nothrow_move_constructible_v<Alloc>,
376  
                std::is_nothrow_move_constructible_v<Alloc>,
345  
                "Allocator must be nothrow move constructible");
377  
                "Allocator must be nothrow move constructible");
346  
        }
378  
        }
347  
        // Set TLS before task argument is evaluated
379  
        // Set TLS before task argument is evaluated
348  
        set_current_frame_allocator(tr_.h_.promise().get_resource());
380  
        set_current_frame_allocator(tr_.h_.promise().get_resource());
349  
    }
381  
    }
350  

382  

351  
    ~run_async_wrapper()
383  
    ~run_async_wrapper()
352  
    {
384  
    {
353  
        // Restore TLS so stale pointer doesn't outlive
385  
        // Restore TLS so stale pointer doesn't outlive
354  
        // the execution context that owns the resource.
386  
        // the execution context that owns the resource.
355  
        set_current_frame_allocator(saved_tls_);
387  
        set_current_frame_allocator(saved_tls_);
356  
    }
388  
    }
357  

389  

358  
    // Non-copyable, non-movable (must be used immediately)
390  
    // Non-copyable, non-movable (must be used immediately)
359  
    run_async_wrapper(run_async_wrapper const&) = delete;
391  
    run_async_wrapper(run_async_wrapper const&) = delete;
360  
    run_async_wrapper(run_async_wrapper&&) = delete;
392  
    run_async_wrapper(run_async_wrapper&&) = delete;
361  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
393  
    run_async_wrapper& operator=(run_async_wrapper const&) = delete;
362  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
394  
    run_async_wrapper& operator=(run_async_wrapper&&) = delete;
363  

395  

364  
    /** Launch the task for execution.
396  
    /** Launch the task for execution.
365  

397  

366  
        This operator accepts a task and launches it on the executor.
398  
        This operator accepts a task and launches it on the executor.
367  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
399  
        The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
368  
        correct LIFO destruction order.
400  
        correct LIFO destruction order.
369  

401  

370  
        The `io_env` constructed for the task is owned by the trampoline
402  
        The `io_env` constructed for the task is owned by the trampoline
371  
        coroutine and is guaranteed to outlive the task and all awaitables
403  
        coroutine and is guaranteed to outlive the task and all awaitables
372  
        in its chain. Awaitables may store `io_env const*` without concern
404  
        in its chain. Awaitables may store `io_env const*` without concern
373  
        for dangling references.
405  
        for dangling references.
374  

406  

375  
        @tparam Task The IoRunnable type.
407  
        @tparam Task The IoRunnable type.
376  

408  

377  
        @param t The task to execute. Ownership is transferred to the
409  
        @param t The task to execute. Ownership is transferred to the
378  
                 run_async_trampoline which will destroy it after completion.
410  
                 run_async_trampoline which will destroy it after completion.
379  
    */
411  
    */
380  
    template<IoRunnable Task>
412  
    template<IoRunnable Task>
381  
    void operator()(Task t) &&
413  
    void operator()(Task t) &&
382  
    {
414  
    {
383  
        auto task_h = t.handle();
415  
        auto task_h = t.handle();
384  
        auto& task_promise = task_h.promise();
416  
        auto& task_promise = task_h.promise();
385  
        t.release();
417  
        t.release();
386  

418  

387  
        auto& p = tr_.h_.promise();
419  
        auto& p = tr_.h_.promise();
388  

420  

389  
        // Inject Task-specific invoke function
421  
        // Inject Task-specific invoke function
390  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
422  
        p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
391  
        p.task_promise_ = &task_promise;
423  
        p.task_promise_ = &task_promise;
392  
        p.task_h_ = task_h;
424  
        p.task_h_ = task_h;
393  

425  

394  
        // Setup task's continuation to return to run_async_trampoline
426  
        // Setup task's continuation to return to run_async_trampoline
395  
        task_promise.set_continuation(tr_.h_);
427  
        task_promise.set_continuation(tr_.h_);
396  
        p.env_ = {p.wg_.executor(), st_, p.get_resource()};
428  
        p.env_ = {p.wg_.executor(), st_, p.get_resource()};
397  
        task_promise.set_environment(&p.env_);
429  
        task_promise.set_environment(&p.env_);
398  

430  

399  
        // Start task through executor
431  
        // Start task through executor
400  
        p.wg_.executor().dispatch(task_h).resume();
432  
        p.wg_.executor().dispatch(task_h).resume();
401  
    }
433  
    }
402  
};
434  
};
403  

435  

404  
// Executor only (uses default recycling allocator)
436  
// Executor only (uses default recycling allocator)
405  

437  

406  
/** Asynchronously launch a lazy task on the given executor.
438  
/** Asynchronously launch a lazy task on the given executor.
407  

439  

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

443  

412  
    Uses the default recycling frame allocator for coroutine frames.
444  
    Uses the default recycling frame allocator for coroutine frames.
413  
    With no handlers, the result is discarded and exceptions are rethrown.
445  
    With no handlers, the result is discarded and exceptions are rethrown.
414  

446  

415  
    @par Thread Safety
447  
    @par Thread Safety
416  
    The wrapper and handlers may be called from any thread where the
448  
    The wrapper and handlers may be called from any thread where the
417  
    executor schedules work.
449  
    executor schedules work.
418  

450  

419  
    @par Example
451  
    @par Example
420  
    @code
452  
    @code
421  
    run_async(ioc.get_executor())(my_task());
453  
    run_async(ioc.get_executor())(my_task());
422  
    @endcode
454  
    @endcode
423  

455  

424  
    @param ex The executor to execute the task on.
456  
    @param ex The executor to execute the task on.
425  

457  

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

459  

428  
    @see task
460  
    @see task
429  
    @see executor
461  
    @see executor
430  
*/
462  
*/
431  
template<Executor Ex>
463  
template<Executor Ex>
432  
[[nodiscard]] auto
464  
[[nodiscard]] auto
433  
run_async(Ex ex)
465  
run_async(Ex ex)
434  
{
466  
{
435  
    auto* mr = ex.context().get_frame_allocator();
467  
    auto* mr = ex.context().get_frame_allocator();
436  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
468  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
437  
        std::move(ex),
469  
        std::move(ex),
438  
        std::stop_token{},
470  
        std::stop_token{},
439  
        detail::default_handler{},
471  
        detail::default_handler{},
440  
        mr);
472  
        mr);
441  
}
473  
}
442  

474  

443  
/** Asynchronously launch a lazy task with a result handler.
475  
/** Asynchronously launch a lazy task with a result handler.
444  

476  

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

480  

449  
    @par Thread Safety
481  
    @par Thread Safety
450  
    The handler may be called from any thread where the executor
482  
    The handler may be called from any thread where the executor
451  
    schedules work.
483  
    schedules work.
452  

484  

453  
    @par Example
485  
    @par Example
454  
    @code
486  
    @code
455  
    // Handler for result only (exceptions rethrown)
487  
    // Handler for result only (exceptions rethrown)
456  
    run_async(ex, [](int result) {
488  
    run_async(ex, [](int result) {
457  
        std::cout << "Got: " << result << "\n";
489  
        std::cout << "Got: " << result << "\n";
458  
    })(compute_value());
490  
    })(compute_value());
459  

491  

460  
    // Overloaded handler for both result and exception
492  
    // Overloaded handler for both result and exception
461  
    run_async(ex, overloaded{
493  
    run_async(ex, overloaded{
462  
        [](int result) { std::cout << "Got: " << result << "\n"; },
494  
        [](int result) { std::cout << "Got: " << result << "\n"; },
463  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
495  
        [](std::exception_ptr) { std::cout << "Failed\n"; }
464  
    })(compute_value());
496  
    })(compute_value());
465  
    @endcode
497  
    @endcode
466  

498  

467  
    @param ex The executor to execute the task on.
499  
    @param ex The executor to execute the task on.
468  
    @param h1 The handler to invoke with the result (and optionally exception).
500  
    @param h1 The handler to invoke with the result (and optionally exception).
469  

501  

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

503  

472  
    @see task
504  
    @see task
473  
    @see executor
505  
    @see executor
474  
*/
506  
*/
475  
template<Executor Ex, class H1>
507  
template<Executor Ex, class H1>
476  
[[nodiscard]] auto
508  
[[nodiscard]] auto
477  
run_async(Ex ex, H1 h1)
509  
run_async(Ex ex, H1 h1)
478  
{
510  
{
479  
    auto* mr = ex.context().get_frame_allocator();
511  
    auto* mr = ex.context().get_frame_allocator();
480  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
512  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
481  
        std::move(ex),
513  
        std::move(ex),
482  
        std::stop_token{},
514  
        std::stop_token{},
483  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
515  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
484  
        mr);
516  
        mr);
485  
}
517  
}
486  

518  

487  
/** Asynchronously launch a lazy task with separate result and error handlers.
519  
/** Asynchronously launch a lazy task with separate result and error handlers.
488  

520  

489  
    The handler `h1` is called with the task's result on success.
521  
    The handler `h1` is called with the task's result on success.
490  
    The handler `h2` is called with the exception_ptr on failure.
522  
    The handler `h2` is called with the exception_ptr on failure.
491  

523  

492  
    @par Thread Safety
524  
    @par Thread Safety
493  
    The handlers may be called from any thread where the executor
525  
    The handlers may be called from any thread where the executor
494  
    schedules work.
526  
    schedules work.
495  

527  

496  
    @par Example
528  
    @par Example
497  
    @code
529  
    @code
498  
    run_async(ex,
530  
    run_async(ex,
499  
        [](int result) { std::cout << "Got: " << result << "\n"; },
531  
        [](int result) { std::cout << "Got: " << result << "\n"; },
500  
        [](std::exception_ptr ep) {
532  
        [](std::exception_ptr ep) {
501  
            try { std::rethrow_exception(ep); }
533  
            try { std::rethrow_exception(ep); }
502  
            catch (std::exception const& e) {
534  
            catch (std::exception const& e) {
503  
                std::cout << "Error: " << e.what() << "\n";
535  
                std::cout << "Error: " << e.what() << "\n";
504  
            }
536  
            }
505  
        }
537  
        }
506  
    )(compute_value());
538  
    )(compute_value());
507  
    @endcode
539  
    @endcode
508  

540  

509  
    @param ex The executor to execute the task on.
541  
    @param ex The executor to execute the task on.
510  
    @param h1 The handler to invoke with the result on success.
542  
    @param h1 The handler to invoke with the result on success.
511  
    @param h2 The handler to invoke with the exception on failure.
543  
    @param h2 The handler to invoke with the exception on failure.
512  

544  

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

546  

515  
    @see task
547  
    @see task
516  
    @see executor
548  
    @see executor
517  
*/
549  
*/
518  
template<Executor Ex, class H1, class H2>
550  
template<Executor Ex, class H1, class H2>
519  
[[nodiscard]] auto
551  
[[nodiscard]] auto
520  
run_async(Ex ex, H1 h1, H2 h2)
552  
run_async(Ex ex, H1 h1, H2 h2)
521  
{
553  
{
522  
    auto* mr = ex.context().get_frame_allocator();
554  
    auto* mr = ex.context().get_frame_allocator();
523  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
555  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
524  
        std::move(ex),
556  
        std::move(ex),
525  
        std::stop_token{},
557  
        std::stop_token{},
526  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
558  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
527  
        mr);
559  
        mr);
528  
}
560  
}
529  

561  

530  
// Ex + stop_token
562  
// Ex + stop_token
531  

563  

532  
/** Asynchronously launch a lazy task with stop token support.
564  
/** Asynchronously launch a lazy task with stop token support.
533  

565  

534  
    The stop token is propagated to the task, enabling cooperative
566  
    The stop token is propagated to the task, enabling cooperative
535  
    cancellation. With no handlers, the result is discarded and
567  
    cancellation. With no handlers, the result is discarded and
536  
    exceptions are rethrown.
568  
    exceptions are rethrown.
537  

569  

538  
    @par Thread Safety
570  
    @par Thread Safety
539  
    The wrapper may be called from any thread where the executor
571  
    The wrapper may be called from any thread where the executor
540  
    schedules work.
572  
    schedules work.
541  

573  

542  
    @par Example
574  
    @par Example
543  
    @code
575  
    @code
544  
    std::stop_source source;
576  
    std::stop_source source;
545  
    run_async(ex, source.get_token())(cancellable_task());
577  
    run_async(ex, source.get_token())(cancellable_task());
546  
    // Later: source.request_stop();
578  
    // Later: source.request_stop();
547  
    @endcode
579  
    @endcode
548  

580  

549  
    @param ex The executor to execute the task on.
581  
    @param ex The executor to execute the task on.
550  
    @param st The stop token for cooperative cancellation.
582  
    @param st The stop token for cooperative cancellation.
551  

583  

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

585  

554  
    @see task
586  
    @see task
555  
    @see executor
587  
    @see executor
556  
*/
588  
*/
557  
template<Executor Ex>
589  
template<Executor Ex>
558  
[[nodiscard]] auto
590  
[[nodiscard]] auto
559  
run_async(Ex ex, std::stop_token st)
591  
run_async(Ex ex, std::stop_token st)
560  
{
592  
{
561  
    auto* mr = ex.context().get_frame_allocator();
593  
    auto* mr = ex.context().get_frame_allocator();
562  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
594  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
563  
        std::move(ex),
595  
        std::move(ex),
564  
        std::move(st),
596  
        std::move(st),
565  
        detail::default_handler{},
597  
        detail::default_handler{},
566  
        mr);
598  
        mr);
567  
}
599  
}
568  

600  

569  
/** Asynchronously launch a lazy task with stop token and result handler.
601  
/** Asynchronously launch a lazy task with stop token and result handler.
570  

602  

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

606  

575  
    @param ex The executor to execute the task on.
607  
    @param ex The executor to execute the task on.
576  
    @param st The stop token for cooperative cancellation.
608  
    @param st The stop token for cooperative cancellation.
577  
    @param h1 The handler to invoke with the result (and optionally exception).
609  
    @param h1 The handler to invoke with the result (and optionally exception).
578  

610  

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

612  

581  
    @see task
613  
    @see task
582  
    @see executor
614  
    @see executor
583  
*/
615  
*/
584  
template<Executor Ex, class H1>
616  
template<Executor Ex, class H1>
585  
[[nodiscard]] auto
617  
[[nodiscard]] auto
586  
run_async(Ex ex, std::stop_token st, H1 h1)
618  
run_async(Ex ex, std::stop_token st, H1 h1)
587  
{
619  
{
588  
    auto* mr = ex.context().get_frame_allocator();
620  
    auto* mr = ex.context().get_frame_allocator();
589  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
621  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
590  
        std::move(ex),
622  
        std::move(ex),
591  
        std::move(st),
623  
        std::move(st),
592  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
624  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
593  
        mr);
625  
        mr);
594  
}
626  
}
595  

627  

596  
/** Asynchronously launch a lazy task with stop token and separate handlers.
628  
/** Asynchronously launch a lazy task with stop token and separate handlers.
597  

629  

598  
    The stop token is propagated to the task for cooperative cancellation.
630  
    The stop token is propagated to the task for cooperative cancellation.
599  
    The handler `h1` is called on success, `h2` on failure.
631  
    The handler `h1` is called on success, `h2` on failure.
600  

632  

601  
    @param ex The executor to execute the task on.
633  
    @param ex The executor to execute the task on.
602  
    @param st The stop token for cooperative cancellation.
634  
    @param st The stop token for cooperative cancellation.
603  
    @param h1 The handler to invoke with the result on success.
635  
    @param h1 The handler to invoke with the result on success.
604  
    @param h2 The handler to invoke with the exception on failure.
636  
    @param h2 The handler to invoke with the exception on failure.
605  

637  

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

639  

608  
    @see task
640  
    @see task
609  
    @see executor
641  
    @see executor
610  
*/
642  
*/
611  
template<Executor Ex, class H1, class H2>
643  
template<Executor Ex, class H1, class H2>
612  
[[nodiscard]] auto
644  
[[nodiscard]] auto
613  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
645  
run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
614  
{
646  
{
615  
    auto* mr = ex.context().get_frame_allocator();
647  
    auto* mr = ex.context().get_frame_allocator();
616  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
648  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
617  
        std::move(ex),
649  
        std::move(ex),
618  
        std::move(st),
650  
        std::move(st),
619  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
651  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
620  
        mr);
652  
        mr);
621  
}
653  
}
622  

654  

623  
// Ex + memory_resource*
655  
// Ex + memory_resource*
624  

656  

625  
/** Asynchronously launch a lazy task with custom memory resource.
657  
/** Asynchronously launch a lazy task with custom memory resource.
626  

658  

627  
    The memory resource is used for coroutine frame allocation. The caller
659  
    The memory resource is used for coroutine frame allocation. The caller
628  
    is responsible for ensuring the memory resource outlives all tasks.
660  
    is responsible for ensuring the memory resource outlives all tasks.
629  

661  

630  
    @param ex The executor to execute the task on.
662  
    @param ex The executor to execute the task on.
631  
    @param mr The memory resource for frame allocation.
663  
    @param mr The memory resource for frame allocation.
632  

664  

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

666  

635  
    @see task
667  
    @see task
636  
    @see executor
668  
    @see executor
637  
*/
669  
*/
638  
template<Executor Ex>
670  
template<Executor Ex>
639  
[[nodiscard]] auto
671  
[[nodiscard]] auto
640  
run_async(Ex ex, std::pmr::memory_resource* mr)
672  
run_async(Ex ex, std::pmr::memory_resource* mr)
641  
{
673  
{
642  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
674  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
643  
        std::move(ex),
675  
        std::move(ex),
644  
        std::stop_token{},
676  
        std::stop_token{},
645  
        detail::default_handler{},
677  
        detail::default_handler{},
646  
        mr);
678  
        mr);
647  
}
679  
}
648  

680  

649  
/** Asynchronously launch a lazy task with memory resource and handler.
681  
/** Asynchronously launch a lazy task with memory resource and handler.
650  

682  

651  
    @param ex The executor to execute the task on.
683  
    @param ex The executor to execute the task on.
652  
    @param mr The memory resource for frame allocation.
684  
    @param mr The memory resource for frame allocation.
653  
    @param h1 The handler to invoke with the result (and optionally exception).
685  
    @param h1 The handler to invoke with the result (and optionally exception).
654  

686  

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

688  

657  
    @see task
689  
    @see task
658  
    @see executor
690  
    @see executor
659  
*/
691  
*/
660  
template<Executor Ex, class H1>
692  
template<Executor Ex, class H1>
661  
[[nodiscard]] auto
693  
[[nodiscard]] auto
662  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
694  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
663  
{
695  
{
664  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
696  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
665  
        std::move(ex),
697  
        std::move(ex),
666  
        std::stop_token{},
698  
        std::stop_token{},
667  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
699  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
668  
        mr);
700  
        mr);
669  
}
701  
}
670  

702  

671  
/** Asynchronously launch a lazy task with memory resource and handlers.
703  
/** Asynchronously launch a lazy task with memory resource and handlers.
672  

704  

673  
    @param ex The executor to execute the task on.
705  
    @param ex The executor to execute the task on.
674  
    @param mr The memory resource for frame allocation.
706  
    @param mr The memory resource for frame allocation.
675  
    @param h1 The handler to invoke with the result on success.
707  
    @param h1 The handler to invoke with the result on success.
676  
    @param h2 The handler to invoke with the exception on failure.
708  
    @param h2 The handler to invoke with the exception on failure.
677  

709  

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

711  

680  
    @see task
712  
    @see task
681  
    @see executor
713  
    @see executor
682  
*/
714  
*/
683  
template<Executor Ex, class H1, class H2>
715  
template<Executor Ex, class H1, class H2>
684  
[[nodiscard]] auto
716  
[[nodiscard]] auto
685  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
717  
run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
686  
{
718  
{
687  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
719  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
688  
        std::move(ex),
720  
        std::move(ex),
689  
        std::stop_token{},
721  
        std::stop_token{},
690  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
722  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
691  
        mr);
723  
        mr);
692  
}
724  
}
693  

725  

694  
// Ex + stop_token + memory_resource*
726  
// Ex + stop_token + memory_resource*
695  

727  

696  
/** Asynchronously launch a lazy task with stop token and memory resource.
728  
/** Asynchronously launch a lazy task with stop token and memory resource.
697  

729  

698  
    @param ex The executor to execute the task on.
730  
    @param ex The executor to execute the task on.
699  
    @param st The stop token for cooperative cancellation.
731  
    @param st The stop token for cooperative cancellation.
700  
    @param mr The memory resource for frame allocation.
732  
    @param mr The memory resource for frame allocation.
701  

733  

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

735  

704  
    @see task
736  
    @see task
705  
    @see executor
737  
    @see executor
706  
*/
738  
*/
707  
template<Executor Ex>
739  
template<Executor Ex>
708  
[[nodiscard]] auto
740  
[[nodiscard]] auto
709  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
741  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
710  
{
742  
{
711  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
743  
    return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
712  
        std::move(ex),
744  
        std::move(ex),
713  
        std::move(st),
745  
        std::move(st),
714  
        detail::default_handler{},
746  
        detail::default_handler{},
715  
        mr);
747  
        mr);
716  
}
748  
}
717  

749  

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

751  

720  
    @param ex The executor to execute the task on.
752  
    @param ex The executor to execute the task on.
721  
    @param st The stop token for cooperative cancellation.
753  
    @param st The stop token for cooperative cancellation.
722  
    @param mr The memory resource for frame allocation.
754  
    @param mr The memory resource for frame allocation.
723  
    @param h1 The handler to invoke with the result (and optionally exception).
755  
    @param h1 The handler to invoke with the result (and optionally exception).
724  

756  

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

758  

727  
    @see task
759  
    @see task
728  
    @see executor
760  
    @see executor
729  
*/
761  
*/
730  
template<Executor Ex, class H1>
762  
template<Executor Ex, class H1>
731  
[[nodiscard]] auto
763  
[[nodiscard]] auto
732  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
764  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
733  
{
765  
{
734  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
766  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
735  
        std::move(ex),
767  
        std::move(ex),
736  
        std::move(st),
768  
        std::move(st),
737  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
769  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
738  
        mr);
770  
        mr);
739  
}
771  
}
740  

772  

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

774  

743  
    @param ex The executor to execute the task on.
775  
    @param ex The executor to execute the task on.
744  
    @param st The stop token for cooperative cancellation.
776  
    @param st The stop token for cooperative cancellation.
745  
    @param mr The memory resource for frame allocation.
777  
    @param mr The memory resource for frame allocation.
746  
    @param h1 The handler to invoke with the result on success.
778  
    @param h1 The handler to invoke with the result on success.
747  
    @param h2 The handler to invoke with the exception on failure.
779  
    @param h2 The handler to invoke with the exception on failure.
748  

780  

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

782  

751  
    @see task
783  
    @see task
752  
    @see executor
784  
    @see executor
753  
*/
785  
*/
754  
template<Executor Ex, class H1, class H2>
786  
template<Executor Ex, class H1, class H2>
755  
[[nodiscard]] auto
787  
[[nodiscard]] auto
756  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
788  
run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
757  
{
789  
{
758  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
790  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
759  
        std::move(ex),
791  
        std::move(ex),
760  
        std::move(st),
792  
        std::move(st),
761  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
793  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
762  
        mr);
794  
        mr);
763  
}
795  
}
764  

796  

765  
// Ex + standard Allocator (value type)
797  
// Ex + standard Allocator (value type)
766  

798  

767  
/** Asynchronously launch a lazy task with custom allocator.
799  
/** Asynchronously launch a lazy task with custom allocator.
768  

800  

769  
    The allocator is wrapped in a frame_memory_resource and stored in the
801  
    The allocator is wrapped in a frame_memory_resource and stored in the
770  
    run_async_trampoline, ensuring it outlives all coroutine frames.
802  
    run_async_trampoline, ensuring it outlives all coroutine frames.
771  

803  

772  
    @param ex The executor to execute the task on.
804  
    @param ex The executor to execute the task on.
773  
    @param alloc The allocator for frame allocation (copied and stored).
805  
    @param alloc The allocator for frame allocation (copied and stored).
774  

806  

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

808  

777  
    @see task
809  
    @see task
778  
    @see executor
810  
    @see executor
779  
*/
811  
*/
780  
template<Executor Ex, detail::Allocator Alloc>
812  
template<Executor Ex, detail::Allocator Alloc>
781  
[[nodiscard]] auto
813  
[[nodiscard]] auto
782  
run_async(Ex ex, Alloc alloc)
814  
run_async(Ex ex, Alloc alloc)
783  
{
815  
{
784  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
816  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
785  
        std::move(ex),
817  
        std::move(ex),
786  
        std::stop_token{},
818  
        std::stop_token{},
787  
        detail::default_handler{},
819  
        detail::default_handler{},
788  
        std::move(alloc));
820  
        std::move(alloc));
789  
}
821  
}
790  

822  

791  
/** Asynchronously launch a lazy task with allocator and handler.
823  
/** Asynchronously launch a lazy task with allocator and handler.
792  

824  

793  
    @param ex The executor to execute the task on.
825  
    @param ex The executor to execute the task on.
794  
    @param alloc The allocator for frame allocation (copied and stored).
826  
    @param alloc The allocator for frame allocation (copied and stored).
795  
    @param h1 The handler to invoke with the result (and optionally exception).
827  
    @param h1 The handler to invoke with the result (and optionally exception).
796  

828  

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

830  

799  
    @see task
831  
    @see task
800  
    @see executor
832  
    @see executor
801  
*/
833  
*/
802  
template<Executor Ex, detail::Allocator Alloc, class H1>
834  
template<Executor Ex, detail::Allocator Alloc, class H1>
803  
[[nodiscard]] auto
835  
[[nodiscard]] auto
804  
run_async(Ex ex, Alloc alloc, H1 h1)
836  
run_async(Ex ex, Alloc alloc, H1 h1)
805  
{
837  
{
806  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
838  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
807  
        std::move(ex),
839  
        std::move(ex),
808  
        std::stop_token{},
840  
        std::stop_token{},
809  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
841  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
810  
        std::move(alloc));
842  
        std::move(alloc));
811  
}
843  
}
812  

844  

813  
/** Asynchronously launch a lazy task with allocator and handlers.
845  
/** Asynchronously launch a lazy task with allocator and handlers.
814  

846  

815  
    @param ex The executor to execute the task on.
847  
    @param ex The executor to execute the task on.
816  
    @param alloc The allocator for frame allocation (copied and stored).
848  
    @param alloc The allocator for frame allocation (copied and stored).
817  
    @param h1 The handler to invoke with the result on success.
849  
    @param h1 The handler to invoke with the result on success.
818  
    @param h2 The handler to invoke with the exception on failure.
850  
    @param h2 The handler to invoke with the exception on failure.
819  

851  

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

853  

822  
    @see task
854  
    @see task
823  
    @see executor
855  
    @see executor
824  
*/
856  
*/
825  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
857  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
826  
[[nodiscard]] auto
858  
[[nodiscard]] auto
827  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
859  
run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
828  
{
860  
{
829  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
861  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
830  
        std::move(ex),
862  
        std::move(ex),
831  
        std::stop_token{},
863  
        std::stop_token{},
832  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
864  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
833  
        std::move(alloc));
865  
        std::move(alloc));
834  
}
866  
}
835  

867  

836  
// Ex + stop_token + standard Allocator
868  
// Ex + stop_token + standard Allocator
837  

869  

838  
/** Asynchronously launch a lazy task with stop token and allocator.
870  
/** Asynchronously launch a lazy task with stop token and allocator.
839  

871  

840  
    @param ex The executor to execute the task on.
872  
    @param ex The executor to execute the task on.
841  
    @param st The stop token for cooperative cancellation.
873  
    @param st The stop token for cooperative cancellation.
842  
    @param alloc The allocator for frame allocation (copied and stored).
874  
    @param alloc The allocator for frame allocation (copied and stored).
843  

875  

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

877  

846  
    @see task
878  
    @see task
847  
    @see executor
879  
    @see executor
848  
*/
880  
*/
849  
template<Executor Ex, detail::Allocator Alloc>
881  
template<Executor Ex, detail::Allocator Alloc>
850  
[[nodiscard]] auto
882  
[[nodiscard]] auto
851  
run_async(Ex ex, std::stop_token st, Alloc alloc)
883  
run_async(Ex ex, std::stop_token st, Alloc alloc)
852  
{
884  
{
853  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
885  
    return run_async_wrapper<Ex, detail::default_handler, Alloc>(
854  
        std::move(ex),
886  
        std::move(ex),
855  
        std::move(st),
887  
        std::move(st),
856  
        detail::default_handler{},
888  
        detail::default_handler{},
857  
        std::move(alloc));
889  
        std::move(alloc));
858  
}
890  
}
859  

891  

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

893  

862  
    @param ex The executor to execute the task on.
894  
    @param ex The executor to execute the task on.
863  
    @param st The stop token for cooperative cancellation.
895  
    @param st The stop token for cooperative cancellation.
864  
    @param alloc The allocator for frame allocation (copied and stored).
896  
    @param alloc The allocator for frame allocation (copied and stored).
865  
    @param h1 The handler to invoke with the result (and optionally exception).
897  
    @param h1 The handler to invoke with the result (and optionally exception).
866  

898  

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

900  

869  
    @see task
901  
    @see task
870  
    @see executor
902  
    @see executor
871  
*/
903  
*/
872  
template<Executor Ex, detail::Allocator Alloc, class H1>
904  
template<Executor Ex, detail::Allocator Alloc, class H1>
873  
[[nodiscard]] auto
905  
[[nodiscard]] auto
874  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
906  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
875  
{
907  
{
876  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
908  
    return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
877  
        std::move(ex),
909  
        std::move(ex),
878  
        std::move(st),
910  
        std::move(st),
879  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
911  
        detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
880  
        std::move(alloc));
912  
        std::move(alloc));
881  
}
913  
}
882  

914  

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

916  

885  
    @param ex The executor to execute the task on.
917  
    @param ex The executor to execute the task on.
886  
    @param st The stop token for cooperative cancellation.
918  
    @param st The stop token for cooperative cancellation.
887  
    @param alloc The allocator for frame allocation (copied and stored).
919  
    @param alloc The allocator for frame allocation (copied and stored).
888  
    @param h1 The handler to invoke with the result on success.
920  
    @param h1 The handler to invoke with the result on success.
889  
    @param h2 The handler to invoke with the exception on failure.
921  
    @param h2 The handler to invoke with the exception on failure.
890  

922  

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

924  

893  
    @see task
925  
    @see task
894  
    @see executor
926  
    @see executor
895  
*/
927  
*/
896  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
928  
template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
897  
[[nodiscard]] auto
929  
[[nodiscard]] auto
898  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
930  
run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
899  
{
931  
{
900  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
932  
    return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
901  
        std::move(ex),
933  
        std::move(ex),
902  
        std::move(st),
934  
        std::move(st),
903  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
935  
        detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
904  
        std::move(alloc));
936  
        std::move(alloc));
905  
}
937  
}
906  

938  

907  
} // namespace capy
939  
} // namespace capy
908  
} // namespace boost
940  
} // namespace boost
909  

941  

910  
#endif
942  
#endif