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