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