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