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