100.00% Lines (152/152) 100.00% Functions (35/35)
TLA Baseline Branch
Line Hits Code Line Hits Code
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 <exception> 27   #include <exception>
28   #include <memory_resource> 28   #include <memory_resource>
29   #include <new> 29   #include <new>
30   #include <stop_token> 30   #include <stop_token>
31   #include <type_traits> 31   #include <type_traits>
32   32  
33   namespace boost { 33   namespace boost {
34   namespace capy { 34   namespace capy {
35   namespace detail { 35   namespace detail {
36   36  
37   /// Function pointer type for type-erased frame deallocation. 37   /// Function pointer type for type-erased frame deallocation.
38   using dealloc_fn = void(*)(void*, std::size_t); 38   using dealloc_fn = void(*)(void*, std::size_t);
39   39  
40   /// Type-erased deallocator implementation for trampoline frames. 40   /// Type-erased deallocator implementation for trampoline frames.
41   template<class Alloc> 41   template<class Alloc>
HITCBC 42   2 void dealloc_impl(void* raw, std::size_t total) 42   2 void dealloc_impl(void* raw, std::size_t total)
43   { 43   {
44   static_assert(std::is_same_v<typename Alloc::value_type, std::byte>); 44   static_assert(std::is_same_v<typename Alloc::value_type, std::byte>);
HITCBC 45   2 auto* a = std::launder(reinterpret_cast<Alloc*>( 45   2 auto* a = std::launder(reinterpret_cast<Alloc*>(
HITCBC 46   2 static_cast<char*>(raw) + total - sizeof(Alloc))); 46   2 static_cast<char*>(raw) + total - sizeof(Alloc)));
HITCBC 47   2 Alloc ba(std::move(*a)); 47   2 Alloc ba(std::move(*a));
48   a->~Alloc(); 48   a->~Alloc();
49   ba.deallocate(static_cast<std::byte*>(raw), total); 49   ba.deallocate(static_cast<std::byte*>(raw), total);
HITCBC 50   2 } 50   2 }
51   51  
52   /// Awaiter to access the promise from within the coroutine. 52   /// Awaiter to access the promise from within the coroutine.
53   template<class Promise> 53   template<class Promise>
54   struct get_promise_awaiter 54   struct get_promise_awaiter
55   { 55   {
56   Promise* p_ = nullptr; 56   Promise* p_ = nullptr;
57   57  
HITCBC 58   2975 bool await_ready() const noexcept { return false; } 58   2952 bool await_ready() const noexcept { return false; }
59   59  
HITCBC 60   2975 bool await_suspend(std::coroutine_handle<Promise> h) noexcept 60   2952 bool await_suspend(std::coroutine_handle<Promise> h) noexcept
61   { 61   {
HITCBC 62   2975 p_ = &h.promise(); 62   2952 p_ = &h.promise();
HITCBC 63   2975 return false; 63   2952 return false;
64   } 64   }
65   65  
HITCBC 66   2975 Promise& await_resume() const noexcept 66   2952 Promise& await_resume() const noexcept
67   { 67   {
HITCBC 68   2975 return *p_; 68   2952 return *p_;
69   } 69   }
70   }; 70   };
71   71  
72   /** Internal run_async_trampoline coroutine for run_async. 72   /** Internal run_async_trampoline coroutine for run_async.
73   73  
74   The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation 74   The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation
75   order) and serves as the task's continuation. When the task final_suspends, 75   order) and serves as the task's continuation. When the task final_suspends,
76   control returns to the run_async_trampoline which then invokes the appropriate handler. 76   control returns to the run_async_trampoline which then invokes the appropriate handler.
77   77  
78   For value-type allocators, the run_async_trampoline stores a frame_memory_resource 78   For value-type allocators, the run_async_trampoline stores a frame_memory_resource
79   that wraps the allocator. For memory_resource*, it stores the pointer directly. 79   that wraps the allocator. For memory_resource*, it stores the pointer directly.
80   80  
81   @tparam Ex The executor type. 81   @tparam Ex The executor type.
82   @tparam Handlers The handler type (default_handler or handler_pair). 82   @tparam Handlers The handler type (default_handler or handler_pair).
83   @tparam Alloc The allocator type (value type or memory_resource*). 83   @tparam Alloc The allocator type (value type or memory_resource*).
84   */ 84   */
85   template<class Ex, class Handlers, class Alloc> 85   template<class Ex, class Handlers, class Alloc>
86   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE run_async_trampoline 86   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE run_async_trampoline
87   { 87   {
88   using invoke_fn = void(*)(void*, Handlers&); 88   using invoke_fn = void(*)(void*, Handlers&);
89   89  
90   struct promise_type 90   struct promise_type
91   { 91   {
92   work_guard<Ex> wg_; 92   work_guard<Ex> wg_;
93   Handlers handlers_; 93   Handlers handlers_;
94   frame_memory_resource<Alloc> resource_; 94   frame_memory_resource<Alloc> resource_;
95   io_env env_; 95   io_env env_;
96   invoke_fn invoke_ = nullptr; 96   invoke_fn invoke_ = nullptr;
97   void* task_promise_ = nullptr; 97   void* task_promise_ = nullptr;
98   // task_h_: raw handle for frame_guard cleanup in make_trampoline. 98   // task_h_: raw handle for frame_guard cleanup in make_trampoline.
99   // task_cont_: continuation wrapping the same handle for executor dispatch. 99   // task_cont_: continuation wrapping the same handle for executor dispatch.
100   // Both must reference the same coroutine and be kept in sync. 100   // Both must reference the same coroutine and be kept in sync.
101   std::coroutine_handle<> task_h_; 101   std::coroutine_handle<> task_h_;
102   continuation task_cont_; 102   continuation task_cont_;
103   103  
HITCBC 104   2 promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept 104   2 promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
HITCBC 105   2 : wg_(std::move(ex)) 105   2 : wg_(std::move(ex))
HITCBC 106   2 , handlers_(std::move(h)) 106   2 , handlers_(std::move(h))
HITCBC 107   2 , resource_(std::move(a)) 107   2 , resource_(std::move(a))
108   { 108   {
HITCBC 109   2 } 109   2 }
110   110  
HITCBC 111   2 static void* operator new( 111   2 static void* operator new(
112   std::size_t size, Ex const&, Handlers const&, Alloc a) 112   std::size_t size, Ex const&, Handlers const&, Alloc a)
113   { 113   {
114   using byte_alloc = typename std::allocator_traits<Alloc> 114   using byte_alloc = typename std::allocator_traits<Alloc>
115   ::template rebind_alloc<std::byte>; 115   ::template rebind_alloc<std::byte>;
116   116  
HITCBC 117   2 constexpr auto footer_align = 117   2 constexpr auto footer_align =
118   (std::max)(alignof(dealloc_fn), alignof(Alloc)); 118   (std::max)(alignof(dealloc_fn), alignof(Alloc));
HITCBC 119   2 auto padded = (size + footer_align - 1) & ~(footer_align - 1); 119   2 auto padded = (size + footer_align - 1) & ~(footer_align - 1);
HITCBC 120   2 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc); 120   2 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
121   121  
122   byte_alloc ba(std::move(a)); 122   byte_alloc ba(std::move(a));
HITCBC 123   2 void* raw = ba.allocate(total); 123   2 void* raw = ba.allocate(total);
124   124  
HITCBC 125   2 auto* fn_loc = reinterpret_cast<dealloc_fn*>( 125   2 auto* fn_loc = reinterpret_cast<dealloc_fn*>(
126   static_cast<char*>(raw) + padded); 126   static_cast<char*>(raw) + padded);
HITCBC 127   2 *fn_loc = &dealloc_impl<byte_alloc>; 127   2 *fn_loc = &dealloc_impl<byte_alloc>;
128   128  
HITCBC 129   2 new (fn_loc + 1) byte_alloc(std::move(ba)); 129   2 new (fn_loc + 1) byte_alloc(std::move(ba));
130   130  
HITCBC 131   4 return raw; 131   4 return raw;
132   } 132   }
133   133  
HITCBC 134   2 static void operator delete(void* ptr, std::size_t size) 134   2 static void operator delete(void* ptr, std::size_t size)
135   { 135   {
HITCBC 136   2 constexpr auto footer_align = 136   2 constexpr auto footer_align =
137   (std::max)(alignof(dealloc_fn), alignof(Alloc)); 137   (std::max)(alignof(dealloc_fn), alignof(Alloc));
HITCBC 138   2 auto padded = (size + footer_align - 1) & ~(footer_align - 1); 138   2 auto padded = (size + footer_align - 1) & ~(footer_align - 1);
HITCBC 139   2 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc); 139   2 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
140   140  
HITCBC 141   2 auto* fn = reinterpret_cast<dealloc_fn*>( 141   2 auto* fn = reinterpret_cast<dealloc_fn*>(
142   static_cast<char*>(ptr) + padded); 142   static_cast<char*>(ptr) + padded);
HITCBC 143   2 (*fn)(ptr, total); 143   2 (*fn)(ptr, total);
HITCBC 144   2 } 144   2 }
145   145  
HITCBC 146   4 std::pmr::memory_resource* get_resource() noexcept 146   4 std::pmr::memory_resource* get_resource() noexcept
147   { 147   {
HITCBC 148   4 return &resource_; 148   4 return &resource_;
149   } 149   }
150   150  
HITCBC 151   2 run_async_trampoline get_return_object() noexcept 151   2 run_async_trampoline get_return_object() noexcept
152   { 152   {
153   return run_async_trampoline{ 153   return run_async_trampoline{
HITCBC 154   2 std::coroutine_handle<promise_type>::from_promise(*this)}; 154   2 std::coroutine_handle<promise_type>::from_promise(*this)};
155   } 155   }
156   156  
HITCBC 157   2 std::suspend_always initial_suspend() noexcept 157   2 std::suspend_always initial_suspend() noexcept
158   { 158   {
HITCBC 159   2 return {}; 159   2 return {};
160   } 160   }
161   161  
HITCBC 162   2 std::suspend_never final_suspend() noexcept 162   2 std::suspend_never final_suspend() noexcept
163   { 163   {
HITCBC 164   2 return {}; 164   2 return {};
165   } 165   }
166   166  
HITCBC 167   2 void return_void() noexcept 167   2 void return_void() noexcept
168   { 168   {
HITCBC 169   2 } 169   2 }
170   170  
171   // An exception reaches here only by escaping a handler: a handler 171   // An exception reaches here only by escaping a handler: a handler
172   // that threw, or the default handler rethrowing an otherwise 172   // that threw, or the default handler rethrowing an otherwise
173   // unhandled task exception. Cancellation is filtered out earlier 173   // unhandled task exception. Cancellation is filtered out earlier
174   // by default_handler, so this is always a genuine error with no 174   // by default_handler, so this is always a genuine error with no
175   // owner to receive it: fail fast. 175   // owner to receive it: fail fast.
176   void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE 176   void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE
177   }; 177   };
178   178  
179   std::coroutine_handle<promise_type> h_; 179   std::coroutine_handle<promise_type> h_;
180   180  
181   template<IoRunnable Task> 181   template<IoRunnable Task>
HITCBC 182   2 static void invoke_impl(void* p, Handlers& h) 182   2 static void invoke_impl(void* p, Handlers& h)
183   { 183   {
184   using R = decltype(std::declval<Task&>().await_resume()); 184   using R = decltype(std::declval<Task&>().await_resume());
HITCBC 185   2 auto& promise = *static_cast<typename Task::promise_type*>(p); 185   2 auto& promise = *static_cast<typename Task::promise_type*>(p);
HITCBC 186   2 if(promise.exception()) 186   2 if(promise.exception())
HITCBC 187   1 h(promise.exception()); 187   1 h(promise.exception());
188   else if constexpr(std::is_void_v<R>) 188   else if constexpr(std::is_void_v<R>)
189   h(); 189   h();
190   else 190   else
HITCBC 191   1 h(std::move(promise.result())); 191   1 h(std::move(promise.result()));
HITCBC 192   2 } 192   2 }
193   }; 193   };
194   194  
195   /** Specialization for memory_resource* - stores pointer directly. 195   /** Specialization for memory_resource* - stores pointer directly.
196   196  
197   This avoids double indirection when the user passes a memory_resource*. 197   This avoids double indirection when the user passes a memory_resource*.
198   */ 198   */
199   template<class Ex, class Handlers> 199   template<class Ex, class Handlers>
200   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE 200   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE
201   run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*> 201   run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
202   { 202   {
203   using invoke_fn = void(*)(void*, Handlers&); 203   using invoke_fn = void(*)(void*, Handlers&);
204   204  
205   struct promise_type 205   struct promise_type
206   { 206   {
207   work_guard<Ex> wg_; 207   work_guard<Ex> wg_;
208   Handlers handlers_; 208   Handlers handlers_;
209   std::pmr::memory_resource* mr_; 209   std::pmr::memory_resource* mr_;
210   io_env env_; 210   io_env env_;
211   invoke_fn invoke_ = nullptr; 211   invoke_fn invoke_ = nullptr;
212   void* task_promise_ = nullptr; 212   void* task_promise_ = nullptr;
213   // task_h_: raw handle for frame_guard cleanup in make_trampoline. 213   // task_h_: raw handle for frame_guard cleanup in make_trampoline.
214   // task_cont_: continuation wrapping the same handle for executor dispatch. 214   // task_cont_: continuation wrapping the same handle for executor dispatch.
215   // Both must reference the same coroutine and be kept in sync. 215   // Both must reference the same coroutine and be kept in sync.
216   std::coroutine_handle<> task_h_; 216   std::coroutine_handle<> task_h_;
217   continuation task_cont_; 217   continuation task_cont_;
218   218  
HITCBC 219   3103 promise_type( 219   3111 promise_type(
220   Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept 220   Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
HITCBC 221   3103 : wg_(std::move(ex)) 221   3111 : wg_(std::move(ex))
HITCBC 222   3103 , handlers_(std::move(h)) 222   3111 , handlers_(std::move(h))
HITCBC 223   3103 , mr_(mr) 223   3111 , mr_(mr)
224   { 224   {
HITCBC 225   3103 } 225   3111 }
226   226  
HITCBC 227   3103 static void* operator new( 227   3111 static void* operator new(
228   std::size_t size, Ex const&, Handlers const&, 228   std::size_t size, Ex const&, Handlers const&,
229   std::pmr::memory_resource* mr) 229   std::pmr::memory_resource* mr)
230   { 230   {
HITCBC 231   3103 auto total = size + sizeof(mr); 231   3111 auto total = size + sizeof(mr);
HITCBC 232   3103 void* raw = mr->allocate(total, alignof(std::max_align_t)); 232   3111 void* raw = mr->allocate(total, alignof(std::max_align_t));
HITCBC 233   3103 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr)); 233   3111 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
HITCBC 234   3103 return raw; 234   3111 return raw;
235   } 235   }
236   236  
HITCBC 237   3103 static void operator delete(void* ptr, std::size_t size) 237   3111 static void operator delete(void* ptr, std::size_t size)
238   { 238   {
239   std::pmr::memory_resource* mr; 239   std::pmr::memory_resource* mr;
HITCBC 240   3103 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr)); 240   3111 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
HITCBC 241   3103 auto total = size + sizeof(mr); 241   3111 auto total = size + sizeof(mr);
HITCBC 242   3103 mr->deallocate(ptr, total, alignof(std::max_align_t)); 242   3111 mr->deallocate(ptr, total, alignof(std::max_align_t));
HITCBC 243   3103 } 243   3111 }
244   244  
HITCBC 245   6206 std::pmr::memory_resource* get_resource() noexcept 245   6222 std::pmr::memory_resource* get_resource() noexcept
246   { 246   {
HITCBC 247   6206 return mr_; 247   6222 return mr_;
248   } 248   }
249   249  
HITCBC 250   3103 run_async_trampoline get_return_object() noexcept 250   3111 run_async_trampoline get_return_object() noexcept
251   { 251   {
252   return run_async_trampoline{ 252   return run_async_trampoline{
HITCBC 253   3103 std::coroutine_handle<promise_type>::from_promise(*this)}; 253   3111 std::coroutine_handle<promise_type>::from_promise(*this)};
254   } 254   }
255   255  
HITCBC 256   3103 std::suspend_always initial_suspend() noexcept 256   3111 std::suspend_always initial_suspend() noexcept
257   { 257   {
HITCBC 258   3103 return {}; 258   3111 return {};
259   } 259   }
260   260  
HITCBC 261   2973 std::suspend_never final_suspend() noexcept 261   2950 std::suspend_never final_suspend() noexcept
262   { 262   {
HITCBC 263   2973 return {}; 263   2950 return {};
264   } 264   }
265   265  
HITCBC 266   2973 void return_void() noexcept 266   2950 void return_void() noexcept
267   { 267   {
HITCBC 268   2973 } 268   2950 }
269   269  
270   // See primary template: an escaping handler exception is fatal. 270   // See primary template: an escaping handler exception is fatal.
271   void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE 271   void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE
272   }; 272   };
273   273  
274   std::coroutine_handle<promise_type> h_; 274   std::coroutine_handle<promise_type> h_;
275   275  
276   template<IoRunnable Task> 276   template<IoRunnable Task>
HITCBC 277   2973 static void invoke_impl(void* p, Handlers& h) 277   2950 static void invoke_impl(void* p, Handlers& h)
278   { 278   {
279   using R = decltype(std::declval<Task&>().await_resume()); 279   using R = decltype(std::declval<Task&>().await_resume());
HITCBC 280   2973 auto& promise = *static_cast<typename Task::promise_type*>(p); 280   2950 auto& promise = *static_cast<typename Task::promise_type*>(p);
HITCBC 281   2973 if(promise.exception()) 281   2950 if(promise.exception())
HITCBC 282   913 h(promise.exception()); 282   922 h(promise.exception());
283   else if constexpr(std::is_void_v<R>) 283   else if constexpr(std::is_void_v<R>)
HITCBC 284   1903 h(); 284   1871 h();
285   else 285   else
HITCBC 286   157 h(std::move(promise.result())); 286   157 h(std::move(promise.result()));
HITCBC 287   2973 } 287   2950 }
288   }; 288   };
289   289  
290   /// Coroutine body for run_async_trampoline - invokes handlers then destroys task. 290   /// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
291   template<class Ex, class Handlers, class Alloc> 291   template<class Ex, class Handlers, class Alloc>
292   run_async_trampoline<Ex, Handlers, Alloc> 292   run_async_trampoline<Ex, Handlers, Alloc>
HITCBC 293   3105 make_trampoline(Ex, Handlers, Alloc) 293   3113 make_trampoline(Ex, Handlers, Alloc)
294   { 294   {
295   // promise_type ctor steals the parameters 295   // promise_type ctor steals the parameters
296   auto& p = co_await get_promise_awaiter< 296   auto& p = co_await get_promise_awaiter<
297   typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{}; 297   typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
298   298  
299   // Guard ensures the task frame is destroyed even when invoke_ 299   // Guard ensures the task frame is destroyed even when invoke_
300   // throws (e.g. default_handler rethrows an unhandled exception). 300   // throws (e.g. default_handler rethrows an unhandled exception).
301   struct frame_guard 301   struct frame_guard
302   { 302   {
303   std::coroutine_handle<>& h; 303   std::coroutine_handle<>& h;
HITCBC 304   2975 ~frame_guard() { h.destroy(); } 304   2952 ~frame_guard() { h.destroy(); }
305   } guard{p.task_h_}; 305   } guard{p.task_h_};
306   306  
307   p.invoke_(p.task_promise_, p.handlers_); 307   p.invoke_(p.task_promise_, p.handlers_);
HITCBC 308   6214 } 308   6230 }
309   309  
310   } // namespace detail 310   } // namespace detail
311   311  
312   /** Wrapper returned by run_async that accepts a task for execution. 312   /** Wrapper returned by run_async that accepts a task for execution.
313   313  
314   This wrapper holds the run_async_trampoline coroutine, executor, stop token, 314   This wrapper holds the run_async_trampoline coroutine, executor, stop token,
315   and handlers. The run_async_trampoline is allocated when the wrapper is constructed 315   and handlers. The run_async_trampoline is allocated when the wrapper is constructed
316   (before the task due to C++17 postfix evaluation order). 316   (before the task due to C++17 postfix evaluation order).
317   317  
318   The rvalue ref-qualifier on `operator()` ensures the wrapper can only 318   The rvalue ref-qualifier on `operator()` ensures the wrapper can only
319   be used as a temporary, preventing misuse that would violate LIFO ordering. 319   be used as a temporary, preventing misuse that would violate LIFO ordering.
320   320  
321   @tparam Ex The executor type satisfying the `Executor` concept. 321   @tparam Ex The executor type satisfying the `Executor` concept.
322   @tparam Handlers The handler type (default_handler or handler_pair). 322   @tparam Handlers The handler type (default_handler or handler_pair).
323   @tparam Alloc The allocator type (value type or memory_resource*). 323   @tparam Alloc The allocator type (value type or memory_resource*).
324   324  
325   @par Thread Safety 325   @par Thread Safety
326   The wrapper itself should only be used from one thread. The handlers 326   The wrapper itself should only be used from one thread. The handlers
327   may be invoked from any thread where the executor schedules work. 327   may be invoked from any thread where the executor schedules work.
328   328  
329   @par Example 329   @par Example
330   @code 330   @code
331   // Correct usage - wrapper is temporary 331   // Correct usage - wrapper is temporary
332   run_async(ex)(my_task()); 332   run_async(ex)(my_task());
333   333  
334   // Compile error - cannot call operator() on lvalue 334   // Compile error - cannot call operator() on lvalue
335   auto w = run_async(ex); 335   auto w = run_async(ex);
336   w(my_task()); // Error: operator() requires rvalue 336   w(my_task()); // Error: operator() requires rvalue
337   @endcode 337   @endcode
338   338  
339   @see run_async 339   @see run_async
340   */ 340   */
341   template<Executor Ex, class Handlers, class Alloc> 341   template<Executor Ex, class Handlers, class Alloc>
342   class [[nodiscard]] run_async_wrapper 342   class [[nodiscard]] run_async_wrapper
343   { 343   {
344   detail::run_async_trampoline<Ex, Handlers, Alloc> tr_; 344   detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
345   std::stop_token st_; 345   std::stop_token st_;
346   std::pmr::memory_resource* saved_tls_; 346   std::pmr::memory_resource* saved_tls_;
347   347  
348   public: 348   public:
349   /** Construct the wrapper and install the frame allocator. 349   /** Construct the wrapper and install the frame allocator.
350   350  
351   Builds the trampoline, saves the current thread-local frame 351   Builds the trampoline, saves the current thread-local frame
352   allocator, and installs the trampoline's resource as the new 352   allocator, and installs the trampoline's resource as the new
353   thread-local allocator so that the task frame (evaluated as the 353   thread-local allocator so that the task frame (evaluated as the
354   argument to @ref operator()) is allocated from it. 354   argument to @ref operator()) is allocated from it.
355   355  
356   @param ex The executor on which the task runs. 356   @param ex The executor on which the task runs.
357   @param st The stop token for cooperative cancellation. 357   @param st The stop token for cooperative cancellation.
358   @param h The completion handlers. 358   @param h The completion handlers.
359   @param a The allocator for frame allocation. 359   @param a The allocator for frame allocation.
360   360  
361   @note When `Alloc` is not `std::pmr::memory_resource*` it must be 361   @note When `Alloc` is not `std::pmr::memory_resource*` it must be
362   nothrow move constructible (enforced by a `static_assert`), which 362   nothrow move constructible (enforced by a `static_assert`), which
363   is what allows this constructor to be `noexcept`. 363   is what allows this constructor to be `noexcept`.
364   */ 364   */
HITCBC 365   3105 run_async_wrapper( 365   3113 run_async_wrapper(
366   Ex ex, 366   Ex ex,
367   std::stop_token st, 367   std::stop_token st,
368   Handlers h, 368   Handlers h,
369   Alloc a) noexcept 369   Alloc a) noexcept
HITCBC 370   3106 : tr_(detail::make_trampoline<Ex, Handlers, Alloc>( 370   3114 : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
HITCBC 371   3108 std::move(ex), std::move(h), std::move(a))) 371   3116 std::move(ex), std::move(h), std::move(a)))
HITCBC 372   3105 , st_(std::move(st)) 372   3113 , st_(std::move(st))
HITCBC 373   3105 , saved_tls_(get_current_frame_allocator()) 373   3113 , saved_tls_(get_current_frame_allocator())
374   { 374   {
375   if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>) 375   if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
376   { 376   {
377   static_assert( 377   static_assert(
378   std::is_nothrow_move_constructible_v<Alloc>, 378   std::is_nothrow_move_constructible_v<Alloc>,
379   "Allocator must be nothrow move constructible"); 379   "Allocator must be nothrow move constructible");
380   } 380   }
381   // Set TLS before task argument is evaluated 381   // Set TLS before task argument is evaluated
HITCBC 382   3105 set_current_frame_allocator(tr_.h_.promise().get_resource()); 382   3113 set_current_frame_allocator(tr_.h_.promise().get_resource());
HITCBC 383   3105 } 383   3113 }
384   384  
385   /** Restore the previously installed frame allocator. 385   /** Restore the previously installed frame allocator.
386   386  
387   Resets the thread-local frame allocator to the value saved at 387   Resets the thread-local frame allocator to the value saved at
388   construction, so a stale pointer to the trampoline's resource does 388   construction, so a stale pointer to the trampoline's resource does
389   not outlive the execution context that owns it. 389   not outlive the execution context that owns it.
390   */ 390   */
HITCBC 391   3105 ~run_async_wrapper() 391   3113 ~run_async_wrapper()
392   { 392   {
HITCBC 393   3105 set_current_frame_allocator(saved_tls_); 393   3113 set_current_frame_allocator(saved_tls_);
HITCBC 394   3105 } 394   3113 }
395   395  
396   // Non-copyable, non-movable (must be used immediately) 396   // Non-copyable, non-movable (must be used immediately)
397   run_async_wrapper(run_async_wrapper const&) = delete; 397   run_async_wrapper(run_async_wrapper const&) = delete;
398   run_async_wrapper(run_async_wrapper&&) = delete; 398   run_async_wrapper(run_async_wrapper&&) = delete;
399   run_async_wrapper& operator=(run_async_wrapper const&) = delete; 399   run_async_wrapper& operator=(run_async_wrapper const&) = delete;
400   run_async_wrapper& operator=(run_async_wrapper&&) = delete; 400   run_async_wrapper& operator=(run_async_wrapper&&) = delete;
401   401  
402   /** Launch the task for execution. 402   /** Launch the task for execution.
403   403  
404   This operator accepts a task and launches it on the executor. 404   This operator accepts a task and launches it on the executor.
405   The rvalue ref-qualifier ensures the wrapper is consumed, enforcing 405   The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
406   correct LIFO destruction order. 406   correct LIFO destruction order.
407   407  
408   The `io_env` constructed for the task is owned by the trampoline 408   The `io_env` constructed for the task is owned by the trampoline
409   coroutine and is guaranteed to outlive the task and all awaitables 409   coroutine and is guaranteed to outlive the task and all awaitables
410   in its chain. Awaitables may store `io_env const*` without concern 410   in its chain. Awaitables may store `io_env const*` without concern
411   for dangling references. 411   for dangling references.
412   412  
413   @tparam Task The IoRunnable type. 413   @tparam Task The IoRunnable type.
414   414  
415   @param t The task to execute. Ownership is transferred to the 415   @param t The task to execute. Ownership is transferred to the
416   run_async_trampoline which will destroy it after completion. 416   run_async_trampoline which will destroy it after completion.
417   */ 417   */
418   template<IoRunnable Task> 418   template<IoRunnable Task>
HITCBC 419   3105 void operator()(Task t) && 419   3113 void operator()(Task t) &&
420   { 420   {
HITCBC 421   3105 auto task_h = t.handle(); 421   3113 auto task_h = t.handle();
HITCBC 422   3105 auto& task_promise = task_h.promise(); 422   3113 auto& task_promise = task_h.promise();
HITCBC 423   3105 t.release(); 423   3113 t.release();
424   424  
HITCBC 425   3105 auto& p = tr_.h_.promise(); 425   3113 auto& p = tr_.h_.promise();
426   426  
427   // Inject Task-specific invoke function 427   // Inject Task-specific invoke function
HITCBC 428   3105 p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>; 428   3113 p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
HITCBC 429   3105 p.task_promise_ = &task_promise; 429   3113 p.task_promise_ = &task_promise;
HITCBC 430   3105 p.task_h_ = task_h; 430   3113 p.task_h_ = task_h;
431   431  
432   // Setup task's continuation to return to run_async_trampoline 432   // Setup task's continuation to return to run_async_trampoline
HITCBC 433   3105 task_promise.set_continuation(tr_.h_); 433   3113 task_promise.set_continuation(tr_.h_);
HITCBC 434   6210 p.env_ = {p.wg_.executor(), st_, p.get_resource()}; 434   6226 p.env_ = {p.wg_.executor(), st_, p.get_resource()};
HITCBC 435   3105 task_promise.set_environment(&p.env_); 435   3113 task_promise.set_environment(&p.env_);
436   436  
437   // Start task through executor. 437   // Start task through executor.
438   // safe_resume is not needed here: TLS is already saved in the 438   // safe_resume is not needed here: TLS is already saved in the
439   // constructor (saved_tls_) and restored in the destructor. 439   // constructor (saved_tls_) and restored in the destructor.
HITCBC 440   3105 p.task_cont_.h = task_h; 440   3113 p.task_cont_.h = task_h;
HITCBC 441   3105 p.wg_.executor().dispatch(p.task_cont_).resume(); 441   3113 p.wg_.executor().dispatch(p.task_cont_).resume();
HITCBC 442   6210 } 442   6226 }
443   }; 443   };
444   444  
445   // Executor only (uses default recycling allocator) 445   // Executor only (uses default recycling allocator)
446   446  
447   /** Asynchronously launch a lazy task on the given executor. 447   /** Asynchronously launch a lazy task on the given executor.
448   448  
449   Use this to start execution of a `task<T>` that was created lazily. 449   Use this to start execution of a `task<T>` that was created lazily.
450   The returned wrapper must be immediately invoked with the task; 450   The returned wrapper must be immediately invoked with the task;
451   storing the wrapper and calling it later violates LIFO ordering. 451   storing the wrapper and calling it later violates LIFO ordering.
452   452  
453   Uses the default recycling frame allocator for coroutine frames. 453   Uses the default recycling frame allocator for coroutine frames.
454   With no handlers, the result is discarded. An unhandled exception 454   With no handlers, the result is discarded. An unhandled exception
455   thrown by the task calls `std::terminate`; pass an error handler to 455   thrown by the task calls `std::terminate`; pass an error handler to
456   receive it as an `exception_ptr`, or `co_await` the work inside a 456   receive it as an `exception_ptr`, or `co_await` the work inside a
457   coroutine if you want to catch it. 457   coroutine if you want to catch it.
458   458  
459   @par Thread Safety 459   @par Thread Safety
460   The wrapper and handlers may be called from any thread where the 460   The wrapper and handlers may be called from any thread where the
461   executor schedules work. 461   executor schedules work.
462   462  
463   @par Example 463   @par Example
464   @code 464   @code
465   run_async(ioc.get_executor())(my_task()); 465   run_async(ioc.get_executor())(my_task());
466   @endcode 466   @endcode
467   467  
468   @param ex The executor to execute the task on. 468   @param ex The executor to execute the task on.
469   469  
470   @return A wrapper that accepts a `task<T>` for immediate execution. 470   @return A wrapper that accepts a `task<T>` for immediate execution.
471   471  
472   @see task 472   @see task
473   @see executor 473   @see executor
474   */ 474   */
475   template<Executor Ex> 475   template<Executor Ex>
476   [[nodiscard]] auto 476   [[nodiscard]] auto
HITCBC 477   45 run_async(Ex ex) 477   28 run_async(Ex ex)
478   { 478   {
HITCBC 479   45 auto* mr = ex.context().get_frame_allocator(); 479   28 auto* mr = ex.context().get_frame_allocator();
480   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>( 480   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
HITCBC 481   45 std::move(ex), 481   28 std::move(ex),
HITCBC 482   90 std::stop_token{}, 482   56 std::stop_token{},
483   detail::default_handler{}, 483   detail::default_handler{},
HITCBC 484   45 mr); 484   28 mr);
485   } 485   }
486   486  
487   /** Asynchronously launch a lazy task with a result handler. 487   /** Asynchronously launch a lazy task with a result handler.
488   488  
489   The handler `h1` is called with the task's result on success. If `h1` 489   The handler `h1` is called with the task's result on success. If `h1`
490   is also invocable with `std::exception_ptr`, it handles exceptions too. 490   is also invocable with `std::exception_ptr`, it handles exceptions too.
491   Otherwise, an unhandled exception calls `std::terminate`. 491   Otherwise, an unhandled exception calls `std::terminate`.
492   492  
493   @par Thread Safety 493   @par Thread Safety
494   The handler may be called from any thread where the executor 494   The handler may be called from any thread where the executor
495   schedules work. 495   schedules work.
496   496  
497   @par Example 497   @par Example
498   @code 498   @code
499   // Handler for result only (exceptions rethrown) 499   // Handler for result only (exceptions rethrown)
500   run_async(ex, [](int result) { 500   run_async(ex, [](int result) {
501   std::cout << "Got: " << result << "\n"; 501   std::cout << "Got: " << result << "\n";
502   })(compute_value()); 502   })(compute_value());
503   503  
504   // Overloaded handler for both result and exception 504   // Overloaded handler for both result and exception
505   run_async(ex, overloaded{ 505   run_async(ex, overloaded{
506   [](int result) { std::cout << "Got: " << result << "\n"; }, 506   [](int result) { std::cout << "Got: " << result << "\n"; },
507   [](std::exception_ptr) { std::cout << "Failed\n"; } 507   [](std::exception_ptr) { std::cout << "Failed\n"; }
508   })(compute_value()); 508   })(compute_value());
509   @endcode 509   @endcode
510   510  
511   @param ex The executor to execute the task on. 511   @param ex The executor to execute the task on.
512   @param h1 The handler to invoke with the result (and optionally exception). 512   @param h1 The handler to invoke with the result (and optionally exception).
513   513  
514   @return A wrapper that accepts a `task<T>` for immediate execution. 514   @return A wrapper that accepts a `task<T>` for immediate execution.
515   515  
516   @see task 516   @see task
517   @see executor 517   @see executor
518   */ 518   */
519   template<Executor Ex, class H1> 519   template<Executor Ex, class H1>
520   [[nodiscard]] auto 520   [[nodiscard]] auto
HITCBC 521   94 run_async(Ex ex, H1 h1) 521   94 run_async(Ex ex, H1 h1)
522   { 522   {
HITCBC 523   94 auto* mr = ex.context().get_frame_allocator(); 523   94 auto* mr = ex.context().get_frame_allocator();
524   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>( 524   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
HITCBC 525   94 std::move(ex), 525   94 std::move(ex),
HITCBC 526   94 std::stop_token{}, 526   94 std::stop_token{},
HITCBC 527   94 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 527   94 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 528   188 mr); 528   188 mr);
529   } 529   }
530   530  
531   /** Asynchronously launch a lazy task with separate result and error handlers. 531   /** Asynchronously launch a lazy task with separate result and error handlers.
532   532  
533   The handler `h1` is called with the task's result on success. 533   The handler `h1` is called with the task's result on success.
534   The handler `h2` is called with the exception_ptr on failure. 534   The handler `h2` is called with the exception_ptr on failure.
535   535  
536   @par Thread Safety 536   @par Thread Safety
537   The handlers may be called from any thread where the executor 537   The handlers may be called from any thread where the executor
538   schedules work. 538   schedules work.
539   539  
540   @par Example 540   @par Example
541   @code 541   @code
542   run_async(ex, 542   run_async(ex,
543   [](int result) { std::cout << "Got: " << result << "\n"; }, 543   [](int result) { std::cout << "Got: " << result << "\n"; },
544   [](std::exception_ptr ep) { 544   [](std::exception_ptr ep) {
545   try { std::rethrow_exception(ep); } 545   try { std::rethrow_exception(ep); }
546   catch (std::exception const& e) { 546   catch (std::exception const& e) {
547   std::cout << "Error: " << e.what() << "\n"; 547   std::cout << "Error: " << e.what() << "\n";
548   } 548   }
549   } 549   }
550   )(compute_value()); 550   )(compute_value());
551   @endcode 551   @endcode
552   552  
553   @param ex The executor to execute the task on. 553   @param ex The executor to execute the task on.
554   @param h1 The handler to invoke with the result on success. 554   @param h1 The handler to invoke with the result on success.
555   @param h2 The handler to invoke with the exception on failure. 555   @param h2 The handler to invoke with the exception on failure.
556   556  
557   @return A wrapper that accepts a `task<T>` for immediate execution. 557   @return A wrapper that accepts a `task<T>` for immediate execution.
558   558  
559   @see task 559   @see task
560   @see executor 560   @see executor
561   */ 561   */
562   template<Executor Ex, class H1, class H2> 562   template<Executor Ex, class H1, class H2>
563   [[nodiscard]] auto 563   [[nodiscard]] auto
HITCBC 564   91 run_async(Ex ex, H1 h1, H2 h2) 564   91 run_async(Ex ex, H1 h1, H2 h2)
565   { 565   {
HITCBC 566   91 auto* mr = ex.context().get_frame_allocator(); 566   91 auto* mr = ex.context().get_frame_allocator();
567   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>( 567   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
HITCBC 568   91 std::move(ex), 568   91 std::move(ex),
HITCBC 569   91 std::stop_token{}, 569   91 std::stop_token{},
HITCBC 570   91 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 570   91 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
HITCBC 571   182 mr); 571   182 mr);
HITCBC 572   1 } 572   1 }
573   573  
574   // Ex + stop_token 574   // Ex + stop_token
575   575  
576   /** Asynchronously launch a lazy task with stop token support. 576   /** Asynchronously launch a lazy task with stop token support.
577   577  
578   The stop token is propagated to the task, enabling cooperative 578   The stop token is propagated to the task, enabling cooperative
579   cancellation. With no handlers, the result is discarded and an 579   cancellation. With no handlers, the result is discarded and an
580   unhandled exception calls `std::terminate`. 580   unhandled exception calls `std::terminate`.
581   581  
582   @par Thread Safety 582   @par Thread Safety
583   The wrapper may be called from any thread where the executor 583   The wrapper may be called from any thread where the executor
584   schedules work. 584   schedules work.
585   585  
586   @par Example 586   @par Example
587   @code 587   @code
588   std::stop_source source; 588   std::stop_source source;
589   run_async(ex, source.get_token())(cancellable_task()); 589   run_async(ex, source.get_token())(cancellable_task());
590   // Later: source.request_stop(); 590   // Later: source.request_stop();
591   @endcode 591   @endcode
592   592  
593   @param ex The executor to execute the task on. 593   @param ex The executor to execute the task on.
594   @param st The stop token for cooperative cancellation. 594   @param st The stop token for cooperative cancellation.
595   595  
596   @return A wrapper that accepts a `task<T>` for immediate execution. 596   @return A wrapper that accepts a `task<T>` for immediate execution.
597   597  
598   @see task 598   @see task
599   @see executor 599   @see executor
600   */ 600   */
601   template<Executor Ex> 601   template<Executor Ex>
602   [[nodiscard]] auto 602   [[nodiscard]] auto
HITCBC 603   363 run_async(Ex ex, std::stop_token st) 603   363 run_async(Ex ex, std::stop_token st)
604   { 604   {
HITCBC 605   363 auto* mr = ex.context().get_frame_allocator(); 605   363 auto* mr = ex.context().get_frame_allocator();
606   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>( 606   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
HITCBC 607   363 std::move(ex), 607   363 std::move(ex),
HITCBC 608   363 std::move(st), 608   363 std::move(st),
609   detail::default_handler{}, 609   detail::default_handler{},
HITCBC 610   726 mr); 610   726 mr);
611   } 611   }
612   612  
613   /** Asynchronously launch a lazy task with stop token and result handler. 613   /** Asynchronously launch a lazy task with stop token and result handler.
614   614  
615   The stop token is propagated to the task for cooperative cancellation. 615   The stop token is propagated to the task for cooperative cancellation.
616   The handler `h1` is called with the result on success, and optionally 616   The handler `h1` is called with the result on success, and optionally
617   with exception_ptr if it accepts that type. 617   with exception_ptr if it accepts that type.
618   618  
619   @param ex The executor to execute the task on. 619   @param ex The executor to execute the task on.
620   @param st The stop token for cooperative cancellation. 620   @param st The stop token for cooperative cancellation.
621   @param h1 The handler to invoke with the result (and optionally exception). 621   @param h1 The handler to invoke with the result (and optionally exception).
622   622  
623   @return A wrapper that accepts a `task<T>` for immediate execution. 623   @return A wrapper that accepts a `task<T>` for immediate execution.
624   624  
625   @see task 625   @see task
626   @see executor 626   @see executor
627   */ 627   */
628   template<Executor Ex, class H1> 628   template<Executor Ex, class H1>
629   [[nodiscard]] auto 629   [[nodiscard]] auto
HITCBC 630   2499 run_async(Ex ex, std::stop_token st, H1 h1) 630   2524 run_async(Ex ex, std::stop_token st, H1 h1)
631   { 631   {
HITCBC 632   2499 auto* mr = ex.context().get_frame_allocator(); 632   2524 auto* mr = ex.context().get_frame_allocator();
633   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>( 633   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
HITCBC 634   2499 std::move(ex), 634   2524 std::move(ex),
HITCBC 635   2499 std::move(st), 635   2524 std::move(st),
HITCBC 636   2499 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 636   2524 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 637   4998 mr); 637   5048 mr);
638   } 638   }
639   639  
640   /** Asynchronously launch a lazy task with stop token and separate handlers. 640   /** Asynchronously launch a lazy task with stop token and separate handlers.
641   641  
642   The stop token is propagated to the task for cooperative cancellation. 642   The stop token is propagated to the task for cooperative cancellation.
643   The handler `h1` is called on success, `h2` on failure. 643   The handler `h1` is called on success, `h2` on failure.
644   644  
645   @param ex The executor to execute the task on. 645   @param ex The executor to execute the task on.
646   @param st The stop token for cooperative cancellation. 646   @param st The stop token for cooperative cancellation.
647   @param h1 The handler to invoke with the result on success. 647   @param h1 The handler to invoke with the result on success.
648   @param h2 The handler to invoke with the exception on failure. 648   @param h2 The handler to invoke with the exception on failure.
649   649  
650   @return A wrapper that accepts a `task<T>` for immediate execution. 650   @return A wrapper that accepts a `task<T>` for immediate execution.
651   651  
652   @see task 652   @see task
653   @see executor 653   @see executor
654   */ 654   */
655   template<Executor Ex, class H1, class H2> 655   template<Executor Ex, class H1, class H2>
656   [[nodiscard]] auto 656   [[nodiscard]] auto
HITCBC 657   11 run_async(Ex ex, std::stop_token st, H1 h1, H2 h2) 657   11 run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
658   { 658   {
HITCBC 659   11 auto* mr = ex.context().get_frame_allocator(); 659   11 auto* mr = ex.context().get_frame_allocator();
660   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>( 660   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
HITCBC 661   11 std::move(ex), 661   11 std::move(ex),
HITCBC 662   11 std::move(st), 662   11 std::move(st),
HITCBC 663   11 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 663   11 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
HITCBC 664   22 mr); 664   22 mr);
665   } 665   }
666   666  
667   // Ex + memory_resource* 667   // Ex + memory_resource*
668   668  
669   /** Asynchronously launch a lazy task with custom memory resource. 669   /** Asynchronously launch a lazy task with custom memory resource.
670   670  
671   The memory resource is used for coroutine frame allocation. The caller 671   The memory resource is used for coroutine frame allocation. The caller
672   is responsible for ensuring the memory resource outlives all tasks. 672   is responsible for ensuring the memory resource outlives all tasks.
673   673  
674   @param ex The executor to execute the task on. 674   @param ex The executor to execute the task on.
675   @param mr The memory resource for frame allocation. 675   @param mr The memory resource for frame allocation.
676   676  
677   @return A wrapper that accepts a `task<T>` for immediate execution. 677   @return A wrapper that accepts a `task<T>` for immediate execution.
678   678  
679   @see task 679   @see task
680   @see executor 680   @see executor
681   */ 681   */
682   template<Executor Ex> 682   template<Executor Ex>
683   [[nodiscard]] auto 683   [[nodiscard]] auto
684   run_async(Ex ex, std::pmr::memory_resource* mr) 684   run_async(Ex ex, std::pmr::memory_resource* mr)
685   { 685   {
686   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>( 686   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
687   std::move(ex), 687   std::move(ex),
688   std::stop_token{}, 688   std::stop_token{},
689   detail::default_handler{}, 689   detail::default_handler{},
690   mr); 690   mr);
691   } 691   }
692   692  
693   /** Asynchronously launch a lazy task with memory resource and handler. 693   /** Asynchronously launch a lazy task with memory resource and handler.
694   694  
695   @param ex The executor to execute the task on. 695   @param ex The executor to execute the task on.
696   @param mr The memory resource for frame allocation. 696   @param mr The memory resource for frame allocation.
697   @param h1 The handler to invoke with the result (and optionally exception). 697   @param h1 The handler to invoke with the result (and optionally exception).
698   698  
699   @return A wrapper that accepts a `task<T>` for immediate execution. 699   @return A wrapper that accepts a `task<T>` for immediate execution.
700   700  
701   @see task 701   @see task
702   @see executor 702   @see executor
703   */ 703   */
704   template<Executor Ex, class H1> 704   template<Executor Ex, class H1>
705   [[nodiscard]] auto 705   [[nodiscard]] auto
706   run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1) 706   run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
707   { 707   {
708   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>( 708   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
709   std::move(ex), 709   std::move(ex),
710   std::stop_token{}, 710   std::stop_token{},
711   detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 711   detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
712   mr); 712   mr);
713   } 713   }
714   714  
715   /** Asynchronously launch a lazy task with memory resource and handlers. 715   /** Asynchronously launch a lazy task with memory resource and handlers.
716   716  
717   @param ex The executor to execute the task on. 717   @param ex The executor to execute the task on.
718   @param mr The memory resource for frame allocation. 718   @param mr The memory resource for frame allocation.
719   @param h1 The handler to invoke with the result on success. 719   @param h1 The handler to invoke with the result on success.
720   @param h2 The handler to invoke with the exception on failure. 720   @param h2 The handler to invoke with the exception on failure.
721   721  
722   @return A wrapper that accepts a `task<T>` for immediate execution. 722   @return A wrapper that accepts a `task<T>` for immediate execution.
723   723  
724   @see task 724   @see task
725   @see executor 725   @see executor
726   */ 726   */
727   template<Executor Ex, class H1, class H2> 727   template<Executor Ex, class H1, class H2>
728   [[nodiscard]] auto 728   [[nodiscard]] auto
729   run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2) 729   run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
730   { 730   {
731   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>( 731   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
732   std::move(ex), 732   std::move(ex),
733   std::stop_token{}, 733   std::stop_token{},
734   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 734   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
735   mr); 735   mr);
736   } 736   }
737   737  
738   // Ex + stop_token + memory_resource* 738   // Ex + stop_token + memory_resource*
739   739  
740   /** Asynchronously launch a lazy task with stop token and memory resource. 740   /** Asynchronously launch a lazy task with stop token and memory resource.
741   741  
742   @param ex The executor to execute the task on. 742   @param ex The executor to execute the task on.
743   @param st The stop token for cooperative cancellation. 743   @param st The stop token for cooperative cancellation.
744   @param mr The memory resource for frame allocation. 744   @param mr The memory resource for frame allocation.
745   745  
746   @return A wrapper that accepts a `task<T>` for immediate execution. 746   @return A wrapper that accepts a `task<T>` for immediate execution.
747   747  
748   @see task 748   @see task
749   @see executor 749   @see executor
750   */ 750   */
751   template<Executor Ex> 751   template<Executor Ex>
752   [[nodiscard]] auto 752   [[nodiscard]] auto
753   run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr) 753   run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
754   { 754   {
755   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>( 755   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
756   std::move(ex), 756   std::move(ex),
757   std::move(st), 757   std::move(st),
758   detail::default_handler{}, 758   detail::default_handler{},
759   mr); 759   mr);
760   } 760   }
761   761  
762   /** Asynchronously launch a lazy task with stop token, memory resource, and handler. 762   /** Asynchronously launch a lazy task with stop token, memory resource, and handler.
763   763  
764   @param ex The executor to execute the task on. 764   @param ex The executor to execute the task on.
765   @param st The stop token for cooperative cancellation. 765   @param st The stop token for cooperative cancellation.
766   @param mr The memory resource for frame allocation. 766   @param mr The memory resource for frame allocation.
767   @param h1 The handler to invoke with the result (and optionally exception). 767   @param h1 The handler to invoke with the result (and optionally exception).
768   768  
769   @return A wrapper that accepts a `task<T>` for immediate execution. 769   @return A wrapper that accepts a `task<T>` for immediate execution.
770   770  
771   @see task 771   @see task
772   @see executor 772   @see executor
773   */ 773   */
774   template<Executor Ex, class H1> 774   template<Executor Ex, class H1>
775   [[nodiscard]] auto 775   [[nodiscard]] auto
776   run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1) 776   run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
777   { 777   {
778   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>( 778   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
779   std::move(ex), 779   std::move(ex),
780   std::move(st), 780   std::move(st),
781   detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 781   detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
782   mr); 782   mr);
783   } 783   }
784   784  
785   /** Asynchronously launch a lazy task with stop token, memory resource, and handlers. 785   /** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
786   786  
787   @param ex The executor to execute the task on. 787   @param ex The executor to execute the task on.
788   @param st The stop token for cooperative cancellation. 788   @param st The stop token for cooperative cancellation.
789   @param mr The memory resource for frame allocation. 789   @param mr The memory resource for frame allocation.
790   @param h1 The handler to invoke with the result on success. 790   @param h1 The handler to invoke with the result on success.
791   @param h2 The handler to invoke with the exception on failure. 791   @param h2 The handler to invoke with the exception on failure.
792   792  
793   @return A wrapper that accepts a `task<T>` for immediate execution. 793   @return A wrapper that accepts a `task<T>` for immediate execution.
794   794  
795   @see task 795   @see task
796   @see executor 796   @see executor
797   */ 797   */
798   template<Executor Ex, class H1, class H2> 798   template<Executor Ex, class H1, class H2>
799   [[nodiscard]] auto 799   [[nodiscard]] auto
800   run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2) 800   run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
801   { 801   {
802   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>( 802   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
803   std::move(ex), 803   std::move(ex),
804   std::move(st), 804   std::move(st),
805   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 805   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
806   mr); 806   mr);
807   } 807   }
808   808  
809   // Ex + standard Allocator (value type) 809   // Ex + standard Allocator (value type)
810   810  
811   /** Asynchronously launch a lazy task with custom allocator. 811   /** Asynchronously launch a lazy task with custom allocator.
812   812  
813   The allocator is wrapped in a frame_memory_resource and stored in the 813   The allocator is wrapped in a frame_memory_resource and stored in the
814   run_async_trampoline, ensuring it outlives all coroutine frames. 814   run_async_trampoline, ensuring it outlives all coroutine frames.
815   815  
816   @param ex The executor to execute the task on. 816   @param ex The executor to execute the task on.
817   @param alloc The allocator for frame allocation (copied and stored). 817   @param alloc The allocator for frame allocation (copied and stored).
818   818  
819   @return A wrapper that accepts a `task<T>` for immediate execution. 819   @return A wrapper that accepts a `task<T>` for immediate execution.
820   820  
821   @see task 821   @see task
822   @see executor 822   @see executor
823   */ 823   */
824   template<Executor Ex, detail::Allocator Alloc> 824   template<Executor Ex, detail::Allocator Alloc>
825   [[nodiscard]] auto 825   [[nodiscard]] auto
826   run_async(Ex ex, Alloc alloc) 826   run_async(Ex ex, Alloc alloc)
827   { 827   {
828   return run_async_wrapper<Ex, detail::default_handler, Alloc>( 828   return run_async_wrapper<Ex, detail::default_handler, Alloc>(
829   std::move(ex), 829   std::move(ex),
830   std::stop_token{}, 830   std::stop_token{},
831   detail::default_handler{}, 831   detail::default_handler{},
832   std::move(alloc)); 832   std::move(alloc));
833   } 833   }
834   834  
835   /** Asynchronously launch a lazy task with allocator and handler. 835   /** Asynchronously launch a lazy task with allocator and handler.
836   836  
837   @param ex The executor to execute the task on. 837   @param ex The executor to execute the task on.
838   @param alloc The allocator for frame allocation (copied and stored). 838   @param alloc The allocator for frame allocation (copied and stored).
839   @param h1 The handler to invoke with the result (and optionally exception). 839   @param h1 The handler to invoke with the result (and optionally exception).
840   840  
841   @return A wrapper that accepts a `task<T>` for immediate execution. 841   @return A wrapper that accepts a `task<T>` for immediate execution.
842   842  
843   @see task 843   @see task
844   @see executor 844   @see executor
845   */ 845   */
846   template<Executor Ex, detail::Allocator Alloc, class H1> 846   template<Executor Ex, detail::Allocator Alloc, class H1>
847   [[nodiscard]] auto 847   [[nodiscard]] auto
HITCBC 848   1 run_async(Ex ex, Alloc alloc, H1 h1) 848   1 run_async(Ex ex, Alloc alloc, H1 h1)
849   { 849   {
850   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>( 850   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
HITCBC 851   1 std::move(ex), 851   1 std::move(ex),
HITCBC 852   1 std::stop_token{}, 852   1 std::stop_token{},
HITCBC 853   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 853   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 854   4 std::move(alloc)); 854   4 std::move(alloc));
855   } 855   }
856   856  
857   /** Asynchronously launch a lazy task with allocator and handlers. 857   /** Asynchronously launch a lazy task with allocator and handlers.
858   858  
859   @param ex The executor to execute the task on. 859   @param ex The executor to execute the task on.
860   @param alloc The allocator for frame allocation (copied and stored). 860   @param alloc The allocator for frame allocation (copied and stored).
861   @param h1 The handler to invoke with the result on success. 861   @param h1 The handler to invoke with the result on success.
862   @param h2 The handler to invoke with the exception on failure. 862   @param h2 The handler to invoke with the exception on failure.
863   863  
864   @return A wrapper that accepts a `task<T>` for immediate execution. 864   @return A wrapper that accepts a `task<T>` for immediate execution.
865   865  
866   @see task 866   @see task
867   @see executor 867   @see executor
868   */ 868   */
869   template<Executor Ex, detail::Allocator Alloc, class H1, class H2> 869   template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
870   [[nodiscard]] auto 870   [[nodiscard]] auto
HITCBC 871   1 run_async(Ex ex, Alloc alloc, H1 h1, H2 h2) 871   1 run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
872   { 872   {
873   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>( 873   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
HITCBC 874   1 std::move(ex), 874   1 std::move(ex),
HITCBC 875   1 std::stop_token{}, 875   1 std::stop_token{},
HITCBC 876   1 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 876   1 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
HITCBC 877   4 std::move(alloc)); 877   4 std::move(alloc));
878   } 878   }
879   879  
880   // Ex + stop_token + standard Allocator 880   // Ex + stop_token + standard Allocator
881   881  
882   /** Asynchronously launch a lazy task with stop token and allocator. 882   /** Asynchronously launch a lazy task with stop token and allocator.
883   883  
884   @param ex The executor to execute the task on. 884   @param ex The executor to execute the task on.
885   @param st The stop token for cooperative cancellation. 885   @param st The stop token for cooperative cancellation.
886   @param alloc The allocator for frame allocation (copied and stored). 886   @param alloc The allocator for frame allocation (copied and stored).
887   887  
888   @return A wrapper that accepts a `task<T>` for immediate execution. 888   @return A wrapper that accepts a `task<T>` for immediate execution.
889   889  
890   @see task 890   @see task
891   @see executor 891   @see executor
892   */ 892   */
893   template<Executor Ex, detail::Allocator Alloc> 893   template<Executor Ex, detail::Allocator Alloc>
894   [[nodiscard]] auto 894   [[nodiscard]] auto
895   run_async(Ex ex, std::stop_token st, Alloc alloc) 895   run_async(Ex ex, std::stop_token st, Alloc alloc)
896   { 896   {
897   return run_async_wrapper<Ex, detail::default_handler, Alloc>( 897   return run_async_wrapper<Ex, detail::default_handler, Alloc>(
898   std::move(ex), 898   std::move(ex),
899   std::move(st), 899   std::move(st),
900   detail::default_handler{}, 900   detail::default_handler{},
901   std::move(alloc)); 901   std::move(alloc));
902   } 902   }
903   903  
904   /** Asynchronously launch a lazy task with stop token, allocator, and handler. 904   /** Asynchronously launch a lazy task with stop token, allocator, and handler.
905   905  
906   @param ex The executor to execute the task on. 906   @param ex The executor to execute the task on.
907   @param st The stop token for cooperative cancellation. 907   @param st The stop token for cooperative cancellation.
908   @param alloc The allocator for frame allocation (copied and stored). 908   @param alloc The allocator for frame allocation (copied and stored).
909   @param h1 The handler to invoke with the result (and optionally exception). 909   @param h1 The handler to invoke with the result (and optionally exception).
910   910  
911   @return A wrapper that accepts a `task<T>` for immediate execution. 911   @return A wrapper that accepts a `task<T>` for immediate execution.
912   912  
913   @see task 913   @see task
914   @see executor 914   @see executor
915   */ 915   */
916   template<Executor Ex, detail::Allocator Alloc, class H1> 916   template<Executor Ex, detail::Allocator Alloc, class H1>
917   [[nodiscard]] auto 917   [[nodiscard]] auto
918   run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1) 918   run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
919   { 919   {
920   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>( 920   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
921   std::move(ex), 921   std::move(ex),
922   std::move(st), 922   std::move(st),
923   detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 923   detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
924   std::move(alloc)); 924   std::move(alloc));
925   } 925   }
926   926  
927   /** Asynchronously launch a lazy task with stop token, allocator, and handlers. 927   /** Asynchronously launch a lazy task with stop token, allocator, and handlers.
928   928  
929   @param ex The executor to execute the task on. 929   @param ex The executor to execute the task on.
930   @param st The stop token for cooperative cancellation. 930   @param st The stop token for cooperative cancellation.
931   @param alloc The allocator for frame allocation (copied and stored). 931   @param alloc The allocator for frame allocation (copied and stored).
932   @param h1 The handler to invoke with the result on success. 932   @param h1 The handler to invoke with the result on success.
933   @param h2 The handler to invoke with the exception on failure. 933   @param h2 The handler to invoke with the exception on failure.
934   934  
935   @return A wrapper that accepts a `task<T>` for immediate execution. 935   @return A wrapper that accepts a `task<T>` for immediate execution.
936   936  
937   @see task 937   @see task
938   @see executor 938   @see executor
939   */ 939   */
940   template<Executor Ex, detail::Allocator Alloc, class H1, class H2> 940   template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
941   [[nodiscard]] auto 941   [[nodiscard]] auto
942   run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2) 942   run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
943   { 943   {
944   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>( 944   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
945   std::move(ex), 945   std::move(ex),
946   std::move(st), 946   std::move(st),
947   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 947   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
948   std::move(alloc)); 948   std::move(alloc));
949   } 949   }
950   950  
951   } // namespace capy 951   } // namespace capy
952   } // namespace boost 952   } // namespace boost
953   953  
954   #endif 954   #endif