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