100.00% Lines (177/177) 100.00% Functions (40/40)
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   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
9   // 9   //
10   10  
11   #ifndef BOOST_CAPY_RUN_ASYNC_HPP 11   #ifndef BOOST_CAPY_RUN_ASYNC_HPP
12   #define BOOST_CAPY_RUN_ASYNC_HPP 12   #define BOOST_CAPY_RUN_ASYNC_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/detail/run.hpp> 15   #include <boost/capy/detail/run.hpp>
16   #include <boost/capy/detail/run_callbacks.hpp> 16   #include <boost/capy/detail/run_callbacks.hpp>
17   #include <boost/capy/concept/executor.hpp> 17   #include <boost/capy/concept/executor.hpp>
18   #include <boost/capy/concept/io_runnable.hpp> 18   #include <boost/capy/concept/io_runnable.hpp>
19   #include <boost/capy/ex/execution_context.hpp> 19   #include <boost/capy/ex/execution_context.hpp>
20   #include <boost/capy/ex/frame_allocator.hpp> 20   #include <boost/capy/ex/frame_allocator.hpp>
21   #include <boost/capy/ex/io_env.hpp> 21   #include <boost/capy/ex/io_env.hpp>
22   #include <boost/capy/ex/recycling_memory_resource.hpp> 22   #include <boost/capy/ex/recycling_memory_resource.hpp>
23   #include <boost/capy/ex/work_guard.hpp> 23   #include <boost/capy/ex/work_guard.hpp>
24   24  
25   #include <algorithm> 25   #include <algorithm>
26   #include <coroutine> 26   #include <coroutine>
27   #include <cstring> 27   #include <cstring>
28   #include <exception> 28   #include <exception>
29   #include <memory_resource> 29   #include <memory_resource>
30   #include <new> 30   #include <new>
31   #include <stop_token> 31   #include <stop_token>
32   #include <type_traits> 32   #include <type_traits>
33   33  
34   namespace boost { 34   namespace boost {
35   namespace capy { 35   namespace capy {
36   namespace detail { 36   namespace detail {
37   37  
38   /** Match types usable as `run_async` completion handlers. 38   /** Match types usable as `run_async` completion handlers.
39   39  
40   Excludes the types meaningful to the other `run_async` parameters. 40   Excludes the types meaningful to the other `run_async` parameters.
41   A stop token, memory resource pointer, or allocator argument 41   A stop token, memory resource pointer, or allocator argument
42   therefore selects its dedicated overload by conversion. It does not 42   therefore selects its dedicated overload by conversion. It does not
43   deduce as an exact-match handler. 43   deduce as an exact-match handler.
44   */ 44   */
45   template<class H> 45   template<class H>
46   concept RunAsyncHandler = 46   concept RunAsyncHandler =
47   !std::is_convertible_v<H, std::pmr::memory_resource*> && 47   !std::is_convertible_v<H, std::pmr::memory_resource*> &&
48   !std::is_convertible_v<H, std::stop_token> && 48   !std::is_convertible_v<H, std::stop_token> &&
49   !Allocator<H>; 49   !Allocator<H>;
50   50  
51   /// Function pointer type for type-erased frame deallocation. 51   /// Function pointer type for type-erased frame deallocation.
52   using dealloc_fn = void(*)(void*, std::size_t); 52   using dealloc_fn = void(*)(void*, std::size_t);
53   53  
54   /// Type-erased deallocator implementation for trampoline frames. 54   /// Type-erased deallocator implementation for trampoline frames.
55   template<class Alloc> 55   template<class Alloc>
HITCBC 56   3 void dealloc_impl(void* raw, std::size_t total) 56   3 void dealloc_impl(void* raw, std::size_t total)
57   { 57   {
58   static_assert(std::is_same_v<typename Alloc::value_type, std::byte>); 58   static_assert(std::is_same_v<typename Alloc::value_type, std::byte>);
HITCBC 59   3 auto* a = std::launder(reinterpret_cast<Alloc*>( 59   3 auto* a = std::launder(reinterpret_cast<Alloc*>(
HITCBC 60   3 static_cast<char*>(raw) + total - sizeof(Alloc))); 60   3 static_cast<char*>(raw) + total - sizeof(Alloc)));
HITCBC 61   3 Alloc ba(std::move(*a)); 61   3 Alloc ba(std::move(*a));
HITCBC 62   1 a->~Alloc(); 62   1 a->~Alloc();
HITCBC 63   1 ba.deallocate(static_cast<std::byte*>(raw), total); 63   1 ba.deallocate(static_cast<std::byte*>(raw), total);
HITCBC 64   3 } 64   3 }
65   65  
66   /// Awaiter to access the promise from within the coroutine. 66   /// Awaiter to access the promise from within the coroutine.
67   template<class Promise> 67   template<class Promise>
68   struct get_promise_awaiter 68   struct get_promise_awaiter
69   { 69   {
70   Promise* p_ = nullptr; 70   Promise* p_ = nullptr;
71   71  
HITCBC 72   1803 bool await_ready() const noexcept { return false; } 72   1816 bool await_ready() const noexcept { return false; }
73   73  
HITCBC 74   1803 bool await_suspend(std::coroutine_handle<Promise> h) noexcept 74   1816 bool await_suspend(std::coroutine_handle<Promise> h) noexcept
75   { 75   {
HITCBC 76   1803 p_ = &h.promise(); 76   1816 p_ = &h.promise();
HITCBC 77   1803 return false; 77   1816 return false;
78   } 78   }
79   79  
HITCBC 80   1803 Promise& await_resume() const noexcept 80   1816 Promise& await_resume() const noexcept
81   { 81   {
HITCBC 82   1803 return *p_; 82   1816 return *p_;
83   } 83   }
84   }; 84   };
85   85  
86   /** Internal run_async_trampoline coroutine for run_async. 86   /** Internal run_async_trampoline coroutine for run_async.
87   87  
88   The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation 88   The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation
89   order) and serves as the task's continuation. When the task final_suspends, 89   order) and serves as the task's continuation. When the task final_suspends,
90   control returns to the run_async_trampoline which then invokes the appropriate handler. 90   control returns to the run_async_trampoline which then invokes the appropriate handler.
91   91  
92   For value-type allocators, the run_async_trampoline stores a frame_memory_resource 92   For value-type allocators, the run_async_trampoline stores a frame_memory_resource
93   that wraps the allocator. For memory_resource*, it stores the pointer directly. 93   that wraps the allocator. For memory_resource*, it stores the pointer directly.
94   94  
95   @tparam Ex The executor type. 95   @tparam Ex The executor type.
96   @tparam Handlers The handler type (default_handler or handler_pair). 96   @tparam Handlers The handler type (default_handler or handler_pair).
97   @tparam Alloc The allocator type (value type or memory_resource*). 97   @tparam Alloc The allocator type (value type or memory_resource*).
98   */ 98   */
99   template<class Ex, class Handlers, class Alloc> 99   template<class Ex, class Handlers, class Alloc>
100   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE run_async_trampoline 100   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE run_async_trampoline
101   { 101   {
102   using invoke_fn = void(*)(void*, Handlers&); 102   using invoke_fn = void(*)(void*, Handlers&);
103   103  
104   struct promise_type 104   struct promise_type
105   { 105   {
106   work_guard<Ex> wg_; 106   work_guard<Ex> wg_;
107   Handlers handlers_; 107   Handlers handlers_;
108   frame_memory_resource<Alloc> resource_; 108   frame_memory_resource<Alloc> resource_;
109   io_env env_; 109   io_env env_;
110   invoke_fn invoke_ = nullptr; 110   invoke_fn invoke_ = nullptr;
111   void* task_promise_ = nullptr; 111   void* task_promise_ = nullptr;
112   // task_h_: raw handle for frame_guard cleanup in make_trampoline. 112   // task_h_: raw handle for frame_guard cleanup in make_trampoline.
113   // task_cont_: continuation wrapping the same handle for executor dispatch. 113   // task_cont_: continuation wrapping the same handle for executor dispatch.
114   // Both must reference the same coroutine and be kept in sync. 114   // Both must reference the same coroutine and be kept in sync.
115   std::coroutine_handle<> task_h_; 115   std::coroutine_handle<> task_h_;
116   continuation task_cont_; 116   continuation task_cont_;
117   117  
HITCBC 118   3 promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept 118   3 promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
HITCBC 119   3 : wg_(std::move(ex)) 119   3 : wg_(std::move(ex))
HITCBC 120   3 , handlers_(std::move(h)) 120   3 , handlers_(std::move(h))
HITCBC 121   3 , resource_(std::move(a)) 121   3 , resource_(std::move(a))
122   { 122   {
HITCBC 123   3 } 123   3 }
124   124  
HITCBC 125   3 static void* operator new( 125   3 static void* operator new(
126   std::size_t size, Ex const&, Handlers const&, Alloc a) 126   std::size_t size, Ex const&, Handlers const&, Alloc a)
127   { 127   {
128   using byte_alloc = typename std::allocator_traits<Alloc> 128   using byte_alloc = typename std::allocator_traits<Alloc>
129   ::template rebind_alloc<std::byte>; 129   ::template rebind_alloc<std::byte>;
130   130  
HITCBC 131   3 constexpr auto footer_align = 131   3 constexpr auto footer_align =
132   (std::max)(alignof(dealloc_fn), alignof(Alloc)); 132   (std::max)(alignof(dealloc_fn), alignof(Alloc));
HITCBC 133   3 auto padded = (size + footer_align - 1) & ~(footer_align - 1); 133   3 auto padded = (size + footer_align - 1) & ~(footer_align - 1);
HITCBC 134   3 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc); 134   3 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
135   135  
HITCBC 136   1 byte_alloc ba(std::move(a)); 136   1 byte_alloc ba(std::move(a));
HITCBC 137   3 void* raw = ba.allocate(total); 137   3 void* raw = ba.allocate(total);
138   138  
HITCBC 139   3 auto* fn_loc = reinterpret_cast<dealloc_fn*>( 139   3 auto* fn_loc = reinterpret_cast<dealloc_fn*>(
140   static_cast<char*>(raw) + padded); 140   static_cast<char*>(raw) + padded);
HITCBC 141   3 *fn_loc = &dealloc_impl<byte_alloc>; 141   3 *fn_loc = &dealloc_impl<byte_alloc>;
142   142  
HITCBC 143   3 new (fn_loc + 1) byte_alloc(std::move(ba)); 143   3 new (fn_loc + 1) byte_alloc(std::move(ba));
144   144  
HITCBC 145   5 return raw; 145   5 return raw;
146   } 146   }
147   147  
HITCBC 148   3 static void operator delete(void* ptr, std::size_t size) 148   3 static void operator delete(void* ptr, std::size_t size)
149   { 149   {
HITCBC 150   3 constexpr auto footer_align = 150   3 constexpr auto footer_align =
151   (std::max)(alignof(dealloc_fn), alignof(Alloc)); 151   (std::max)(alignof(dealloc_fn), alignof(Alloc));
HITCBC 152   3 auto padded = (size + footer_align - 1) & ~(footer_align - 1); 152   3 auto padded = (size + footer_align - 1) & ~(footer_align - 1);
HITCBC 153   3 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc); 153   3 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
154   154  
HITCBC 155   3 auto* fn = reinterpret_cast<dealloc_fn*>( 155   3 auto* fn = reinterpret_cast<dealloc_fn*>(
156   static_cast<char*>(ptr) + padded); 156   static_cast<char*>(ptr) + padded);
HITCBC 157   3 (*fn)(ptr, total); 157   3 (*fn)(ptr, total);
HITCBC 158   3 } 158   3 }
159   159  
HITCBC 160   6 std::pmr::memory_resource* get_resource() noexcept 160   6 std::pmr::memory_resource* get_resource() noexcept
161   { 161   {
HITCBC 162   6 return &resource_; 162   6 return &resource_;
163   } 163   }
164   164  
HITCBC 165   3 run_async_trampoline get_return_object() noexcept 165   3 run_async_trampoline get_return_object() noexcept
166   { 166   {
167   return run_async_trampoline{ 167   return run_async_trampoline{
HITCBC 168   3 std::coroutine_handle<promise_type>::from_promise(*this)}; 168   3 std::coroutine_handle<promise_type>::from_promise(*this)};
169   } 169   }
170   170  
HITCBC 171   3 std::suspend_always initial_suspend() noexcept 171   3 std::suspend_always initial_suspend() noexcept
172   { 172   {
HITCBC 173   3 return {}; 173   3 return {};
174   } 174   }
175   175  
HITCBC 176   3 std::suspend_never final_suspend() noexcept 176   3 std::suspend_never final_suspend() noexcept
177   { 177   {
HITCBC 178   3 return {}; 178   3 return {};
179   } 179   }
180   180  
HITCBC 181   3 void return_void() noexcept 181   3 void return_void() noexcept
182   { 182   {
HITCBC 183   3 } 183   3 }
184   184  
185   // An exception reaches here only by escaping a handler: a handler 185   // An exception reaches here only by escaping a handler: a handler
186   // that threw, or the default handler rethrowing an otherwise 186   // that threw, or the default handler rethrowing an otherwise
187   // unhandled task exception. Cancellation is filtered out earlier 187   // unhandled task exception. Cancellation is filtered out earlier
188   // by default_handler, so this is always a genuine error with no 188   // by default_handler, so this is always a genuine error with no
189   // owner to receive it: fail fast. 189   // owner to receive it: fail fast.
190   void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE 190   void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE
191   }; 191   };
192   192  
193   std::coroutine_handle<promise_type> h_; 193   std::coroutine_handle<promise_type> h_;
194   194  
195   template<IoRunnable Task> 195   template<IoRunnable Task>
HITCBC 196   3 static void invoke_impl(void* p, Handlers& h) 196   3 static void invoke_impl(void* p, Handlers& h)
197   { 197   {
198   using R = decltype(std::declval<Task&>().await_resume()); 198   using R = decltype(std::declval<Task&>().await_resume());
HITCBC 199   3 auto& promise = *static_cast<typename Task::promise_type*>(p); 199   3 auto& promise = *static_cast<typename Task::promise_type*>(p);
HITCBC 200   3 if(promise.exception()) 200   3 if(promise.exception())
HITCBC 201   1 h(promise.exception()); 201   1 h(promise.exception());
202   else if constexpr(std::is_void_v<R>) 202   else if constexpr(std::is_void_v<R>)
HITCBC 203   1 h(); 203   1 h();
204   else 204   else
HITCBC 205   1 h(std::move(promise.result())); 205   1 h(std::move(promise.result()));
HITCBC 206   3 } 206   3 }
207   }; 207   };
208   208  
209   /** Specialization for memory_resource* - stores pointer directly. 209   /** Specialization for memory_resource* - stores pointer directly.
210   210  
211   This avoids double indirection when the user passes a memory_resource*. 211   This avoids double indirection when the user passes a memory_resource*.
212   */ 212   */
213   template<class Ex, class Handlers> 213   template<class Ex, class Handlers>
214   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE 214   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE
215   run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*> 215   run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
216   { 216   {
217   using invoke_fn = void(*)(void*, Handlers&); 217   using invoke_fn = void(*)(void*, Handlers&);
218   218  
219   struct promise_type 219   struct promise_type
220   { 220   {
221   work_guard<Ex> wg_; 221   work_guard<Ex> wg_;
222   Handlers handlers_; 222   Handlers handlers_;
223   std::pmr::memory_resource* mr_; 223   std::pmr::memory_resource* mr_;
224   io_env env_; 224   io_env env_;
225   invoke_fn invoke_ = nullptr; 225   invoke_fn invoke_ = nullptr;
226   void* task_promise_ = nullptr; 226   void* task_promise_ = nullptr;
227   // task_h_: raw handle for frame_guard cleanup in make_trampoline. 227   // task_h_: raw handle for frame_guard cleanup in make_trampoline.
228   // task_cont_: continuation wrapping the same handle for executor dispatch. 228   // task_cont_: continuation wrapping the same handle for executor dispatch.
229   // Both must reference the same coroutine and be kept in sync. 229   // Both must reference the same coroutine and be kept in sync.
230   std::coroutine_handle<> task_h_; 230   std::coroutine_handle<> task_h_;
231   continuation task_cont_; 231   continuation task_cont_;
232   232  
HITCBC 233   1934 promise_type( 233   1953 promise_type(
234   Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept 234   Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
HITCBC 235   1934 : wg_(std::move(ex)) 235   1953 : wg_(std::move(ex))
HITCBC 236   1934 , handlers_(std::move(h)) 236   1953 , handlers_(std::move(h))
HITCBC 237   1934 , mr_(mr) 237   1953 , mr_(mr)
238   { 238   {
HITCBC 239   1934 } 239   1953 }
240   240  
HITCBC 241   1934 static void* operator new( 241   1953 static void* operator new(
242   std::size_t size, Ex const&, Handlers const&, 242   std::size_t size, Ex const&, Handlers const&,
243   std::pmr::memory_resource* mr) 243   std::pmr::memory_resource* mr)
244   { 244   {
HITCBC 245   1934 auto total = size + sizeof(mr); 245   1953 auto total = size + sizeof(mr);
HITCBC 246   1934 void* raw = mr->allocate(total, alignof(std::max_align_t)); 246   1953 void* raw = mr->allocate(total, alignof(std::max_align_t));
HITCBC 247   1934 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr)); 247   1953 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
HITCBC 248   1934 return raw; 248   1953 return raw;
249   } 249   }
250   250  
HITCBC 251   1934 static void operator delete(void* ptr, std::size_t size) 251   1953 static void operator delete(void* ptr, std::size_t size)
252   { 252   {
253   std::pmr::memory_resource* mr; 253   std::pmr::memory_resource* mr;
HITCBC 254   1934 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr)); 254   1953 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
HITCBC 255   1934 auto total = size + sizeof(mr); 255   1953 auto total = size + sizeof(mr);
HITCBC 256   1934 mr->deallocate(ptr, total, alignof(std::max_align_t)); 256   1953 mr->deallocate(ptr, total, alignof(std::max_align_t));
HITCBC 257   1934 } 257   1953 }
258   258  
HITCBC 259   3868 std::pmr::memory_resource* get_resource() noexcept 259   3906 std::pmr::memory_resource* get_resource() noexcept
260   { 260   {
HITCBC 261   3868 return mr_; 261   3906 return mr_;
262   } 262   }
263   263  
HITCBC 264   1934 run_async_trampoline get_return_object() noexcept 264   1953 run_async_trampoline get_return_object() noexcept
265   { 265   {
266   return run_async_trampoline{ 266   return run_async_trampoline{
HITCBC 267   1934 std::coroutine_handle<promise_type>::from_promise(*this)}; 267   1953 std::coroutine_handle<promise_type>::from_promise(*this)};
268   } 268   }
269   269  
HITCBC 270   1934 std::suspend_always initial_suspend() noexcept 270   1953 std::suspend_always initial_suspend() noexcept
271   { 271   {
HITCBC 272   1934 return {}; 272   1953 return {};
273   } 273   }
274   274  
HITCBC 275   1800 std::suspend_never final_suspend() noexcept 275   1813 std::suspend_never final_suspend() noexcept
276   { 276   {
HITCBC 277   1800 return {}; 277   1813 return {};
278   } 278   }
279   279  
HITCBC 280   1800 void return_void() noexcept 280   1813 void return_void() noexcept
281   { 281   {
HITCBC 282   1800 } 282   1813 }
283   283  
284   // See primary template: an escaping handler exception is fatal. 284   // See primary template: an escaping handler exception is fatal.
285   void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE 285   void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE
286   }; 286   };
287   287  
288   std::coroutine_handle<promise_type> h_; 288   std::coroutine_handle<promise_type> h_;
289   289  
290   template<IoRunnable Task> 290   template<IoRunnable Task>
HITCBC 291   1800 static void invoke_impl(void* p, Handlers& h) 291   1813 static void invoke_impl(void* p, Handlers& h)
292   { 292   {
293   using R = decltype(std::declval<Task&>().await_resume()); 293   using R = decltype(std::declval<Task&>().await_resume());
HITCBC 294   1800 auto& promise = *static_cast<typename Task::promise_type*>(p); 294   1813 auto& promise = *static_cast<typename Task::promise_type*>(p);
HITCBC 295   1800 if(promise.exception()) 295   1813 if(promise.exception())
HITCBC 296   373 h(promise.exception()); 296   373 h(promise.exception());
297   else if constexpr(std::is_void_v<R>) 297   else if constexpr(std::is_void_v<R>)
HITCBC 298   1149 h(); 298   1162 h();
299   else 299   else
HITCBC 300   278 h(std::move(promise.result())); 300   278 h(std::move(promise.result()));
HITCBC 301   1800 } 301   1813 }
302   }; 302   };
303   303  
304   /// Coroutine body for run_async_trampoline - invokes handlers then destroys task. 304   /// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
305   template<class Ex, class Handlers, class Alloc> 305   template<class Ex, class Handlers, class Alloc>
306   run_async_trampoline<Ex, Handlers, Alloc> 306   run_async_trampoline<Ex, Handlers, Alloc>
HITCBC 307   1937 make_trampoline(Ex, Handlers, Alloc) 307   1956 make_trampoline(Ex, Handlers, Alloc)
308   { 308   {
309   // promise_type ctor steals the parameters 309   // promise_type ctor steals the parameters
310   auto& p = co_await get_promise_awaiter< 310   auto& p = co_await get_promise_awaiter<
311   typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{}; 311   typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
312   312  
313   // Guard ensures the task frame is destroyed even when invoke_ 313   // Guard ensures the task frame is destroyed even when invoke_
314   // throws (e.g. default_handler rethrows an unhandled exception). 314   // throws (e.g. default_handler rethrows an unhandled exception).
315   struct frame_guard 315   struct frame_guard
316   { 316   {
317   std::coroutine_handle<>& h; 317   std::coroutine_handle<>& h;
HITCBC 318   1803 ~frame_guard() { h.destroy(); } 318   1816 ~frame_guard() { h.destroy(); }
319   } guard{p.task_h_}; 319   } guard{p.task_h_};
320   320  
321   p.invoke_(p.task_promise_, p.handlers_); 321   p.invoke_(p.task_promise_, p.handlers_);
HITCBC 322   3878 } 322   3916 }
323   323  
324   } // namespace detail 324   } // namespace detail
325   325  
326   /** Installs the frame allocator, then starts the task on the executor when called once. 326   /** Installs the frame allocator, then starts the task on the executor when called once.
327   327  
328   This wrapper holds the run_async_trampoline coroutine, executor, stop token, 328   This wrapper holds the run_async_trampoline coroutine, executor, stop token,
329   and handlers. The run_async_trampoline is allocated when the wrapper is constructed 329   and handlers. The run_async_trampoline is allocated when the wrapper is constructed
330   (before the task due to C++17 postfix evaluation order). 330   (before the task due to C++17 postfix evaluation order).
331   331  
332   The rvalue ref-qualifier on `operator()` ensures the wrapper can only 332   The rvalue ref-qualifier on `operator()` ensures the wrapper can only
333   be used as a temporary, preventing misuse that would violate LIFO ordering. 333   be used as a temporary, preventing misuse that would violate LIFO ordering.
334   334  
335   @tparam Ex The executor type satisfying the `Executor` concept. 335   @tparam Ex The executor type satisfying the `Executor` concept.
336   @tparam Handlers The handler type (default_handler or handler_pair). 336   @tparam Handlers The handler type (default_handler or handler_pair).
337   @tparam Alloc The allocator type (value type or memory_resource*). 337   @tparam Alloc The allocator type (value type or memory_resource*).
338   338  
339   @par Thread Safety 339   @par Thread Safety
340   The wrapper itself should only be used from one thread. The handlers 340   The wrapper itself should only be used from one thread. The handlers
341   may be invoked from any thread where the executor schedules work. 341   may be invoked from any thread where the executor schedules work.
342   342  
343   @warning **Always construct the task as the direct argument of the 343   @warning **Always construct the task as the direct argument of the
344   two-call expression `run_async(ex)(task)`.** The wrapper's constructor 344   two-call expression `run_async(ex)(task)`.** The wrapper's constructor
345   installs the frame allocator in thread-local storage. The task's 345   installs the frame allocator in thread-local storage. The task's
346   `operator new` reads that thread-local state. Splitting the two calls 346   `operator new` reads that thread-local state. Splitting the two calls
347   apart in any of the following ways allocates the task's coroutine 347   apart in any of the following ways allocates the task's coroutine
348   frame under the wrong allocator. Each does so silently, with no 348   frame under the wrong allocator. Each does so silently, with no
349   compile error. 349   compile error.
350   @li *Stored wrapper.* Storing the wrapper itself 350   @li *Stored wrapper.* Storing the wrapper itself
351   (`auto w = run_async(ex);`) compiles fine. C++17 guaranteed copy 351   (`auto w = run_async(ex);`) compiles fine. C++17 guaranteed copy
352   elision constructs `w` directly from the prvalue. The deleted 352   elision constructs `w` directly from the prvalue. The deleted
353   copy/move constructors are never considered. What the rvalue 353   copy/move constructors are never considered. What the rvalue
354   ref-qualifier on `operator()` rejects is calling through that 354   ref-qualifier on `operator()` rejects is calling through that
355   stored lvalue: `w(my_task())` does not compile, and 355   stored lvalue: `w(my_task())` does not compile, and
356   `std::move(w)(my_task())` is required instead. The silent 356   `std::move(w)(my_task())` is required instead. The silent
357   variant is storing the *task* 357   variant is storing the *task*
358   (`auto t = my_task(); run_async(ex)(std::move(t));`): `t`'s frame 358   (`auto t = my_task(); run_async(ex)(std::move(t));`): `t`'s frame
359   is allocated before `run_async(ex)` ever runs. 359   is allocated before `run_async(ex)` ever runs.
360   @li *Preconstructed task.* Passing an already-constructed task object 360   @li *Preconstructed task.* Passing an already-constructed task object
361   has the same effect as the stored-wrapper case. So does passing a 361   has the same effect as the stored-wrapper case. So does passing a
362   moved-from local, or a task returned from an earlier statement. 362   moved-from local, or a task returned from an earlier statement.
363   The frame exists before the allocator is installed. 363   The frame exists before the allocator is installed.
364   @li *Wrapper function.* Forwarding the task through a helper that 364   @li *Wrapper function.* Forwarding the task through a helper that
365   itself performs the two-call pattern constructs the task as an 365   itself performs the two-call pattern constructs the task as an
366   argument to the helper. It is therefore constructed before the 366   argument to the helper. It is therefore constructed before the
367   helper's body runs, and so before `run_async` runs. An example is 367   helper's body runs, and so before `run_async` runs. An example is
368   `submit(ex, my_task())`, where `submit` calls 368   `submit(ex, my_task())`, where `submit` calls
369   `run_async(ex)(std::forward<Task>(t))` internally. 369   `run_async(ex)(std::forward<Task>(t))` internally.
370   370  
371   See the Frame Allocators guide 371   See the Frame Allocators guide
372   (`doc/modules/ROOT/pages/4.coroutines/4g.allocators.adoc`) for the full 372   (`doc/modules/ROOT/pages/4.coroutines/4g.allocators.adoc`) for the full
373   C++17-evaluation-order rationale behind this constraint. 373   C++17-evaluation-order rationale behind this constraint.
374   374  
375   @par Example 375   @par Example
376   @code 376   @code
377   // Correct usage - wrapper is temporary, task is the direct argument 377   // Correct usage - wrapper is temporary, task is the direct argument
378   run_async(ex)(my_task()); 378   run_async(ex)(my_task());
379   379  
380   // Compiles - copy elision constructs w directly from the prvalue 380   // Compiles - copy elision constructs w directly from the prvalue
381   auto w = run_async(ex); 381   auto w = run_async(ex);
382   w(my_task()); // Compile error: operator() requires rvalue 382   w(my_task()); // Compile error: operator() requires rvalue
383   std::move(w)(my_task()); // Compiles: w is now an rvalue 383   std::move(w)(my_task()); // Compiles: w is now an rvalue
384   384  
385   // Compiles, but WRONG - task frame allocated before run_async runs 385   // Compiles, but WRONG - task frame allocated before run_async runs
386   auto t = my_task(); 386   auto t = my_task();
387   run_async(ex)(std::move(t)); 387   run_async(ex)(std::move(t));
388   @endcode 388   @endcode
389   389  
390   @see run_async 390   @see run_async
391   */ 391   */
392   template<Executor Ex, class Handlers, class Alloc> 392   template<Executor Ex, class Handlers, class Alloc>
393   class [[nodiscard]] run_async_wrapper 393   class [[nodiscard]] run_async_wrapper
394   { 394   {
395   detail::run_async_trampoline<Ex, Handlers, Alloc> tr_; 395   detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
396   std::stop_token st_; 396   std::stop_token st_;
397   std::pmr::memory_resource* saved_tls_; 397   std::pmr::memory_resource* saved_tls_;
398   398  
399   public: 399   public:
400   /** Construct the wrapper and install the frame allocator. 400   /** Construct the wrapper and install the frame allocator.
401   401  
402   Builds the trampoline and saves the current thread-local frame 402   Builds the trampoline and saves the current thread-local frame
403   allocator. Then installs the trampoline's resource as the new 403   allocator. Then installs the trampoline's resource as the new
404   thread-local allocator. The task frame, evaluated as the argument 404   thread-local allocator. The task frame, evaluated as the argument
405   to @ref operator(), is therefore allocated from that resource. 405   to @ref operator(), is therefore allocated from that resource.
406   406  
407   @param ex The executor on which the task runs. 407   @param ex The executor on which the task runs.
408   @param st The stop token for cooperative cancellation. 408   @param st The stop token for cooperative cancellation.
409   @param h The completion handlers. 409   @param h The completion handlers.
410   @param a The allocator for frame allocation. 410   @param a The allocator for frame allocation.
411   411  
412   @note When `Alloc` is not `std::pmr::memory_resource*` it must be 412   @note When `Alloc` is not `std::pmr::memory_resource*` it must be
413   nothrow move constructible (enforced by a `static_assert`), which 413   nothrow move constructible (enforced by a `static_assert`), which
414   is what allows this constructor to be `noexcept`. 414   is what allows this constructor to be `noexcept`.
415   */ 415   */
HITCBC 416   1937 run_async_wrapper( 416   1956 run_async_wrapper(
417   Ex ex, 417   Ex ex,
418   std::stop_token st, 418   std::stop_token st,
419   Handlers h, 419   Handlers h,
420   Alloc a) noexcept 420   Alloc a) noexcept
HITCBC 421   1938 : tr_(detail::make_trampoline<Ex, Handlers, Alloc>( 421   1957 : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
HITCBC 422   1940 std::move(ex), std::move(h), std::move(a))) 422   1959 std::move(ex), std::move(h), std::move(a)))
HITCBC 423   1937 , st_(std::move(st)) 423   1956 , st_(std::move(st))
HITCBC 424   1937 , saved_tls_(get_current_frame_allocator()) 424   1956 , saved_tls_(get_current_frame_allocator())
425   { 425   {
426   if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>) 426   if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
427   { 427   {
428   static_assert( 428   static_assert(
429   std::is_nothrow_move_constructible_v<Alloc>, 429   std::is_nothrow_move_constructible_v<Alloc>,
430   "Allocator must be nothrow move constructible"); 430   "Allocator must be nothrow move constructible");
431   } 431   }
432   // Set TLS before task argument is evaluated 432   // Set TLS before task argument is evaluated
HITCBC 433   1937 set_current_frame_allocator(tr_.h_.promise().get_resource()); 433   1956 set_current_frame_allocator(tr_.h_.promise().get_resource());
HITCBC 434   1937 } 434   1956 }
435   435  
436   /** Restore the previously installed frame allocator. 436   /** Restore the previously installed frame allocator.
437   437  
438   Resets the thread-local frame allocator to the value saved at 438   Resets the thread-local frame allocator to the value saved at
439   construction. A stale pointer to the trampoline's resource 439   construction. A stale pointer to the trampoline's resource
440   therefore does not outlive the execution context that owns it. 440   therefore does not outlive the execution context that owns it.
441   */ 441   */
HITCBC 442   1937 ~run_async_wrapper() 442   1956 ~run_async_wrapper()
443   { 443   {
HITCBC 444   1937 set_current_frame_allocator(saved_tls_); 444   1956 set_current_frame_allocator(saved_tls_);
HITCBC 445   1937 } 445   1956 }
446   446  
447   // Non-copyable, non-movable (must be used immediately) 447   // Non-copyable, non-movable (must be used immediately)
448   448  
449   /** Copy construction is disabled; the wrapper must be used immediately. 449   /** Copy construction is disabled; the wrapper must be used immediately.
450   450  
451   @param other The wrapper that would be copied. 451   @param other The wrapper that would be copied.
452   */ 452   */
453   run_async_wrapper(run_async_wrapper const& other) = delete; 453   run_async_wrapper(run_async_wrapper const& other) = delete;
454   454  
455   /** Move construction is disabled; the wrapper must be used immediately. 455   /** Move construction is disabled; the wrapper must be used immediately.
456   456  
457   @param other The wrapper that would be moved from. 457   @param other The wrapper that would be moved from.
458   */ 458   */
459   run_async_wrapper(run_async_wrapper&& other) = delete; 459   run_async_wrapper(run_async_wrapper&& other) = delete;
460   460  
461   /** Copy assignment is disabled; the wrapper must be used immediately. 461   /** Copy assignment is disabled; the wrapper must be used immediately.
462   462  
463   @param other The wrapper that would be assigned from. 463   @param other The wrapper that would be assigned from.
464   464  
465   @return A reference to `*this`. 465   @return A reference to `*this`.
466   */ 466   */
467   run_async_wrapper& operator=(run_async_wrapper const& other) = delete; 467   run_async_wrapper& operator=(run_async_wrapper const& other) = delete;
468   468  
469   /** Move assignment is disabled; the wrapper must be used immediately. 469   /** Move assignment is disabled; the wrapper must be used immediately.
470   470  
471   @param other The wrapper that would be moved from. 471   @param other The wrapper that would be moved from.
472   472  
473   @return A reference to `*this`. 473   @return A reference to `*this`.
474   */ 474   */
475   run_async_wrapper& operator=(run_async_wrapper&& other) = delete; 475   run_async_wrapper& operator=(run_async_wrapper&& other) = delete;
476   476  
477   /** Start the task for execution. 477   /** Start the task for execution.
478   478  
479   This operator accepts a task and starts it on the executor. 479   This operator accepts a task and starts it on the executor.
480   The rvalue ref-qualifier ensures the wrapper is consumed, enforcing 480   The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
481   correct LIFO destruction order. 481   correct LIFO destruction order.
482   482  
483   The `io_env` constructed for the task is owned by the trampoline 483   The `io_env` constructed for the task is owned by the trampoline
484   coroutine and is guaranteed to outlive the task and all awaitables 484   coroutine and is guaranteed to outlive the task and all awaitables
485   in its chain. Awaitables may store `io_env const*` without concern 485   in its chain. Awaitables may store `io_env const*` without concern
486   for dangling references. 486   for dangling references.
487   487  
488   @tparam Task The IoRunnable type. 488   @tparam Task The IoRunnable type.
489   489  
490   @param t The task to execute. Ownership is transferred to the 490   @param t The task to execute. Ownership is transferred to the
491   run_async_trampoline which destroys it after completion. 491   run_async_trampoline which destroys it after completion.
492   */ 492   */
493   template<IoRunnable Task> 493   template<IoRunnable Task>
HITCBC 494   1937 void operator()(Task t) && 494   1956 void operator()(Task t) &&
495   { 495   {
HITCBC 496   1937 auto task_h = t.handle(); 496   1956 auto task_h = t.handle();
HITCBC 497   1937 auto& task_promise = task_h.promise(); 497   1956 auto& task_promise = task_h.promise();
HITCBC 498   1937 t.release(); 498   1956 t.release();
499   499  
HITCBC 500   1937 auto& p = tr_.h_.promise(); 500   1956 auto& p = tr_.h_.promise();
501   501  
502   // Inject Task-specific invoke function 502   // Inject Task-specific invoke function
HITCBC 503   1937 p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>; 503   1956 p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
HITCBC 504   1937 p.task_promise_ = &task_promise; 504   1956 p.task_promise_ = &task_promise;
HITCBC 505   1937 p.task_h_ = task_h; 505   1956 p.task_h_ = task_h;
506   506  
507   // Setup task's continuation to return to run_async_trampoline 507   // Setup task's continuation to return to run_async_trampoline
HITCBC 508   1937 task_promise.set_continuation(tr_.h_); 508   1956 task_promise.set_continuation(tr_.h_);
HITCBC 509   3874 p.env_ = {p.wg_.executor(), st_, p.get_resource()}; 509   3912 p.env_ = {p.wg_.executor(), st_, p.get_resource()};
HITCBC 510   1937 task_promise.set_environment(&p.env_); 510   1956 task_promise.set_environment(&p.env_);
511   511  
512   // Start task through executor. 512   // Start task through executor.
513   // safe_resume is not needed here: TLS is already saved in the 513   // safe_resume is not needed here: TLS is already saved in the
514   // constructor (saved_tls_) and restored in the destructor. 514   // constructor (saved_tls_) and restored in the destructor.
HITCBC 515   1937 p.task_cont_.h = task_h; 515   1956 p.task_cont_.h = task_h;
HITCBC 516   1937 p.wg_.executor().dispatch(p.task_cont_).resume(); 516   1956 p.wg_.executor().dispatch(p.task_cont_).resume();
HITCBC 517   3874 } 517   3912 }
518   }; 518   };
519   519  
520   // Executor only (uses default recycling allocator) 520   // Executor only (uses default recycling allocator)
521   521  
522   /** Bind an executor to produce a launcher. Invoke the launcher with a task to start it. 522   /** Bind an executor to produce a launcher. Invoke the launcher with a task to start it.
523   523  
524   Use this to start execution of a `task<T>` that was created lazily. 524   Use this to start execution of a `task<T>` that was created lazily.
525   The returned wrapper must be immediately invoked with the task; 525   The returned wrapper must be immediately invoked with the task;
526   storing the wrapper and calling it later violates LIFO ordering. 526   storing the wrapper and calling it later violates LIFO ordering.
527   527  
528   Uses the default recycling frame allocator for coroutine frames. 528   Uses the default recycling frame allocator for coroutine frames.
529   With no handlers, the result is discarded. An unhandled exception 529   With no handlers, the result is discarded. An unhandled exception
530   thrown by the task calls `std::terminate`. To catch it instead, pass 530   thrown by the task calls `std::terminate`. To catch it instead, pass
531   an error handler that receives it as an `exception_ptr`, or `co_await` 531   an error handler that receives it as an `exception_ptr`, or `co_await`
532   the work inside a coroutine. 532   the work inside a coroutine.
533   533  
534   Construct the task as the direct argument of the two-call expression 534   Construct the task as the direct argument of the two-call expression
535   `run_async(ex)(task)`. 535   `run_async(ex)(task)`.
536   536  
537   @par Thread Safety 537   @par Thread Safety
538   The wrapper itself should only be used from one thread. 538   The wrapper itself should only be used from one thread.
539   539  
540   @par Example 540   @par Example
541   @code 541   @code
542   run_async(ioc.get_executor())(my_task()); 542   run_async(ioc.get_executor())(my_task());
543   @endcode 543   @endcode
544   544  
545   @param ex The executor to execute the task on. 545   @param ex The executor to execute the task on.
546   546  
547   @return A wrapper that accepts a `task<T>` for immediate execution. 547   @return A wrapper that accepts a `task<T>` for immediate execution.
548   548  
549   @see task 549   @see task
550   @see Executor 550   @see Executor
551   @see run_async_wrapper 551   @see run_async_wrapper
552   */ 552   */
553   template<Executor Ex> 553   template<Executor Ex>
554   [[nodiscard]] auto 554   [[nodiscard]] auto
HITCBC 555   205 run_async(Ex ex) 555   224 run_async(Ex ex)
556   { 556   {
HITCBC 557   205 auto* mr = ex.context().get_frame_allocator(); 557   224 auto* mr = ex.context().get_frame_allocator();
558   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>( 558   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
HITCBC 559   205 std::move(ex), 559   224 std::move(ex),
HITCBC 560   410 std::stop_token{}, 560   448 std::stop_token{},
561   detail::default_handler{}, 561   detail::default_handler{},
HITCBC 562   205 mr); 562   224 mr);
563   } 563   }
564   564  
565   /** Bind an executor and a result handler to produce a launcher. Invoke the launcher with a task to start it. 565   /** Bind an executor and a result handler to produce a launcher. Invoke the launcher with a task to start it.
566   566  
567   The handler `h1` is called with the task's result on success. If `h1` 567   The handler `h1` is called with the task's result on success. If `h1`
568   is also invocable with `std::exception_ptr`, it handles exceptions too. 568   is also invocable with `std::exception_ptr`, it handles exceptions too.
569   Otherwise, an unhandled exception calls `std::terminate`. 569   Otherwise, an unhandled exception calls `std::terminate`.
570   570  
571   Construct the task as the direct argument of the two-call expression 571   Construct the task as the direct argument of the two-call expression
572   `run_async(ex)(task)`. 572   `run_async(ex)(task)`.
573   573  
574   @par Thread Safety 574   @par Thread Safety
575   The wrapper itself should only be used from one thread. The handlers 575   The wrapper itself should only be used from one thread. The handlers
576   may be invoked from any thread where the executor schedules work. 576   may be invoked from any thread where the executor schedules work.
577   577  
578   @par Example 578   @par Example
579   @code 579   @code
580   // Handler for result only (exceptions rethrown) 580   // Handler for result only (exceptions rethrown)
581   run_async(ex, [](int result) { 581   run_async(ex, [](int result) {
582   std::cout << "Got: " << result << "\n"; 582   std::cout << "Got: " << result << "\n";
583   })(compute_value()); 583   })(compute_value());
584   584  
585   // Overloaded handler for both result and exception 585   // Overloaded handler for both result and exception
586   run_async(ex, overloaded{ 586   run_async(ex, overloaded{
587   [](int result) { std::cout << "Got: " << result << "\n"; }, 587   [](int result) { std::cout << "Got: " << result << "\n"; },
588   [](std::exception_ptr) { std::cout << "Failed\n"; } 588   [](std::exception_ptr) { std::cout << "Failed\n"; }
589   })(compute_value()); 589   })(compute_value());
590   @endcode 590   @endcode
591   591  
592   @param ex The executor to execute the task on. 592   @param ex The executor to execute the task on.
593   @param h1 The handler to invoke with the result (and optionally exception). 593   @param h1 The handler to invoke with the result (and optionally exception).
594   594  
595   @return A wrapper that accepts a `task<T>` for immediate execution. 595   @return A wrapper that accepts a `task<T>` for immediate execution.
596   596  
597   @see task 597   @see task
598   @see Executor 598   @see Executor
599   @see run_async_wrapper 599   @see run_async_wrapper
600   */ 600   */
601   template<Executor Ex, class H1> 601   template<Executor Ex, class H1>
602   requires detail::RunAsyncHandler<H1> 602   requires detail::RunAsyncHandler<H1>
603   [[nodiscard]] auto 603   [[nodiscard]] auto
HITCBC 604   109 run_async(Ex ex, H1 h1) 604   109 run_async(Ex ex, H1 h1)
605   { 605   {
HITCBC 606   109 auto* mr = ex.context().get_frame_allocator(); 606   109 auto* mr = ex.context().get_frame_allocator();
607   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>( 607   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
HITCBC 608   109 std::move(ex), 608   109 std::move(ex),
HITCBC 609   115 std::stop_token{}, 609   115 std::stop_token{},
HITCBC 610   103 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 610   103 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 611   212 mr); 611   212 mr);
612   } 612   }
613   613  
614   /** Bind an executor and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it. 614   /** Bind an executor and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
615   615  
616   The handler `h1` is called with the task's result on success. 616   The handler `h1` is called with the task's result on success.
617   The handler `h2` is called with the exception_ptr on failure. 617   The handler `h2` is called with the exception_ptr on failure.
618   618  
619   Construct the task as the direct argument of the two-call expression 619   Construct the task as the direct argument of the two-call expression
620   `run_async(ex)(task)`. 620   `run_async(ex)(task)`.
621   621  
622   @par Thread Safety 622   @par Thread Safety
623   The wrapper itself should only be used from one thread. The handlers 623   The wrapper itself should only be used from one thread. The handlers
624   may be invoked from any thread where the executor schedules work. 624   may be invoked from any thread where the executor schedules work.
625   625  
626   @par Example 626   @par Example
627   @code 627   @code
628   run_async(ex, 628   run_async(ex,
629   [](int result) { std::cout << "Got: " << result << "\n"; }, 629   [](int result) { std::cout << "Got: " << result << "\n"; },
630   [](std::exception_ptr ep) { 630   [](std::exception_ptr ep) {
631   try { std::rethrow_exception(ep); } 631   try { std::rethrow_exception(ep); }
632   catch (std::exception const& e) { 632   catch (std::exception const& e) {
633   std::cout << "Error: " << e.what() << "\n"; 633   std::cout << "Error: " << e.what() << "\n";
634   } 634   }
635   } 635   }
636   )(compute_value()); 636   )(compute_value());
637   @endcode 637   @endcode
638   638  
639   @param ex The executor to execute the task on. 639   @param ex The executor to execute the task on.
640   @param h1 The handler to invoke with the result on success. 640   @param h1 The handler to invoke with the result on success.
641   @param h2 The handler to invoke with the exception on failure. 641   @param h2 The handler to invoke with the exception on failure.
642   642  
643   @return A wrapper that accepts a `task<T>` for immediate execution. 643   @return A wrapper that accepts a `task<T>` for immediate execution.
644   644  
645   @see task 645   @see task
646   @see Executor 646   @see Executor
647   @see run_async_wrapper 647   @see run_async_wrapper
648   */ 648   */
649   template<Executor Ex, class H1, class H2> 649   template<Executor Ex, class H1, class H2>
650   requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>) 650   requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>)
651   [[nodiscard]] auto 651   [[nodiscard]] auto
HITCBC 652   95 run_async(Ex ex, H1 h1, H2 h2) 652   95 run_async(Ex ex, H1 h1, H2 h2)
653   { 653   {
HITCBC 654   95 auto* mr = ex.context().get_frame_allocator(); 654   95 auto* mr = ex.context().get_frame_allocator();
655   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>( 655   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
HITCBC 656   95 std::move(ex), 656   95 std::move(ex),
HITCBC 657   98 std::stop_token{}, 657   98 std::stop_token{},
HITCBC 658   92 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 658   92 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
HITCBC 659   187 mr); 659   187 mr);
HITCBC 660   1 } 660   1 }
661   661  
662   // Ex + stop_token 662   // Ex + stop_token
663   663  
664   /** Bind an executor and a stop token to produce a launcher. Invoke the launcher with a task to start it. 664   /** Bind an executor and a stop token to produce a launcher. Invoke the launcher with a task to start it.
665   665  
666   The stop token is propagated to the task, enabling cooperative 666   The stop token is propagated to the task, enabling cooperative
667   cancellation. With no handlers, the result is discarded and an 667   cancellation. With no handlers, the result is discarded and an
668   unhandled exception calls `std::terminate`. 668   unhandled exception calls `std::terminate`.
669   669  
670   Construct the task as the direct argument of the two-call expression 670   Construct the task as the direct argument of the two-call expression
671   `run_async(ex)(task)`. 671   `run_async(ex)(task)`.
672   672  
673   @par Thread Safety 673   @par Thread Safety
674   The wrapper itself should only be used from one thread. 674   The wrapper itself should only be used from one thread.
675   675  
676   @par Example 676   @par Example
677   @code 677   @code
678   std::stop_source source; 678   std::stop_source source;
679   run_async(ex, source.get_token())(cancellable_task()); 679   run_async(ex, source.get_token())(cancellable_task());
680   // Later: source.request_stop(); 680   // Later: source.request_stop();
681   @endcode 681   @endcode
682   682  
683   @param ex The executor to execute the task on. 683   @param ex The executor to execute the task on.
684   @param st The stop token for cooperative cancellation. 684   @param st The stop token for cooperative cancellation.
685   685  
686   @return A wrapper that accepts a `task<T>` for immediate execution. 686   @return A wrapper that accepts a `task<T>` for immediate execution.
687   687  
688   @see task 688   @see task
689   @see Executor 689   @see Executor
690   @see run_async_wrapper 690   @see run_async_wrapper
691   */ 691   */
692   template<Executor Ex> 692   template<Executor Ex>
693   [[nodiscard]] auto 693   [[nodiscard]] auto
HITCBC 694   371 run_async(Ex ex, std::stop_token st) 694   371 run_async(Ex ex, std::stop_token st)
695   { 695   {
HITCBC 696   371 auto* mr = ex.context().get_frame_allocator(); 696   371 auto* mr = ex.context().get_frame_allocator();
697   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>( 697   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
HITCBC 698   371 std::move(ex), 698   371 std::move(ex),
HITCBC 699   371 std::move(st), 699   371 std::move(st),
700   detail::default_handler{}, 700   detail::default_handler{},
HITCBC 701   742 mr); 701   742 mr);
702   } 702   }
703   703  
704   /** Bind an executor, a stop token, and a result handler to produce a launcher. Invoke the launcher with a task to start it. 704   /** Bind an executor, a stop token, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
705   705  
706   The stop token is propagated to the task for cooperative cancellation. 706   The stop token is propagated to the task for cooperative cancellation.
707   The handler `h1` is called with the result on success, and optionally 707   The handler `h1` is called with the result on success, and optionally
708   with exception_ptr if it accepts that type. 708   with exception_ptr if it accepts that type.
709   709  
710   Construct the task as the direct argument of the two-call expression 710   Construct the task as the direct argument of the two-call expression
711   `run_async(ex)(task)`. 711   `run_async(ex)(task)`.
712   712  
713   @par Thread Safety 713   @par Thread Safety
714   The wrapper itself should only be used from one thread. The handlers 714   The wrapper itself should only be used from one thread. The handlers
715   may be invoked from any thread where the executor schedules work. 715   may be invoked from any thread where the executor schedules work.
716   716  
717   @param ex The executor to execute the task on. 717   @param ex The executor to execute the task on.
718   @param st The stop token for cooperative cancellation. 718   @param st The stop token for cooperative cancellation.
719   @param h1 The handler to invoke with the result (and optionally exception). 719   @param h1 The handler to invoke with the result (and optionally exception).
720   720  
721   @return A wrapper that accepts a `task<T>` for immediate execution. 721   @return A wrapper that accepts a `task<T>` for immediate execution.
722   722  
723   @see task 723   @see task
724   @see Executor 724   @see Executor
725   @see run_async_wrapper 725   @see run_async_wrapper
726   */ 726   */
727   template<Executor Ex, class H1> 727   template<Executor Ex, class H1>
728   requires detail::RunAsyncHandler<H1> 728   requires detail::RunAsyncHandler<H1>
729   [[nodiscard]] auto 729   [[nodiscard]] auto
HITCBC 730   1123 run_async(Ex ex, std::stop_token st, H1 h1) 730   1123 run_async(Ex ex, std::stop_token st, H1 h1)
731   { 731   {
HITCBC 732   1123 auto* mr = ex.context().get_frame_allocator(); 732   1123 auto* mr = ex.context().get_frame_allocator();
733   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>( 733   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
HITCBC 734   1123 std::move(ex), 734   1123 std::move(ex),
HITCBC 735   1123 std::move(st), 735   1123 std::move(st),
HITCBC 736   1123 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 736   1123 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 737   2246 mr); 737   2246 mr);
738   } 738   }
739   739  
740   /** Bind an executor, a stop token, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it. 740   /** Bind an executor, a stop token, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
741   741  
742   The stop token is propagated to the task for cooperative cancellation. 742   The stop token is propagated to the task for cooperative cancellation.
743   The handler `h1` is called on success, `h2` on failure. 743   The handler `h1` is called on success, `h2` on failure.
744   744  
745   Construct the task as the direct argument of the two-call expression 745   Construct the task as the direct argument of the two-call expression
746   `run_async(ex)(task)`. 746   `run_async(ex)(task)`.
747   747  
748   @par Thread Safety 748   @par Thread Safety
749   The wrapper itself should only be used from one thread. The handlers 749   The wrapper itself should only be used from one thread. The handlers
750   may be invoked from any thread where the executor schedules work. 750   may be invoked from any thread where the executor schedules work.
751   751  
752   @param ex The executor to execute the task on. 752   @param ex The executor to execute the task on.
753   @param st The stop token for cooperative cancellation. 753   @param st The stop token for cooperative cancellation.
754   @param h1 The handler to invoke with the result on success. 754   @param h1 The handler to invoke with the result on success.
755   @param h2 The handler to invoke with the exception on failure. 755   @param h2 The handler to invoke with the exception on failure.
756   756  
757   @return A wrapper that accepts a `task<T>` for immediate execution. 757   @return A wrapper that accepts a `task<T>` for immediate execution.
758   758  
759   @see task 759   @see task
760   @see Executor 760   @see Executor
761   @see run_async_wrapper 761   @see run_async_wrapper
762   */ 762   */
763   template<Executor Ex, class H1, class H2> 763   template<Executor Ex, class H1, class H2>
764   requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>) 764   requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>)
765   [[nodiscard]] auto 765   [[nodiscard]] auto
HITCBC 766   12 run_async(Ex ex, std::stop_token st, H1 h1, H2 h2) 766   12 run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
767   { 767   {
HITCBC 768   12 auto* mr = ex.context().get_frame_allocator(); 768   12 auto* mr = ex.context().get_frame_allocator();
769   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>( 769   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
HITCBC 770   12 std::move(ex), 770   12 std::move(ex),
HITCBC 771   12 std::move(st), 771   12 std::move(st),
HITCBC 772   12 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 772   12 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
HITCBC 773   24 mr); 773   24 mr);
774   } 774   }
775   775  
776   // Ex + memory_resource* 776   // Ex + memory_resource*
777   777  
778   /** Bind an executor and a memory resource to produce a launcher. Invoke the launcher with a task to start it. 778   /** Bind an executor and a memory resource to produce a launcher. Invoke the launcher with a task to start it.
779   779  
780   The memory resource is used for coroutine frame allocation. 780   The memory resource is used for coroutine frame allocation.
781   781  
782   Construct the task as the direct argument of the two-call expression 782   Construct the task as the direct argument of the two-call expression
783   `run_async(ex)(task)`. 783   `run_async(ex)(task)`.
784   784  
785   @par Thread Safety 785   @par Thread Safety
786   The wrapper itself should only be used from one thread. 786   The wrapper itself should only be used from one thread.
787   787  
788   @pre `mr` outlives every task started through the returned wrapper. 788   @pre `mr` outlives every task started through the returned wrapper.
789   789  
790   @param ex The executor to execute the task on. 790   @param ex The executor to execute the task on.
791   @param mr The memory resource for frame allocation. 791   @param mr The memory resource for frame allocation.
792   792  
793   @return A wrapper that accepts a `task<T>` for immediate execution. 793   @return A wrapper that accepts a `task<T>` for immediate execution.
794   794  
795   @see task 795   @see task
796   @see Executor 796   @see Executor
797   @see run_async_wrapper 797   @see run_async_wrapper
798   */ 798   */
799   template<Executor Ex> 799   template<Executor Ex>
800   [[nodiscard]] auto 800   [[nodiscard]] auto
HITCBC 801   16 run_async(Ex ex, std::pmr::memory_resource* mr) 801   16 run_async(Ex ex, std::pmr::memory_resource* mr)
802   { 802   {
803   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>( 803   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
HITCBC 804   16 std::move(ex), 804   16 std::move(ex),
HITCBC 805   32 std::stop_token{}, 805   32 std::stop_token{},
806   detail::default_handler{}, 806   detail::default_handler{},
HITCBC 807   16 mr); 807   16 mr);
808   } 808   }
809   809  
810   /** Bind an executor, a memory resource, and a result handler to produce a launcher. Invoke the launcher with a task to start it. 810   /** Bind an executor, a memory resource, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
811   811  
812   Construct the task as the direct argument of the two-call expression 812   Construct the task as the direct argument of the two-call expression
813   `run_async(ex)(task)`. 813   `run_async(ex)(task)`.
814   814  
815   @par Thread Safety 815   @par Thread Safety
816   The wrapper itself should only be used from one thread. The handlers 816   The wrapper itself should only be used from one thread. The handlers
817   may be invoked from any thread where the executor schedules work. 817   may be invoked from any thread where the executor schedules work.
818   818  
819   @pre `mr` outlives every task started through the returned wrapper. 819   @pre `mr` outlives every task started through the returned wrapper.
820   820  
821   @param ex The executor to execute the task on. 821   @param ex The executor to execute the task on.
822   @param mr The memory resource for frame allocation. 822   @param mr The memory resource for frame allocation.
823   @param h1 The handler to invoke with the result (and optionally exception). 823   @param h1 The handler to invoke with the result (and optionally exception).
824   824  
825   @return A wrapper that accepts a `task<T>` for immediate execution. 825   @return A wrapper that accepts a `task<T>` for immediate execution.
826   826  
827   @see task 827   @see task
828   @see Executor 828   @see Executor
829   @see run_async_wrapper 829   @see run_async_wrapper
830   */ 830   */
831   template<Executor Ex, class H1> 831   template<Executor Ex, class H1>
832   [[nodiscard]] auto 832   [[nodiscard]] auto
HITCBC 833   1 run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1) 833   1 run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
834   { 834   {
835   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>( 835   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
HITCBC 836   1 std::move(ex), 836   1 std::move(ex),
HITCBC 837   1 std::stop_token{}, 837   1 std::stop_token{},
HITCBC 838   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 838   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 839   2 mr); 839   2 mr);
840   } 840   }
841   841  
842   /** Bind an executor, a memory resource, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it. 842   /** Bind an executor, a memory resource, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
843   843  
844   Construct the task as the direct argument of the two-call expression 844   Construct the task as the direct argument of the two-call expression
845   `run_async(ex)(task)`. 845   `run_async(ex)(task)`.
846   846  
847   @par Thread Safety 847   @par Thread Safety
848   The wrapper itself should only be used from one thread. The handlers 848   The wrapper itself should only be used from one thread. The handlers
849   may be invoked from any thread where the executor schedules work. 849   may be invoked from any thread where the executor schedules work.
850   850  
851   @pre `mr` outlives every task started through the returned wrapper. 851   @pre `mr` outlives every task started through the returned wrapper.
852   852  
853   @param ex The executor to execute the task on. 853   @param ex The executor to execute the task on.
854   @param mr The memory resource for frame allocation. 854   @param mr The memory resource for frame allocation.
855   @param h1 The handler to invoke with the result on success. 855   @param h1 The handler to invoke with the result on success.
856   @param h2 The handler to invoke with the exception on failure. 856   @param h2 The handler to invoke with the exception on failure.
857   857  
858   @return A wrapper that accepts a `task<T>` for immediate execution. 858   @return A wrapper that accepts a `task<T>` for immediate execution.
859   859  
860   @see task 860   @see task
861   @see Executor 861   @see Executor
862   @see run_async_wrapper 862   @see run_async_wrapper
863   */ 863   */
864   template<Executor Ex, class H1, class H2> 864   template<Executor Ex, class H1, class H2>
865   [[nodiscard]] auto 865   [[nodiscard]] auto
866   run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2) 866   run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
867   { 867   {
868   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>( 868   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
869   std::move(ex), 869   std::move(ex),
870   std::stop_token{}, 870   std::stop_token{},
871   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 871   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
872   mr); 872   mr);
873   } 873   }
874   874  
875   // Ex + stop_token + memory_resource* 875   // Ex + stop_token + memory_resource*
876   876  
877   /** Bind an executor, a stop token, and a memory resource to produce a launcher. Invoke the launcher with a task to start it. 877   /** Bind an executor, a stop token, and a memory resource to produce a launcher. Invoke the launcher with a task to start it.
878   878  
879   Construct the task as the direct argument of the two-call expression 879   Construct the task as the direct argument of the two-call expression
880   `run_async(ex)(task)`. 880   `run_async(ex)(task)`.
881   881  
882   @par Thread Safety 882   @par Thread Safety
883   The wrapper itself should only be used from one thread. 883   The wrapper itself should only be used from one thread.
884   884  
885   @pre `mr` outlives every task started through the returned wrapper. 885   @pre `mr` outlives every task started through the returned wrapper.
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 mr The memory resource for frame allocation. 889   @param mr The memory resource for frame allocation.
890   890  
891   @return A wrapper that accepts a `task<T>` for immediate execution. 891   @return A wrapper that accepts a `task<T>` for immediate execution.
892   892  
893   @see task 893   @see task
894   @see Executor 894   @see Executor
895   @see run_async_wrapper 895   @see run_async_wrapper
896   */ 896   */
897   template<Executor Ex> 897   template<Executor Ex>
898   [[nodiscard]] auto 898   [[nodiscard]] auto
HITCBC 899   1 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr) 899   1 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
900   { 900   {
901   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>( 901   return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
HITCBC 902   1 std::move(ex), 902   1 std::move(ex),
HITCBC 903   1 std::move(st), 903   1 std::move(st),
904   detail::default_handler{}, 904   detail::default_handler{},
HITCBC 905   2 mr); 905   2 mr);
906   } 906   }
907   907  
908   /** Bind an executor, a stop token, a memory resource, and a result handler to produce a launcher. Invoke the launcher with a task to start it. 908   /** Bind an executor, a stop token, a memory resource, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
909   909  
910   Construct the task as the direct argument of the two-call expression 910   Construct the task as the direct argument of the two-call expression
911   `run_async(ex)(task)`. 911   `run_async(ex)(task)`.
912   912  
913   @par Thread Safety 913   @par Thread Safety
914   The wrapper itself should only be used from one thread. The handlers 914   The wrapper itself should only be used from one thread. The handlers
915   may be invoked from any thread where the executor schedules work. 915   may be invoked from any thread where the executor schedules work.
916   916  
917   @pre `mr` outlives every task started through the returned wrapper. 917   @pre `mr` outlives every task started through the returned wrapper.
918   918  
919   @param ex The executor to execute the task on. 919   @param ex The executor to execute the task on.
920   @param st The stop token for cooperative cancellation. 920   @param st The stop token for cooperative cancellation.
921   @param mr The memory resource for frame allocation. 921   @param mr The memory resource for frame allocation.
922   @param h1 The handler to invoke with the result (and optionally exception). 922   @param h1 The handler to invoke with the result (and optionally exception).
923   923  
924   @return A wrapper that accepts a `task<T>` for immediate execution. 924   @return A wrapper that accepts a `task<T>` for immediate execution.
925   925  
926   @see task 926   @see task
927   @see Executor 927   @see Executor
928   @see run_async_wrapper 928   @see run_async_wrapper
929   */ 929   */
930   template<Executor Ex, class H1> 930   template<Executor Ex, class H1>
931   [[nodiscard]] auto 931   [[nodiscard]] auto
932   run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1) 932   run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
933   { 933   {
934   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>( 934   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
935   std::move(ex), 935   std::move(ex),
936   std::move(st), 936   std::move(st),
937   detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 937   detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
938   mr); 938   mr);
939   } 939   }
940   940  
941   /** Bind an executor, a stop token, a memory resource, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it. 941   /** Bind an executor, a stop token, a memory resource, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
942   942  
943   Construct the task as the direct argument of the two-call expression 943   Construct the task as the direct argument of the two-call expression
944   `run_async(ex)(task)`. 944   `run_async(ex)(task)`.
945   945  
946   @par Thread Safety 946   @par Thread Safety
947   The wrapper itself should only be used from one thread. The handlers 947   The wrapper itself should only be used from one thread. The handlers
948   may be invoked from any thread where the executor schedules work. 948   may be invoked from any thread where the executor schedules work.
949   949  
950   @pre `mr` outlives every task started through the returned wrapper. 950   @pre `mr` outlives every task started through the returned wrapper.
951   951  
952   @param ex The executor to execute the task on. 952   @param ex The executor to execute the task on.
953   @param st The stop token for cooperative cancellation. 953   @param st The stop token for cooperative cancellation.
954   @param mr The memory resource for frame allocation. 954   @param mr The memory resource for frame allocation.
955   @param h1 The handler to invoke with the result on success. 955   @param h1 The handler to invoke with the result on success.
956   @param h2 The handler to invoke with the exception on failure. 956   @param h2 The handler to invoke with the exception on failure.
957   957  
958   @return A wrapper that accepts a `task<T>` for immediate execution. 958   @return A wrapper that accepts a `task<T>` for immediate execution.
959   959  
960   @see task 960   @see task
961   @see Executor 961   @see Executor
962   @see run_async_wrapper 962   @see run_async_wrapper
963   */ 963   */
964   template<Executor Ex, class H1, class H2> 964   template<Executor Ex, class H1, class H2>
965   [[nodiscard]] auto 965   [[nodiscard]] auto
HITCBC 966   1 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2) 966   1 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
967   { 967   {
968   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>( 968   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
HITCBC 969   1 std::move(ex), 969   1 std::move(ex),
HITCBC 970   1 std::move(st), 970   1 std::move(st),
971   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 971   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
HITCBC 972   2 mr); 972   2 mr);
973   } 973   }
974   974  
975   // Ex + standard Allocator (value type) 975   // Ex + standard Allocator (value type)
976   976  
977   /** Bind an executor and an allocator to produce a launcher. Invoke the launcher with a task to start it. 977   /** Bind an executor and an allocator to produce a launcher. Invoke the launcher with a task to start it.
978   978  
979   The allocator is wrapped in a frame_memory_resource and stored in the 979   The allocator is wrapped in a frame_memory_resource and stored in the
980   run_async_trampoline, ensuring it outlives all coroutine frames. 980   run_async_trampoline, ensuring it outlives all coroutine frames.
981   981  
982   Construct the task as the direct argument of the two-call expression 982   Construct the task as the direct argument of the two-call expression
983   `run_async(ex)(task)`. 983   `run_async(ex)(task)`.
984   984  
985   @par Thread Safety 985   @par Thread Safety
986   The wrapper itself should only be used from one thread. 986   The wrapper itself should only be used from one thread.
987   987  
988   @param ex The executor to execute the task on. 988   @param ex The executor to execute the task on.
989   @param alloc The allocator for frame allocation (copied and stored). 989   @param alloc The allocator for frame allocation (copied and stored).
990   990  
991   @return A wrapper that accepts a `task<T>` for immediate execution. 991   @return A wrapper that accepts a `task<T>` for immediate execution.
992   992  
993   @see task 993   @see task
994   @see Executor 994   @see Executor
995   @see run_async_wrapper 995   @see run_async_wrapper
996   */ 996   */
997   template<Executor Ex, detail::Allocator Alloc> 997   template<Executor Ex, detail::Allocator Alloc>
998   [[nodiscard]] auto 998   [[nodiscard]] auto
HITCBC 999   1 run_async(Ex ex, Alloc alloc) 999   1 run_async(Ex ex, Alloc alloc)
1000   { 1000   {
1001   return run_async_wrapper<Ex, detail::default_handler, Alloc>( 1001   return run_async_wrapper<Ex, detail::default_handler, Alloc>(
HITCBC 1002   1 std::move(ex), 1002   1 std::move(ex),
HITCBC 1003   2 std::stop_token{}, 1003   2 std::stop_token{},
1004   detail::default_handler{}, 1004   detail::default_handler{},
HITCBC 1005   2 std::move(alloc)); 1005   2 std::move(alloc));
1006   } 1006   }
1007   1007  
1008   /** Bind an executor, an allocator, and a result handler to produce a launcher. Invoke the launcher with a task to start it. 1008   /** Bind an executor, an allocator, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
1009   1009  
1010   Construct the task as the direct argument of the two-call expression 1010   Construct the task as the direct argument of the two-call expression
1011   `run_async(ex)(task)`. 1011   `run_async(ex)(task)`.
1012   1012  
1013   @par Thread Safety 1013   @par Thread Safety
1014   The wrapper itself should only be used from one thread. The handlers 1014   The wrapper itself should only be used from one thread. The handlers
1015   may be invoked from any thread where the executor schedules work. 1015   may be invoked from any thread where the executor schedules work.
1016   1016  
1017   @param ex The executor to execute the task on. 1017   @param ex The executor to execute the task on.
1018   @param alloc The allocator for frame allocation (copied and stored). 1018   @param alloc The allocator for frame allocation (copied and stored).
1019   @param h1 The handler to invoke with the result (and optionally exception). 1019   @param h1 The handler to invoke with the result (and optionally exception).
1020   1020  
1021   @return A wrapper that accepts a `task<T>` for immediate execution. 1021   @return A wrapper that accepts a `task<T>` for immediate execution.
1022   1022  
1023   @see task 1023   @see task
1024   @see Executor 1024   @see Executor
1025   @see run_async_wrapper 1025   @see run_async_wrapper
1026   */ 1026   */
1027   template<Executor Ex, detail::Allocator Alloc, class H1> 1027   template<Executor Ex, detail::Allocator Alloc, class H1>
1028   [[nodiscard]] auto 1028   [[nodiscard]] auto
HITCBC 1029   1 run_async(Ex ex, Alloc alloc, H1 h1) 1029   1 run_async(Ex ex, Alloc alloc, H1 h1)
1030   { 1030   {
1031   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>( 1031   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
HITCBC 1032   1 std::move(ex), 1032   1 std::move(ex),
HITCBC 1033   1 std::stop_token{}, 1033   1 std::stop_token{},
HITCBC 1034   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 1034   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 1035   4 std::move(alloc)); 1035   4 std::move(alloc));
1036   } 1036   }
1037   1037  
1038   /** Bind an executor, an allocator, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it. 1038   /** Bind an executor, an allocator, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
1039   1039  
1040   Construct the task as the direct argument of the two-call expression 1040   Construct the task as the direct argument of the two-call expression
1041   `run_async(ex)(task)`. 1041   `run_async(ex)(task)`.
1042   1042  
1043   @par Thread Safety 1043   @par Thread Safety
1044   The wrapper itself should only be used from one thread. The handlers 1044   The wrapper itself should only be used from one thread. The handlers
1045   may be invoked from any thread where the executor schedules work. 1045   may be invoked from any thread where the executor schedules work.
1046   1046  
1047   @param ex The executor to execute the task on. 1047   @param ex The executor to execute the task on.
1048   @param alloc The allocator for frame allocation (copied and stored). 1048   @param alloc The allocator for frame allocation (copied and stored).
1049   @param h1 The handler to invoke with the result on success. 1049   @param h1 The handler to invoke with the result on success.
1050   @param h2 The handler to invoke with the exception on failure. 1050   @param h2 The handler to invoke with the exception on failure.
1051   1051  
1052   @return A wrapper that accepts a `task<T>` for immediate execution. 1052   @return A wrapper that accepts a `task<T>` for immediate execution.
1053   1053  
1054   @see task 1054   @see task
1055   @see Executor 1055   @see Executor
1056   @see run_async_wrapper 1056   @see run_async_wrapper
1057   */ 1057   */
1058   template<Executor Ex, detail::Allocator Alloc, class H1, class H2> 1058   template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
1059   [[nodiscard]] auto 1059   [[nodiscard]] auto
HITCBC 1060   1 run_async(Ex ex, Alloc alloc, H1 h1, H2 h2) 1060   1 run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
1061   { 1061   {
1062   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>( 1062   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
HITCBC 1063   1 std::move(ex), 1063   1 std::move(ex),
HITCBC 1064   1 std::stop_token{}, 1064   1 std::stop_token{},
HITCBC 1065   1 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 1065   1 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
HITCBC 1066   4 std::move(alloc)); 1066   4 std::move(alloc));
1067   } 1067   }
1068   1068  
1069   // Ex + stop_token + standard Allocator 1069   // Ex + stop_token + standard Allocator
1070   1070  
1071   /** Bind an executor, a stop token, and an allocator to produce a launcher. Invoke the launcher with a task to start it. 1071   /** Bind an executor, a stop token, and an allocator to produce a launcher. Invoke the launcher with a task to start it.
1072   1072  
1073   Construct the task as the direct argument of the two-call expression 1073   Construct the task as the direct argument of the two-call expression
1074   `run_async(ex)(task)`. 1074   `run_async(ex)(task)`.
1075   1075  
1076   @par Thread Safety 1076   @par Thread Safety
1077   The wrapper itself should only be used from one thread. 1077   The wrapper itself should only be used from one thread.
1078   1078  
1079   @param ex The executor to execute the task on. 1079   @param ex The executor to execute the task on.
1080   @param st The stop token for cooperative cancellation. 1080   @param st The stop token for cooperative cancellation.
1081   @param alloc The allocator for frame allocation (copied and stored). 1081   @param alloc The allocator for frame allocation (copied and stored).
1082   1082  
1083   @return A wrapper that accepts a `task<T>` for immediate execution. 1083   @return A wrapper that accepts a `task<T>` for immediate execution.
1084   1084  
1085   @see task 1085   @see task
1086   @see Executor 1086   @see Executor
1087   @see run_async_wrapper 1087   @see run_async_wrapper
1088   */ 1088   */
1089   template<Executor Ex, detail::Allocator Alloc> 1089   template<Executor Ex, detail::Allocator Alloc>
1090   [[nodiscard]] auto 1090   [[nodiscard]] auto
1091   run_async(Ex ex, std::stop_token st, Alloc alloc) 1091   run_async(Ex ex, std::stop_token st, Alloc alloc)
1092   { 1092   {
1093   return run_async_wrapper<Ex, detail::default_handler, Alloc>( 1093   return run_async_wrapper<Ex, detail::default_handler, Alloc>(
1094   std::move(ex), 1094   std::move(ex),
1095   std::move(st), 1095   std::move(st),
1096   detail::default_handler{}, 1096   detail::default_handler{},
1097   std::move(alloc)); 1097   std::move(alloc));
1098   } 1098   }
1099   1099  
1100   /** Bind an executor, a stop token, an allocator, and a result handler to produce a launcher. Invoke the launcher with a task to start it. 1100   /** Bind an executor, a stop token, an allocator, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
1101   1101  
1102   Construct the task as the direct argument of the two-call expression 1102   Construct the task as the direct argument of the two-call expression
1103   `run_async(ex)(task)`. 1103   `run_async(ex)(task)`.
1104   1104  
1105   @par Thread Safety 1105   @par Thread Safety
1106   The wrapper itself should only be used from one thread. The handlers 1106   The wrapper itself should only be used from one thread. The handlers
1107   may be invoked from any thread where the executor schedules work. 1107   may be invoked from any thread where the executor schedules work.
1108   1108  
1109   @param ex The executor to execute the task on. 1109   @param ex The executor to execute the task on.
1110   @param st The stop token for cooperative cancellation. 1110   @param st The stop token for cooperative cancellation.
1111   @param alloc The allocator for frame allocation (copied and stored). 1111   @param alloc The allocator for frame allocation (copied and stored).
1112   @param h1 The handler to invoke with the result (and optionally exception). 1112   @param h1 The handler to invoke with the result (and optionally exception).
1113   1113  
1114   @return A wrapper that accepts a `task<T>` for immediate execution. 1114   @return A wrapper that accepts a `task<T>` for immediate execution.
1115   1115  
1116   @see task 1116   @see task
1117   @see Executor 1117   @see Executor
1118   @see run_async_wrapper 1118   @see run_async_wrapper
1119   */ 1119   */
1120   template<Executor Ex, detail::Allocator Alloc, class H1> 1120   template<Executor Ex, detail::Allocator Alloc, class H1>
1121   [[nodiscard]] auto 1121   [[nodiscard]] auto
1122   run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1) 1122   run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
1123   { 1123   {
1124   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>( 1124   return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
1125   std::move(ex), 1125   std::move(ex),
1126   std::move(st), 1126   std::move(st),
1127   detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 1127   detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
1128   std::move(alloc)); 1128   std::move(alloc));
1129   } 1129   }
1130   1130  
1131   /** Bind an executor, a stop token, an allocator, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it. 1131   /** Bind an executor, a stop token, an allocator, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
1132   1132  
1133   Construct the task as the direct argument of the two-call expression 1133   Construct the task as the direct argument of the two-call expression
1134   `run_async(ex)(task)`. 1134   `run_async(ex)(task)`.
1135   1135  
1136   @par Thread Safety 1136   @par Thread Safety
1137   The wrapper itself should only be used from one thread. The handlers 1137   The wrapper itself should only be used from one thread. The handlers
1138   may be invoked from any thread where the executor schedules work. 1138   may be invoked from any thread where the executor schedules work.
1139   1139  
1140   @param ex The executor to execute the task on. 1140   @param ex The executor to execute the task on.
1141   @param st The stop token for cooperative cancellation. 1141   @param st The stop token for cooperative cancellation.
1142   @param alloc The allocator for frame allocation (copied and stored). 1142   @param alloc The allocator for frame allocation (copied and stored).
1143   @param h1 The handler to invoke with the result on success. 1143   @param h1 The handler to invoke with the result on success.
1144   @param h2 The handler to invoke with the exception on failure. 1144   @param h2 The handler to invoke with the exception on failure.
1145   1145  
1146   @return A wrapper that accepts a `task<T>` for immediate execution. 1146   @return A wrapper that accepts a `task<T>` for immediate execution.
1147   1147  
1148   @see task 1148   @see task
1149   @see Executor 1149   @see Executor
1150   @see run_async_wrapper 1150   @see run_async_wrapper
1151   */ 1151   */
1152   template<Executor Ex, detail::Allocator Alloc, class H1, class H2> 1152   template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
1153   [[nodiscard]] auto 1153   [[nodiscard]] auto
1154   run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2) 1154   run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
1155   { 1155   {
1156   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>( 1156   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
1157   std::move(ex), 1157   std::move(ex),
1158   std::move(st), 1158   std::move(st),
1159   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 1159   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
1160   std::move(alloc)); 1160   std::move(alloc));
1161   } 1161   }
1162   1162  
1163   } // namespace capy 1163   } // namespace capy
1164   } // namespace boost 1164   } // namespace boost
1165   1165  
1166   #endif 1166   #endif