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