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