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