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