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