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