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