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