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