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