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   1814 bool await_ready() const noexcept { return false; } 72   1799 bool await_ready() const noexcept { return false; }
73   73  
HITCBC 74   1814 bool await_suspend(std::coroutine_handle<Promise> h) noexcept 74   1799 bool await_suspend(std::coroutine_handle<Promise> h) noexcept
75   { 75   {
HITCBC 76   1814 p_ = &h.promise(); 76   1799 p_ = &h.promise();
HITCBC 77   1814 return false; 77   1799 return false;
78   } 78   }
79   79  
HITCBC 80   1814 Promise& await_resume() const noexcept 80   1799 Promise& await_resume() const noexcept
81   { 81   {
HITCBC 82   1814 return *p_; 82   1799 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   1947 promise_type( 233   1954 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   1947 : wg_(std::move(ex)) 235   1954 : wg_(std::move(ex))
HITCBC 236   1947 , handlers_(std::move(h)) 236   1954 , handlers_(std::move(h))
HITCBC 237   1947 , mr_(mr) 237   1954 , mr_(mr)
238   { 238   {
HITCBC 239   1947 } 239   1954 }
240   240  
HITCBC 241   1947 static void* operator new( 241   1954 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   1947 auto total = size + sizeof(mr); 245   1954 auto total = size + sizeof(mr);
HITCBC 246   1947 void* raw = mr->allocate(total, alignof(std::max_align_t)); 246   1954 void* raw = mr->allocate(total, alignof(std::max_align_t));
HITCBC 247   1947 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr)); 247   1954 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
HITCBC 248   1947 return raw; 248   1954 return raw;
249   } 249   }
250   250  
HITCBC 251   1947 static void operator delete(void* ptr, std::size_t size) 251   1954 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   1947 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr)); 254   1954 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
HITCBC 255   1947 auto total = size + sizeof(mr); 255   1954 auto total = size + sizeof(mr);
HITCBC 256   1947 mr->deallocate(ptr, total, alignof(std::max_align_t)); 256   1954 mr->deallocate(ptr, total, alignof(std::max_align_t));
HITCBC 257   1947 } 257   1954 }
258   258  
HITCBC 259   3894 std::pmr::memory_resource* get_resource() noexcept 259   3908 std::pmr::memory_resource* get_resource() noexcept
260   { 260   {
HITCBC 261   3894 return mr_; 261   3908 return mr_;
262   } 262   }
263   263  
HITCBC 264   1947 run_async_trampoline get_return_object() noexcept 264   1954 run_async_trampoline get_return_object() noexcept
265   { 265   {
266   return run_async_trampoline{ 266   return run_async_trampoline{
HITCBC 267   1947 std::coroutine_handle<promise_type>::from_promise(*this)}; 267   1954 std::coroutine_handle<promise_type>::from_promise(*this)};
268   } 268   }
269   269  
HITCBC 270   1947 std::suspend_always initial_suspend() noexcept 270   1954 std::suspend_always initial_suspend() noexcept
271   { 271   {
HITCBC 272   1947 return {}; 272   1954 return {};
273   } 273   }
274   274  
HITCBC 275   1811 std::suspend_never final_suspend() noexcept 275   1796 std::suspend_never final_suspend() noexcept
276   { 276   {
HITCBC 277   1811 return {}; 277   1796 return {};
278   } 278   }
279   279  
HITCBC 280   1811 void return_void() noexcept 280   1796 void return_void() noexcept
281   { 281   {
HITCBC 282   1811 } 282   1796 }
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   1811 static void invoke_impl(void* p, Handlers& h) 291   1796 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   1811 auto& promise = *static_cast<typename Task::promise_type*>(p); 294   1796 auto& promise = *static_cast<typename Task::promise_type*>(p);
HITCBC 295   1811 if(promise.exception()) 295   1796 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   1160 h(); 298   1145 h();
299   else 299   else
HITCBC 300   278 h(std::move(promise.result())); 300   278 h(std::move(promise.result()));
HITCBC 301   1811 } 301   1796 }
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   1950 make_trampoline(Ex, Handlers, Alloc) 307   1957 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   1814 ~frame_guard() { h.destroy(); } 318   1799 ~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   3904 } 322   3918 }
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   @par !example example 376   @par !example example
377   377  
378   378  
379   @see run_async 379   @see run_async
380   */ 380   */
381   template<Executor Ex, class Handlers, class Alloc> 381   template<Executor Ex, class Handlers, class Alloc>
382   class [[nodiscard]] run_async_wrapper 382   class [[nodiscard]] run_async_wrapper
383   { 383   {
384   detail::run_async_trampoline<Ex, Handlers, Alloc> tr_; 384   detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
385   std::stop_token st_; 385   std::stop_token st_;
386   std::pmr::memory_resource* saved_tls_; 386   std::pmr::memory_resource* saved_tls_;
387   387  
388   public: 388   public:
389   /** Construct the wrapper and install the frame allocator. 389   /** Construct the wrapper and install the frame allocator.
390   390  
391   Builds the trampoline and saves the current thread-local frame 391   Builds the trampoline and saves the current thread-local frame
392   allocator. Then installs the trampoline's resource as the new 392   allocator. Then installs the trampoline's resource as the new
393   thread-local allocator. The task frame, evaluated as the argument 393   thread-local allocator. The task frame, evaluated as the argument
394   to @ref operator(), is therefore allocated from that resource. 394   to @ref operator(), is therefore allocated from that resource.
395   395  
396   @param ex The executor on which the task runs. 396   @param ex The executor on which the task runs.
397   @param st The stop token for cooperative cancellation. 397   @param st The stop token for cooperative cancellation.
398   @param h The completion handlers. 398   @param h The completion handlers.
399   @param a The allocator for frame allocation. 399   @param a The allocator for frame allocation.
400   400  
401   @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
402   nothrow move constructible (enforced by a `static_assert`), which 402   nothrow move constructible (enforced by a `static_assert`), which
403   is what allows this constructor to be `noexcept`. 403   is what allows this constructor to be `noexcept`.
404   */ 404   */
HITCBC 405   1950 run_async_wrapper( 405   1957 run_async_wrapper(
406   Ex ex, 406   Ex ex,
407   std::stop_token st, 407   std::stop_token st,
408   Handlers h, 408   Handlers h,
409   Alloc a) noexcept 409   Alloc a) noexcept
HITCBC 410   1951 : tr_(detail::make_trampoline<Ex, Handlers, Alloc>( 410   1958 : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
HITCBC 411   1953 std::move(ex), std::move(h), std::move(a))) 411   1960 std::move(ex), std::move(h), std::move(a)))
HITCBC 412   1950 , st_(std::move(st)) 412   1957 , st_(std::move(st))
HITCBC 413   1950 , saved_tls_(get_current_frame_allocator()) 413   1957 , saved_tls_(get_current_frame_allocator())
414   { 414   {
415   if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>) 415   if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
416   { 416   {
417   static_assert( 417   static_assert(
418   std::is_nothrow_move_constructible_v<Alloc>, 418   std::is_nothrow_move_constructible_v<Alloc>,
419   "Allocator must be nothrow move constructible"); 419   "Allocator must be nothrow move constructible");
420   } 420   }
421   // Set TLS before task argument is evaluated 421   // Set TLS before task argument is evaluated
HITCBC 422   1950 set_current_frame_allocator(tr_.h_.promise().get_resource()); 422   1957 set_current_frame_allocator(tr_.h_.promise().get_resource());
HITCBC 423   1950 } 423   1957 }
424   424  
425   /** Restore the previously installed frame allocator. 425   /** Restore the previously installed frame allocator.
426   426  
427   Resets the thread-local frame allocator to the value saved at 427   Resets the thread-local frame allocator to the value saved at
428   construction. A stale pointer to the trampoline's resource 428   construction. A stale pointer to the trampoline's resource
429   therefore does not outlive the execution context that owns it. 429   therefore does not outlive the execution context that owns it.
430   */ 430   */
HITCBC 431   1950 ~run_async_wrapper() 431   1957 ~run_async_wrapper()
432   { 432   {
HITCBC 433   1950 set_current_frame_allocator(saved_tls_); 433   1957 set_current_frame_allocator(saved_tls_);
HITCBC 434   1950 } 434   1957 }
435   435  
436   // Non-copyable, non-movable (must be used immediately) 436   // Non-copyable, non-movable (must be used immediately)
437   437  
438   /** Copy construction is disabled; the wrapper must be used immediately. 438   /** Copy construction is disabled; the wrapper must be used immediately.
439   439  
440   @param other The wrapper that would be copied. 440   @param other The wrapper that would be copied.
441   */ 441   */
442   run_async_wrapper(run_async_wrapper const& other) = delete; 442   run_async_wrapper(run_async_wrapper const& other) = delete;
443   443  
444   /** Move construction is disabled; the wrapper must be used immediately. 444   /** Move construction is disabled; the wrapper must be used immediately.
445   445  
446   @param other The wrapper that would be moved from. 446   @param other The wrapper that would be moved from.
447   */ 447   */
448   run_async_wrapper(run_async_wrapper&& other) = delete; 448   run_async_wrapper(run_async_wrapper&& other) = delete;
449   449  
450   /** Copy assignment is disabled; the wrapper must be used immediately. 450   /** Copy assignment is disabled; the wrapper must be used immediately.
451   451  
452   @param other The wrapper that would be assigned from. 452   @param other The wrapper that would be assigned from.
453   453  
454   @return A reference to `*this`. 454   @return A reference to `*this`.
455   */ 455   */
456   run_async_wrapper& operator=(run_async_wrapper const& other) = delete; 456   run_async_wrapper& operator=(run_async_wrapper const& other) = delete;
457   457  
458   /** Move assignment is disabled; the wrapper must be used immediately. 458   /** Move assignment is disabled; the wrapper must be used immediately.
459   459  
460   @param other The wrapper that would be moved from. 460   @param other The wrapper that would be moved from.
461   461  
462   @return A reference to `*this`. 462   @return A reference to `*this`.
463   */ 463   */
464   run_async_wrapper& operator=(run_async_wrapper&& other) = delete; 464   run_async_wrapper& operator=(run_async_wrapper&& other) = delete;
465   465  
466   /** Start the task for execution. 466   /** Start the task for execution.
467   467  
468   This operator accepts a task and starts it on the executor. 468   This operator accepts a task and starts it on the executor.
469   The rvalue ref-qualifier ensures the wrapper is consumed, enforcing 469   The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
470   correct LIFO destruction order. 470   correct LIFO destruction order.
471   471  
472   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
473   coroutine and is guaranteed to outlive the task and all awaitables 473   coroutine and is guaranteed to outlive the task and all awaitables
474   in its chain. Awaitables may store `io_env const*` without concern 474   in its chain. Awaitables may store `io_env const*` without concern
475   for dangling references. 475   for dangling references.
476   476  
477   @tparam Task The IoRunnable type. 477   @tparam Task The IoRunnable type.
478   478  
479   @param t The task to execute. Ownership is transferred to the 479   @param t The task to execute. Ownership is transferred to the
480   run_async_trampoline which destroys it after completion. 480   run_async_trampoline which destroys it after completion.
481   */ 481   */
482   template<IoRunnable Task> 482   template<IoRunnable Task>
HITCBC 483   1950 void operator()(Task t) && 483   1957 void operator()(Task t) &&
484   { 484   {
HITCBC 485   1950 auto task_h = t.handle(); 485   1957 auto task_h = t.handle();
HITCBC 486   1950 auto& task_promise = task_h.promise(); 486   1957 auto& task_promise = task_h.promise();
HITCBC 487   1950 t.release(); 487   1957 t.release();
488   488  
HITCBC 489   1950 auto& p = tr_.h_.promise(); 489   1957 auto& p = tr_.h_.promise();
490   490  
491   // Inject Task-specific invoke function 491   // Inject Task-specific invoke function
HITCBC 492   1950 p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>; 492   1957 p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
HITCBC 493   1950 p.task_promise_ = &task_promise; 493   1957 p.task_promise_ = &task_promise;
HITCBC 494   1950 p.task_h_ = task_h; 494   1957 p.task_h_ = task_h;
495   495  
496   // Setup task's continuation to return to run_async_trampoline 496   // Setup task's continuation to return to run_async_trampoline
HITCBC 497   1950 task_promise.set_continuation(tr_.h_); 497   1957 task_promise.set_continuation(tr_.h_);
HITCBC 498   3900 p.env_ = {p.wg_.executor(), st_, p.get_resource()}; 498   3914 p.env_ = {p.wg_.executor(), st_, p.get_resource()};
HITCBC 499   1950 task_promise.set_environment(&p.env_); 499   1957 task_promise.set_environment(&p.env_);
500   500  
501   // Start task through executor. 501   // Start task through executor.
502   // 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
503   // constructor (saved_tls_) and restored in the destructor. 503   // constructor (saved_tls_) and restored in the destructor.
HITCBC 504   1950 p.task_cont_.h = task_h; 504   1957 p.task_cont_.h = task_h;
HITCBC 505   1950 p.wg_.executor().dispatch(p.task_cont_).resume(); 505   1957 p.wg_.executor().dispatch(p.task_cont_).resume();
HITCBC 506   3900 } 506   3914 }
507   }; 507   };
508   508  
509   // Executor only (uses default recycling allocator) 509   // Executor only (uses default recycling allocator)
510   510  
511   /** 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.
512   512  
513   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.
514   The returned wrapper must be immediately invoked with the task; 514   The returned wrapper must be immediately invoked with the task;
515   storing the wrapper and calling it later violates LIFO ordering. 515   storing the wrapper and calling it later violates LIFO ordering.
516   516  
517   Uses the default recycling frame allocator for coroutine frames. 517   Uses the default recycling frame allocator for coroutine frames.
518   With no handlers, the result is discarded. An unhandled exception 518   With no handlers, the result is discarded. An unhandled exception
519   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
520   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`
521   the work inside a coroutine. 521   the work inside a coroutine.
522   522  
523   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
524   `run_async(ex)(task)`. 524   `run_async(ex)(task)`.
525   525  
526   @par Thread Safety 526   @par Thread Safety
527   The wrapper itself should only be used from one thread. 527   The wrapper itself should only be used from one thread.
528   528  
529   @par Example 529   @par Example
530   @par !example example_1 530   @par !example example_1
531   531  
532   532  
533   @param ex The executor to execute the task on. 533   @param ex The executor to execute the task on.
534   534  
535   @return A wrapper that accepts a `task<T>` for immediate execution. 535   @return A wrapper that accepts a `task<T>` for immediate execution.
536   536  
537   @see task 537   @see task
538   @see Executor 538   @see Executor
539   @see run_async_wrapper 539   @see run_async_wrapper
540   */ 540   */
541   template<Executor Ex> 541   template<Executor Ex>
542   [[nodiscard]] auto 542   [[nodiscard]] auto
HITCBC 543   218 run_async(Ex ex) 543   225 run_async(Ex ex)
544   { 544   {
HITCBC 545   218 auto* mr = ex.context().get_frame_allocator(); 545   225 auto* mr = ex.context().get_frame_allocator();
546   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 547   218 std::move(ex), 547   225 std::move(ex),
HITCBC 548   436 std::stop_token{}, 548   450 std::stop_token{},
549   detail::default_handler{}, 549   detail::default_handler{},
HITCBC 550   218 mr); 550   225 mr);
551   } 551   }
552   552  
553   /** 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.
554   554  
555   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`
556   is also invocable with `std::exception_ptr`, it handles exceptions too. 556   is also invocable with `std::exception_ptr`, it handles exceptions too.
557   Otherwise, an unhandled exception calls `std::terminate`. 557   Otherwise, an unhandled exception calls `std::terminate`.
558   558  
559   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
560   `run_async(ex)(task)`. 560   `run_async(ex)(task)`.
561   561  
562   @par Thread Safety 562   @par Thread Safety
563   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
564   may be invoked from any thread where the executor schedules work. 564   may be invoked from any thread where the executor schedules work.
565   565  
566   @par Example 566   @par Example
567   @par !example example_2 567   @par !example example_2
568   568  
569   569  
570   @param ex The executor to execute the task on. 570   @param ex The executor to execute the task on.
571   @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).
572   572  
573   @return A wrapper that accepts a `task<T>` for immediate execution. 573   @return A wrapper that accepts a `task<T>` for immediate execution.
574   574  
575   @see task 575   @see task
576   @see Executor 576   @see Executor
577   @see run_async_wrapper 577   @see run_async_wrapper
578   */ 578   */
579   template<Executor Ex, class H1> 579   template<Executor Ex, class H1>
580   requires detail::RunAsyncHandler<H1> 580   requires detail::RunAsyncHandler<H1>
581   [[nodiscard]] auto 581   [[nodiscard]] auto
HITCBC 582   109 run_async(Ex ex, H1 h1) 582   109 run_async(Ex ex, H1 h1)
583   { 583   {
HITCBC 584   109 auto* mr = ex.context().get_frame_allocator(); 584   109 auto* mr = ex.context().get_frame_allocator();
585   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 586   109 std::move(ex), 586   109 std::move(ex),
HITCBC 587   115 std::stop_token{}, 587   115 std::stop_token{},
HITCBC 588   103 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 588   103 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 589   212 mr); 589   212 mr);
590   } 590   }
591   591  
592   /** 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.
593   593  
594   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.
595   The handler `h2` is called with the exception_ptr on failure. 595   The handler `h2` is called with the exception_ptr on failure.
596   596  
597   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
598   `run_async(ex)(task)`. 598   `run_async(ex)(task)`.
599   599  
600   @par Thread Safety 600   @par Thread Safety
601   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
602   may be invoked from any thread where the executor schedules work. 602   may be invoked from any thread where the executor schedules work.
603   603  
604   @par Example 604   @par Example
605   @par !example example_3 605   @par !example example_3
606   606  
607   607  
608   @param ex The executor to execute the task on. 608   @param ex The executor to execute the task on.
609   @param h1 The handler to invoke with the result on success. 609   @param h1 The handler to invoke with the result on success.
610   @param h2 The handler to invoke with the exception on failure. 610   @param h2 The handler to invoke with the exception on failure.
611   611  
612   @return A wrapper that accepts a `task<T>` for immediate execution. 612   @return A wrapper that accepts a `task<T>` for immediate execution.
613   613  
614   @see task 614   @see task
615   @see Executor 615   @see Executor
616   @see run_async_wrapper 616   @see run_async_wrapper
617   */ 617   */
618   template<Executor Ex, class H1, class H2> 618   template<Executor Ex, class H1, class H2>
619   requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>) 619   requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>)
620   [[nodiscard]] auto 620   [[nodiscard]] auto
HITCBC 621   95 run_async(Ex ex, H1 h1, H2 h2) 621   95 run_async(Ex ex, H1 h1, H2 h2)
622   { 622   {
HITCBC 623   95 auto* mr = ex.context().get_frame_allocator(); 623   95 auto* mr = ex.context().get_frame_allocator();
624   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 625   95 std::move(ex), 625   95 std::move(ex),
HITCBC 626   98 std::stop_token{}, 626   98 std::stop_token{},
HITCBC 627   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 628   187 mr); 628   187 mr);
HITCBC 629   1 } 629   1 }
630   630  
631   // Ex + stop_token 631   // Ex + stop_token
632   632  
633   /** 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.
634   634  
635   The stop token is propagated to the task, enabling cooperative 635   The stop token is propagated to the task, enabling cooperative
636   cancellation. With no handlers, the result is discarded and an 636   cancellation. With no handlers, the result is discarded and an
637   unhandled exception calls `std::terminate`. 637   unhandled exception calls `std::terminate`.
638   638  
639   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
640   `run_async(ex)(task)`. 640   `run_async(ex)(task)`.
641   641  
642   @par Thread Safety 642   @par Thread Safety
643   The wrapper itself should only be used from one thread. 643   The wrapper itself should only be used from one thread.
644   644  
645   @par Example 645   @par Example
646   @par !example example_4 646   @par !example example_4
647   647  
648   648  
649   @param ex The executor to execute the task on. 649   @param ex The executor to execute the task on.
650   @param st The stop token for cooperative cancellation. 650   @param st The stop token for cooperative cancellation.
651   651  
652   @return A wrapper that accepts a `task<T>` for immediate execution. 652   @return A wrapper that accepts a `task<T>` for immediate execution.
653   653  
654   @see task 654   @see task
655   @see Executor 655   @see Executor
656   @see run_async_wrapper 656   @see run_async_wrapper
657   */ 657   */
658   template<Executor Ex> 658   template<Executor Ex>
659   [[nodiscard]] auto 659   [[nodiscard]] auto
HITCBC 660   371 run_async(Ex ex, std::stop_token st) 660   371 run_async(Ex ex, std::stop_token st)
661   { 661   {
HITCBC 662   371 auto* mr = ex.context().get_frame_allocator(); 662   371 auto* mr = ex.context().get_frame_allocator();
663   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 664   371 std::move(ex), 664   371 std::move(ex),
HITCBC 665   371 std::move(st), 665   371 std::move(st),
666   detail::default_handler{}, 666   detail::default_handler{},
HITCBC 667   742 mr); 667   742 mr);
668   } 668   }
669   669  
670   /** 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.
671   671  
672   The stop token is propagated to the task for cooperative cancellation. 672   The stop token is propagated to the task for cooperative cancellation.
673   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
674   with exception_ptr if it accepts that type. 674   with exception_ptr if it accepts that type.
675   675  
676   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
677   `run_async(ex)(task)`. 677   `run_async(ex)(task)`.
678   678  
679   @par Thread Safety 679   @par Thread Safety
680   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
681   may be invoked from any thread where the executor schedules work. 681   may be invoked from any thread where the executor schedules work.
682   682  
683   @param ex The executor to execute the task on. 683   @param ex The executor to execute the task on.
684   @param st The stop token for cooperative cancellation. 684   @param st The stop token for cooperative cancellation.
685   @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).
686   686  
687   @return A wrapper that accepts a `task<T>` for immediate execution. 687   @return A wrapper that accepts a `task<T>` for immediate execution.
688   688  
689   @see task 689   @see task
690   @see Executor 690   @see Executor
691   @see run_async_wrapper 691   @see run_async_wrapper
692   */ 692   */
693   template<Executor Ex, class H1> 693   template<Executor Ex, class H1>
694   requires detail::RunAsyncHandler<H1> 694   requires detail::RunAsyncHandler<H1>
695   [[nodiscard]] auto 695   [[nodiscard]] auto
HITCBC 696   1123 run_async(Ex ex, std::stop_token st, H1 h1) 696   1123 run_async(Ex ex, std::stop_token st, H1 h1)
697   { 697   {
HITCBC 698   1123 auto* mr = ex.context().get_frame_allocator(); 698   1123 auto* mr = ex.context().get_frame_allocator();
699   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 700   1123 std::move(ex), 700   1123 std::move(ex),
HITCBC 701   1123 std::move(st), 701   1123 std::move(st),
HITCBC 702   1123 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 702   1123 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 703   2246 mr); 703   2246 mr);
704   } 704   }
705   705  
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. 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.
707   707  
708   The stop token is propagated to the task for cooperative cancellation. 708   The stop token is propagated to the task for cooperative cancellation.
709   The handler `h1` is called on success, `h2` on failure. 709   The handler `h1` is called on success, `h2` on failure.
710   710  
711   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
712   `run_async(ex)(task)`. 712   `run_async(ex)(task)`.
713   713  
714   @par Thread Safety 714   @par Thread Safety
715   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
716   may be invoked from any thread where the executor schedules work. 716   may be invoked from any thread where the executor schedules work.
717   717  
718   @param ex The executor to execute the task on. 718   @param ex The executor to execute the task on.
719   @param st The stop token for cooperative cancellation. 719   @param st The stop token for cooperative cancellation.
720   @param h1 The handler to invoke with the result on success. 720   @param h1 The handler to invoke with the result on success.
721   @param h2 The handler to invoke with the exception on failure. 721   @param h2 The handler to invoke with the exception on failure.
722   722  
723   @return A wrapper that accepts a `task<T>` for immediate execution. 723   @return A wrapper that accepts a `task<T>` for immediate execution.
724   724  
725   @see task 725   @see task
726   @see Executor 726   @see Executor
727   @see run_async_wrapper 727   @see run_async_wrapper
728   */ 728   */
729   template<Executor Ex, class H1, class H2> 729   template<Executor Ex, class H1, class H2>
730   requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>) 730   requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>)
731   [[nodiscard]] auto 731   [[nodiscard]] auto
HITCBC 732   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)
733   { 733   {
HITCBC 734   12 auto* mr = ex.context().get_frame_allocator(); 734   12 auto* mr = ex.context().get_frame_allocator();
735   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 736   12 std::move(ex), 736   12 std::move(ex),
HITCBC 737   12 std::move(st), 737   12 std::move(st),
HITCBC 738   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 739   24 mr); 739   24 mr);
740   } 740   }
741   741  
742   // Ex + memory_resource* 742   // Ex + memory_resource*
743   743  
744   /** 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.
745   745  
746   The memory resource is used for coroutine frame allocation. 746   The memory resource is used for coroutine frame allocation.
747   747  
748   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
749   `run_async(ex)(task)`. 749   `run_async(ex)(task)`.
750   750  
751   @par Thread Safety 751   @par Thread Safety
752   The wrapper itself should only be used from one thread. 752   The wrapper itself should only be used from one thread.
753   753  
754   @pre `mr` outlives every task started through the returned wrapper. 754   @pre `mr` outlives every task started through the returned wrapper.
755   755  
756   @param ex The executor to execute the task on. 756   @param ex The executor to execute the task on.
757   @param mr The memory resource for frame allocation. 757   @param mr The memory resource for frame allocation.
758   758  
759   @return A wrapper that accepts a `task<T>` for immediate execution. 759   @return A wrapper that accepts a `task<T>` for immediate execution.
760   760  
761   @see task 761   @see task
762   @see Executor 762   @see Executor
763   @see run_async_wrapper 763   @see run_async_wrapper
764   */ 764   */
765   template<Executor Ex> 765   template<Executor Ex>
766   [[nodiscard]] auto 766   [[nodiscard]] auto
HITCBC 767   16 run_async(Ex ex, std::pmr::memory_resource* mr) 767   16 run_async(Ex ex, std::pmr::memory_resource* mr)
768   { 768   {
769   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 770   16 std::move(ex), 770   16 std::move(ex),
HITCBC 771   32 std::stop_token{}, 771   32 std::stop_token{},
772   detail::default_handler{}, 772   detail::default_handler{},
HITCBC 773   16 mr); 773   16 mr);
774   } 774   }
775   775  
776   /** 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.
777   777  
778   Construct the task as the direct argument of the two-call expression 778   Construct the task as the direct argument of the two-call expression
779   `run_async(ex)(task)`. 779   `run_async(ex)(task)`.
780   780  
781   @par Thread Safety 781   @par Thread Safety
782   The wrapper itself should only be used from one thread. The handlers 782   The wrapper itself should only be used from one thread. The handlers
783   may be invoked from any thread where the executor schedules work. 783   may be invoked from any thread where the executor schedules work.
784   784  
785   @pre `mr` outlives every task started through the returned wrapper. 785   @pre `mr` outlives every task started through the returned wrapper.
786   786  
787   @param ex The executor to execute the task on. 787   @param ex The executor to execute the task on.
788   @param mr The memory resource for frame allocation. 788   @param mr The memory resource for frame allocation.
789   @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).
790   790  
791   @return A wrapper that accepts a `task<T>` for immediate execution. 791   @return A wrapper that accepts a `task<T>` for immediate execution.
792   792  
793   @see task 793   @see task
794   @see Executor 794   @see Executor
795   @see run_async_wrapper 795   @see run_async_wrapper
796   */ 796   */
797   template<Executor Ex, class H1> 797   template<Executor Ex, class H1>
798   [[nodiscard]] auto 798   [[nodiscard]] auto
HITCBC 799   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)
800   { 800   {
801   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 802   1 std::move(ex), 802   1 std::move(ex),
HITCBC 803   1 std::stop_token{}, 803   1 std::stop_token{},
HITCBC 804   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 804   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 805   2 mr); 805   2 mr);
806   } 806   }
807   807  
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. 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.
809   809  
810   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
811   `run_async(ex)(task)`. 811   `run_async(ex)(task)`.
812   812  
813   @par Thread Safety 813   @par Thread Safety
814   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
815   may be invoked from any thread where the executor schedules work. 815   may be invoked from any thread where the executor schedules work.
816   816  
817   @pre `mr` outlives every task started through the returned wrapper. 817   @pre `mr` outlives every task started through the returned wrapper.
818   818  
819   @param ex The executor to execute the task on. 819   @param ex The executor to execute the task on.
820   @param mr The memory resource for frame allocation. 820   @param mr The memory resource for frame allocation.
821   @param h1 The handler to invoke with the result on success. 821   @param h1 The handler to invoke with the result on success.
822   @param h2 The handler to invoke with the exception on failure. 822   @param h2 The handler to invoke with the exception on failure.
823   823  
824   @return A wrapper that accepts a `task<T>` for immediate execution. 824   @return A wrapper that accepts a `task<T>` for immediate execution.
825   825  
826   @see task 826   @see task
827   @see Executor 827   @see Executor
828   @see run_async_wrapper 828   @see run_async_wrapper
829   */ 829   */
830   template<Executor Ex, class H1, class H2> 830   template<Executor Ex, class H1, class H2>
831   [[nodiscard]] auto 831   [[nodiscard]] auto
832   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)
833   { 833   {
834   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*>(
835   std::move(ex), 835   std::move(ex),
836   std::stop_token{}, 836   std::stop_token{},
837   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 837   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
838   mr); 838   mr);
839   } 839   }
840   840  
841   // Ex + stop_token + memory_resource* 841   // Ex + stop_token + memory_resource*
842   842  
843   /** 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.
844   844  
845   Construct the task as the direct argument of the two-call expression 845   Construct the task as the direct argument of the two-call expression
846   `run_async(ex)(task)`. 846   `run_async(ex)(task)`.
847   847  
848   @par Thread Safety 848   @par Thread Safety
849   The wrapper itself should only be used from one thread. 849   The wrapper itself should only be used from one thread.
850   850  
851   @pre `mr` outlives every task started through the returned wrapper. 851   @pre `mr` outlives every task started through the returned wrapper.
852   852  
853   @param ex The executor to execute the task on. 853   @param ex The executor to execute the task on.
854   @param st The stop token for cooperative cancellation. 854   @param st The stop token for cooperative cancellation.
855   @param mr The memory resource for frame allocation. 855   @param mr The memory resource for frame allocation.
856   856  
857   @return A wrapper that accepts a `task<T>` for immediate execution. 857   @return A wrapper that accepts a `task<T>` for immediate execution.
858   858  
859   @see task 859   @see task
860   @see Executor 860   @see Executor
861   @see run_async_wrapper 861   @see run_async_wrapper
862   */ 862   */
863   template<Executor Ex> 863   template<Executor Ex>
864   [[nodiscard]] auto 864   [[nodiscard]] auto
HITCBC 865   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)
866   { 866   {
867   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 868   1 std::move(ex), 868   1 std::move(ex),
HITCBC 869   1 std::move(st), 869   1 std::move(st),
870   detail::default_handler{}, 870   detail::default_handler{},
HITCBC 871   2 mr); 871   2 mr);
872   } 872   }
873   873  
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. 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.
875   875  
876   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
877   `run_async(ex)(task)`. 877   `run_async(ex)(task)`.
878   878  
879   @par Thread Safety 879   @par Thread Safety
880   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
881   may be invoked from any thread where the executor schedules work. 881   may be invoked from any thread where the executor schedules work.
882   882  
883   @pre `mr` outlives every task started through the returned wrapper. 883   @pre `mr` outlives every task started through the returned wrapper.
884   884  
885   @param ex The executor to execute the task on. 885   @param ex The executor to execute the task on.
886   @param st The stop token for cooperative cancellation. 886   @param st The stop token for cooperative cancellation.
887   @param mr The memory resource for frame allocation. 887   @param mr The memory resource for frame allocation.
888   @param h1 The handler to invoke with the result (and optionally exception). 888   @param h1 The handler to invoke with the result (and optionally exception).
889   889  
890   @return A wrapper that accepts a `task<T>` for immediate execution. 890   @return A wrapper that accepts a `task<T>` for immediate execution.
891   891  
892   @see task 892   @see task
893   @see Executor 893   @see Executor
894   @see run_async_wrapper 894   @see run_async_wrapper
895   */ 895   */
896   template<Executor Ex, class H1> 896   template<Executor Ex, class H1>
897   [[nodiscard]] auto 897   [[nodiscard]] auto
898   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)
899   { 899   {
900   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*>(
901   std::move(ex), 901   std::move(ex),
902   std::move(st), 902   std::move(st),
903   detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 903   detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
904   mr); 904   mr);
905   } 905   }
906   906  
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. 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.
908   908  
909   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
910   `run_async(ex)(task)`. 910   `run_async(ex)(task)`.
911   911  
912   @par Thread Safety 912   @par Thread Safety
913   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
914   may be invoked from any thread where the executor schedules work. 914   may be invoked from any thread where the executor schedules work.
915   915  
916   @pre `mr` outlives every task started through the returned wrapper. 916   @pre `mr` outlives every task started through the returned wrapper.
917   917  
918   @param ex The executor to execute the task on. 918   @param ex The executor to execute the task on.
919   @param st The stop token for cooperative cancellation. 919   @param st The stop token for cooperative cancellation.
920   @param mr The memory resource for frame allocation. 920   @param mr The memory resource for frame allocation.
921   @param h1 The handler to invoke with the result on success. 921   @param h1 The handler to invoke with the result on success.
922   @param h2 The handler to invoke with the exception on failure. 922   @param h2 The handler to invoke with the exception on failure.
923   923  
924   @return A wrapper that accepts a `task<T>` for immediate execution. 924   @return A wrapper that accepts a `task<T>` for immediate execution.
925   925  
926   @see task 926   @see task
927   @see Executor 927   @see Executor
928   @see run_async_wrapper 928   @see run_async_wrapper
929   */ 929   */
930   template<Executor Ex, class H1, class H2> 930   template<Executor Ex, class H1, class H2>
931   [[nodiscard]] auto 931   [[nodiscard]] auto
HITCBC 932   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)
933   { 933   {
934   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 935   1 std::move(ex), 935   1 std::move(ex),
HITCBC 936   1 std::move(st), 936   1 std::move(st),
937   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 937   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
HITCBC 938   2 mr); 938   2 mr);
939   } 939   }
940   940  
941   // Ex + standard Allocator (value type) 941   // Ex + standard Allocator (value type)
942   942  
943   /** 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.
944   944  
945   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
946   run_async_trampoline, ensuring it outlives all coroutine frames. 946   run_async_trampoline, ensuring it outlives all coroutine frames.
947   947  
948   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
949   `run_async(ex)(task)`. 949   `run_async(ex)(task)`.
950   950  
951   @par Thread Safety 951   @par Thread Safety
952   The wrapper itself should only be used from one thread. 952   The wrapper itself should only be used from one thread.
953   953  
954   @param ex The executor to execute the task on. 954   @param ex The executor to execute the task on.
955   @param alloc The allocator for frame allocation (copied and stored). 955   @param alloc The allocator for frame allocation (copied and stored).
956   956  
957   @return A wrapper that accepts a `task<T>` for immediate execution. 957   @return A wrapper that accepts a `task<T>` for immediate execution.
958   958  
959   @see task 959   @see task
960   @see Executor 960   @see Executor
961   @see run_async_wrapper 961   @see run_async_wrapper
962   */ 962   */
963   template<Executor Ex, detail::Allocator Alloc> 963   template<Executor Ex, detail::Allocator Alloc>
964   [[nodiscard]] auto 964   [[nodiscard]] auto
HITCBC 965   1 run_async(Ex ex, Alloc alloc) 965   1 run_async(Ex ex, Alloc alloc)
966   { 966   {
967   return run_async_wrapper<Ex, detail::default_handler, Alloc>( 967   return run_async_wrapper<Ex, detail::default_handler, Alloc>(
HITCBC 968   1 std::move(ex), 968   1 std::move(ex),
HITCBC 969   2 std::stop_token{}, 969   2 std::stop_token{},
970   detail::default_handler{}, 970   detail::default_handler{},
HITCBC 971   2 std::move(alloc)); 971   2 std::move(alloc));
972   } 972   }
973   973  
974   /** 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.
975   975  
976   Construct the task as the direct argument of the two-call expression 976   Construct the task as the direct argument of the two-call expression
977   `run_async(ex)(task)`. 977   `run_async(ex)(task)`.
978   978  
979   @par Thread Safety 979   @par Thread Safety
980   The wrapper itself should only be used from one thread. The handlers 980   The wrapper itself should only be used from one thread. The handlers
981   may be invoked from any thread where the executor schedules work. 981   may be invoked from any thread where the executor schedules work.
982   982  
983   @param ex The executor to execute the task on. 983   @param ex The executor to execute the task on.
984   @param alloc The allocator for frame allocation (copied and stored). 984   @param alloc The allocator for frame allocation (copied and stored).
985   @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).
986   986  
987   @return A wrapper that accepts a `task<T>` for immediate execution. 987   @return A wrapper that accepts a `task<T>` for immediate execution.
988   988  
989   @see task 989   @see task
990   @see Executor 990   @see Executor
991   @see run_async_wrapper 991   @see run_async_wrapper
992   */ 992   */
993   template<Executor Ex, detail::Allocator Alloc, class H1> 993   template<Executor Ex, detail::Allocator Alloc, class H1>
994   [[nodiscard]] auto 994   [[nodiscard]] auto
HITCBC 995   1 run_async(Ex ex, Alloc alloc, H1 h1) 995   1 run_async(Ex ex, Alloc alloc, H1 h1)
996   { 996   {
997   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 998   1 std::move(ex), 998   1 std::move(ex),
HITCBC 999   1 std::stop_token{}, 999   1 std::stop_token{},
HITCBC 1000   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 1000   1 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
HITCBC 1001   4 std::move(alloc)); 1001   4 std::move(alloc));
1002   } 1002   }
1003   1003  
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. 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.
1005   1005  
1006   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
1007   `run_async(ex)(task)`. 1007   `run_async(ex)(task)`.
1008   1008  
1009   @par Thread Safety 1009   @par Thread Safety
1010   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
1011   may be invoked from any thread where the executor schedules work. 1011   may be invoked from any thread where the executor schedules work.
1012   1012  
1013   @param ex The executor to execute the task on. 1013   @param ex The executor to execute the task on.
1014   @param alloc The allocator for frame allocation (copied and stored). 1014   @param alloc The allocator for frame allocation (copied and stored).
1015   @param h1 The handler to invoke with the result on success. 1015   @param h1 The handler to invoke with the result on success.
1016   @param h2 The handler to invoke with the exception on failure. 1016   @param h2 The handler to invoke with the exception on failure.
1017   1017  
1018   @return A wrapper that accepts a `task<T>` for immediate execution. 1018   @return A wrapper that accepts a `task<T>` for immediate execution.
1019   1019  
1020   @see task 1020   @see task
1021   @see Executor 1021   @see Executor
1022   @see run_async_wrapper 1022   @see run_async_wrapper
1023   */ 1023   */
1024   template<Executor Ex, detail::Allocator Alloc, class H1, class H2> 1024   template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
1025   [[nodiscard]] auto 1025   [[nodiscard]] auto
HITCBC 1026   1 run_async(Ex ex, Alloc alloc, H1 h1, H2 h2) 1026   1 run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
1027   { 1027   {
1028   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>( 1028   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
HITCBC 1029   1 std::move(ex), 1029   1 std::move(ex),
HITCBC 1030   1 std::stop_token{}, 1030   1 std::stop_token{},
HITCBC 1031   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 1032   4 std::move(alloc)); 1032   4 std::move(alloc));
1033   } 1033   }
1034   1034  
1035   // Ex + stop_token + standard Allocator 1035   // Ex + stop_token + standard Allocator
1036   1036  
1037   /** 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.
1038   1038  
1039   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
1040   `run_async(ex)(task)`. 1040   `run_async(ex)(task)`.
1041   1041  
1042   @par Thread Safety 1042   @par Thread Safety
1043   The wrapper itself should only be used from one thread. 1043   The wrapper itself should only be used from one thread.
1044   1044  
1045   @param ex The executor to execute the task on. 1045   @param ex The executor to execute the task on.
1046   @param st The stop token for cooperative cancellation. 1046   @param st The stop token for cooperative cancellation.
1047   @param alloc The allocator for frame allocation (copied and stored). 1047   @param alloc The allocator for frame allocation (copied and stored).
1048   1048  
1049   @return A wrapper that accepts a `task<T>` for immediate execution. 1049   @return A wrapper that accepts a `task<T>` for immediate execution.
1050   1050  
1051   @see task 1051   @see task
1052   @see Executor 1052   @see Executor
1053   @see run_async_wrapper 1053   @see run_async_wrapper
1054   */ 1054   */
1055   template<Executor Ex, detail::Allocator Alloc> 1055   template<Executor Ex, detail::Allocator Alloc>
1056   [[nodiscard]] auto 1056   [[nodiscard]] auto
1057   run_async(Ex ex, std::stop_token st, Alloc alloc) 1057   run_async(Ex ex, std::stop_token st, Alloc alloc)
1058   { 1058   {
1059   return run_async_wrapper<Ex, detail::default_handler, Alloc>( 1059   return run_async_wrapper<Ex, detail::default_handler, Alloc>(
1060   std::move(ex), 1060   std::move(ex),
1061   std::move(st), 1061   std::move(st),
1062   detail::default_handler{}, 1062   detail::default_handler{},
1063   std::move(alloc)); 1063   std::move(alloc));
1064   } 1064   }
1065   1065  
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. 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.
1067   1067  
1068   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
1069   `run_async(ex)(task)`. 1069   `run_async(ex)(task)`.
1070   1070  
1071   @par Thread Safety 1071   @par Thread Safety
1072   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
1073   may be invoked from any thread where the executor schedules work. 1073   may be invoked from any thread where the executor schedules work.
1074   1074  
1075   @param ex The executor to execute the task on. 1075   @param ex The executor to execute the task on.
1076   @param st The stop token for cooperative cancellation. 1076   @param st The stop token for cooperative cancellation.
1077   @param alloc The allocator for frame allocation (copied and stored). 1077   @param alloc The allocator for frame allocation (copied and stored).
1078   @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).
1079   1079  
1080   @return A wrapper that accepts a `task<T>` for immediate execution. 1080   @return A wrapper that accepts a `task<T>` for immediate execution.
1081   1081  
1082   @see task 1082   @see task
1083   @see Executor 1083   @see Executor
1084   @see run_async_wrapper 1084   @see run_async_wrapper
1085   */ 1085   */
1086   template<Executor Ex, detail::Allocator Alloc, class H1> 1086   template<Executor Ex, detail::Allocator Alloc, class H1>
1087   [[nodiscard]] auto 1087   [[nodiscard]] auto
1088   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)
1089   { 1089   {
1090   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>(
1091   std::move(ex), 1091   std::move(ex),
1092   std::move(st), 1092   std::move(st),
1093   detail::handler_pair<H1, detail::default_handler>{std::move(h1)}, 1093   detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
1094   std::move(alloc)); 1094   std::move(alloc));
1095   } 1095   }
1096   1096  
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. 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.
1098   1098  
1099   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
1100   `run_async(ex)(task)`. 1100   `run_async(ex)(task)`.
1101   1101  
1102   @par Thread Safety 1102   @par Thread Safety
1103   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
1104   may be invoked from any thread where the executor schedules work. 1104   may be invoked from any thread where the executor schedules work.
1105   1105  
1106   @param ex The executor to execute the task on. 1106   @param ex The executor to execute the task on.
1107   @param st The stop token for cooperative cancellation. 1107   @param st The stop token for cooperative cancellation.
1108   @param alloc The allocator for frame allocation (copied and stored). 1108   @param alloc The allocator for frame allocation (copied and stored).
1109   @param h1 The handler to invoke with the result on success. 1109   @param h1 The handler to invoke with the result on success.
1110   @param h2 The handler to invoke with the exception on failure. 1110   @param h2 The handler to invoke with the exception on failure.
1111   1111  
1112   @return A wrapper that accepts a `task<T>` for immediate execution. 1112   @return A wrapper that accepts a `task<T>` for immediate execution.
1113   1113  
1114   @see task 1114   @see task
1115   @see Executor 1115   @see Executor
1116   @see run_async_wrapper 1116   @see run_async_wrapper
1117   */ 1117   */
1118   template<Executor Ex, detail::Allocator Alloc, class H1, class H2> 1118   template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
1119   [[nodiscard]] auto 1119   [[nodiscard]] auto
1120   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)
1121   { 1121   {
1122   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>( 1122   return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
1123   std::move(ex), 1123   std::move(ex),
1124   std::move(st), 1124   std::move(st),
1125   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)}, 1125   detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
1126   std::move(alloc)); 1126   std::move(alloc));
1127   } 1127   }
1128   1128  
1129   } // namespace capy 1129   } // namespace capy
1130   } // namespace boost 1130   } // namespace boost
1131   1131  
1132   #endif 1132   #endif