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