100.00% Lines (161/161) 100.00% Functions (43/43)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
  3 + // Copyright (c) 2026 Michael Vandeberg
3   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // 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) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
8   // 9   //
9   10  
10   #ifndef BOOST_CAPY_WHEN_ALL_HPP 11   #ifndef BOOST_CAPY_WHEN_ALL_HPP
11   #define BOOST_CAPY_WHEN_ALL_HPP 12   #define BOOST_CAPY_WHEN_ALL_HPP
12   13  
13   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/detail/io_result_combinators.hpp> 15   #include <boost/capy/detail/io_result_combinators.hpp>
15   #include <boost/capy/continuation.hpp> 16   #include <boost/capy/continuation.hpp>
16   #include <boost/capy/concept/executor.hpp> 17   #include <boost/capy/concept/executor.hpp>
17   #include <boost/capy/concept/io_awaitable.hpp> 18   #include <boost/capy/concept/io_awaitable.hpp>
18   #include <coroutine> 19   #include <coroutine>
19   #include <boost/capy/ex/frame_alloc_mixin.hpp> 20   #include <boost/capy/ex/frame_alloc_mixin.hpp>
20   #include <boost/capy/ex/io_env.hpp> 21   #include <boost/capy/ex/io_env.hpp>
21   #include <boost/capy/ex/frame_allocator.hpp> 22   #include <boost/capy/ex/frame_allocator.hpp>
22   #include <boost/capy/task.hpp> 23   #include <boost/capy/task.hpp>
23   24  
24   #include <array> 25   #include <array>
25   #include <atomic> 26   #include <atomic>
26   #include <exception> 27   #include <exception>
27   #include <memory> 28   #include <memory>
28   #include <optional> 29   #include <optional>
29   #include <ranges> 30   #include <ranges>
30   #include <stdexcept> 31   #include <stdexcept>
31   #include <stop_token> 32   #include <stop_token>
32   #include <tuple> 33   #include <tuple>
33   #include <type_traits> 34   #include <type_traits>
34   #include <utility> 35   #include <utility>
35   #include <vector> 36   #include <vector>
36   37  
37   namespace boost { 38   namespace boost {
38   namespace capy { 39   namespace capy {
39   40  
40   namespace detail { 41   namespace detail {
41   42  
42   /** Holds the result of a single task within when_all. 43   /** Holds the result of a single task within when_all.
43   */ 44   */
44   template<typename T> 45   template<typename T>
45   struct result_holder 46   struct result_holder
46   { 47   {
47   std::optional<T> value_; 48   std::optional<T> value_;
48   49  
HITCBC 49   119 void set(T v) 50   119 void set(T v)
50   { 51   {
HITCBC 51   119 value_ = std::move(v); 52   119 value_ = std::move(v);
HITCBC 52   119 } 53   119 }
53   54  
HITCBC 54   105 T get() && 55   105 T get() &&
55   { 56   {
HITCBC 56   105 return std::move(*value_); 57   105 return std::move(*value_);
57   } 58   }
58   }; 59   };
59   60  
60   /** Core shared state for when_all operations. 61   /** Core shared state for when_all operations.
61   62  
62   Contains all members and methods common to both heterogeneous (variadic) 63   Contains all members and methods common to both heterogeneous (variadic)
63   and homogeneous (range) when_all implementations. State classes embed 64   and homogeneous (range) when_all implementations. State classes embed
64   this via composition to avoid CRTP destructor ordering issues. 65   this via composition to avoid CRTP destructor ordering issues.
65   66  
66   @par Thread Safety 67   @par Thread Safety
67   Atomic operations protect exception capture and completion count. 68   Atomic operations protect exception capture and completion count.
68   */ 69   */
69   struct when_all_core 70   struct when_all_core
70   { 71   {
71   std::atomic<std::size_t> remaining_count_; 72   std::atomic<std::size_t> remaining_count_;
72   73  
73   // Exception storage - first error wins, others discarded 74   // Exception storage - first error wins, others discarded
74   std::atomic<bool> has_exception_{false}; 75   std::atomic<bool> has_exception_{false};
75   std::exception_ptr first_exception_; 76   std::exception_ptr first_exception_;
76   77  
77   std::stop_source stop_source_; 78   std::stop_source stop_source_;
78   79  
79   // Bridges parent's stop token to our stop_source 80   // Bridges parent's stop token to our stop_source
80   struct stop_callback_fn 81   struct stop_callback_fn
81   { 82   {
82   std::stop_source* source_; 83   std::stop_source* source_;
HITCBC 83   3 void operator()() const { source_->request_stop(); } 84   3 void operator()() const { source_->request_stop(); }
84   }; 85   };
85   using stop_callback_t = std::stop_callback<stop_callback_fn>; 86   using stop_callback_t = std::stop_callback<stop_callback_fn>;
86   std::optional<stop_callback_t> parent_stop_callback_; 87   std::optional<stop_callback_t> parent_stop_callback_;
87   88  
88   continuation continuation_; 89   continuation continuation_;
89   io_env const* caller_env_ = nullptr; 90   io_env const* caller_env_ = nullptr;
90   91  
HITCBC 91   82 explicit when_all_core(std::size_t count) noexcept 92   82 explicit when_all_core(std::size_t count) noexcept
HITCBC 92   82 : remaining_count_(count) 93   82 : remaining_count_(count)
93   { 94   {
HITCBC 94   82 } 95   82 }
95   96  
96   /** Capture an exception (first one wins). */ 97   /** Capture an exception (first one wins). */
HITCBC 97   21 void capture_exception(std::exception_ptr ep) 98   21 void capture_exception(std::exception_ptr ep)
98   { 99   {
HITCBC 99   21 bool expected = false; 100   21 bool expected = false;
HITCBC 100   21 if(has_exception_.compare_exchange_strong( 101   21 if(has_exception_.compare_exchange_strong(
101   expected, true, std::memory_order_relaxed)) 102   expected, true, std::memory_order_relaxed))
HITCBC 102   19 first_exception_ = ep; 103   19 first_exception_ = ep;
HITCBC 103   21 } 104   21 }
104   }; 105   };
105   106  
106   /** Shared state for heterogeneous when_all (variadic overload). 107   /** Shared state for heterogeneous when_all (variadic overload).
107   108  
108   @tparam Ts The result types of the tasks. 109   @tparam Ts The result types of the tasks.
109   */ 110   */
110   template<typename... Ts> 111   template<typename... Ts>
111   struct when_all_state 112   struct when_all_state
112   { 113   {
113   static constexpr std::size_t task_count = sizeof...(Ts); 114   static constexpr std::size_t task_count = sizeof...(Ts);
114   115  
115   when_all_core core_; 116   when_all_core core_;
116   std::tuple<result_holder<Ts>...> results_; 117   std::tuple<result_holder<Ts>...> results_;
117   std::array<continuation, task_count> runner_handles_{}; 118   std::array<continuation, task_count> runner_handles_{};
118   119  
119   std::atomic<bool> has_error_{false}; 120   std::atomic<bool> has_error_{false};
120   std::error_code first_error_; 121   std::error_code first_error_;
121   122  
HITCBC 122   66 when_all_state() 123   66 when_all_state()
HITCBC 123   66 : core_(task_count) 124   66 : core_(task_count)
124   { 125   {
HITCBC 125   66 } 126   66 }
126   127  
127   /** Record the first error (subsequent errors are discarded). */ 128   /** Record the first error (subsequent errors are discarded). */
HITCBC 128   46 void record_error(std::error_code ec) 129   46 void record_error(std::error_code ec)
129   { 130   {
HITCBC 130   46 bool expected = false; 131   46 bool expected = false;
HITCBC 131   46 if(has_error_.compare_exchange_strong( 132   46 if(has_error_.compare_exchange_strong(
132   expected, true, std::memory_order_relaxed)) 133   expected, true, std::memory_order_relaxed))
HITCBC 133   32 first_error_ = ec; 134   32 first_error_ = ec;
HITCBC 134   46 } 135   46 }
135   }; 136   };
136   137  
137   /** Shared state for homogeneous when_all (range overload). 138   /** Shared state for homogeneous when_all (range overload).
138   139  
139   Stores extracted io_result payloads in a vector indexed by task 140   Stores extracted io_result payloads in a vector indexed by task
140   position. Tracks the first error_code for error propagation. 141   position. Tracks the first error_code for error propagation.
141   142  
142   @tparam T The payload type extracted from io_result. 143   @tparam T The payload type extracted from io_result.
143   */ 144   */
144   template<typename T> 145   template<typename T>
145   struct when_all_homogeneous_state 146   struct when_all_homogeneous_state
146   { 147   {
147   when_all_core core_; 148   when_all_core core_;
148   std::vector<std::optional<T>> results_; 149   std::vector<std::optional<T>> results_;
149   std::unique_ptr<continuation[]> runner_handles_; 150   std::unique_ptr<continuation[]> runner_handles_;
150   151  
151   std::atomic<bool> has_error_{false}; 152   std::atomic<bool> has_error_{false};
152   std::error_code first_error_; 153   std::error_code first_error_;
153   154  
HITCBC 154   13 explicit when_all_homogeneous_state(std::size_t count) 155   13 explicit when_all_homogeneous_state(std::size_t count)
HITCBC 155   13 : core_(count) 156   13 : core_(count)
HITCBC 156   26 , results_(count) 157   26 , results_(count)
HITCBC 157   13 , runner_handles_(std::make_unique<continuation[]>(count)) 158   13 , runner_handles_(std::make_unique<continuation[]>(count))
158   { 159   {
HITCBC 159   13 } 160   13 }
160   161  
HITCBC 161   21 void set_result(std::size_t index, T value) 162   21 void set_result(std::size_t index, T value)
162   { 163   {
HITCBC 163   21 results_[index].emplace(std::move(value)); 164   21 results_[index].emplace(std::move(value));
HITCBC 164   21 } 165   21 }
165   166  
166   /** Record the first error (subsequent errors are discarded). */ 167   /** Record the first error (subsequent errors are discarded). */
HITCBC 167   7 void record_error(std::error_code ec) 168   7 void record_error(std::error_code ec)
168   { 169   {
HITCBC 169   7 bool expected = false; 170   7 bool expected = false;
HITCBC 170   7 if(has_error_.compare_exchange_strong( 171   7 if(has_error_.compare_exchange_strong(
171   expected, true, std::memory_order_relaxed)) 172   expected, true, std::memory_order_relaxed))
HITCBC 172   5 first_error_ = ec; 173   5 first_error_ = ec;
HITCBC 173   7 } 174   7 }
174   }; 175   };
175   176  
176   /** Specialization for void io_result children (no payload storage). */ 177   /** Specialization for void io_result children (no payload storage). */
177   template<> 178   template<>
178   struct when_all_homogeneous_state<std::tuple<>> 179   struct when_all_homogeneous_state<std::tuple<>>
179   { 180   {
180   when_all_core core_; 181   when_all_core core_;
181   std::unique_ptr<continuation[]> runner_handles_; 182   std::unique_ptr<continuation[]> runner_handles_;
182   183  
183   std::atomic<bool> has_error_{false}; 184   std::atomic<bool> has_error_{false};
184   std::error_code first_error_; 185   std::error_code first_error_;
185   186  
HITCBC 186   3 explicit when_all_homogeneous_state(std::size_t count) 187   3 explicit when_all_homogeneous_state(std::size_t count)
HITCBC 187   3 : core_(count) 188   3 : core_(count)
HITCBC 188   3 , runner_handles_(std::make_unique<continuation[]>(count)) 189   3 , runner_handles_(std::make_unique<continuation[]>(count))
189   { 190   {
HITCBC 190   3 } 191   3 }
191   192  
192   /** Record the first error (subsequent errors are discarded). */ 193   /** Record the first error (subsequent errors are discarded). */
HITCBC 193   1 void record_error(std::error_code ec) 194   1 void record_error(std::error_code ec)
194   { 195   {
HITCBC 195   1 bool expected = false; 196   1 bool expected = false;
HITCBC 196   1 if(has_error_.compare_exchange_strong( 197   1 if(has_error_.compare_exchange_strong(
197   expected, true, std::memory_order_relaxed)) 198   expected, true, std::memory_order_relaxed))
HITCBC 198   1 first_error_ = ec; 199   1 first_error_ = ec;
HITCBC 199   1 } 200   1 }
200   }; 201   };
201   202  
202   /** Wrapper coroutine that intercepts task completion for when_all. 203   /** Wrapper coroutine that intercepts task completion for when_all.
203   204  
204   Parameterized on StateType to work with both heterogeneous (variadic) 205   Parameterized on StateType to work with both heterogeneous (variadic)
205   and homogeneous (range) state types. All state types expose their 206   and homogeneous (range) state types. All state types expose their
206   shared members through a `core_` member of type when_all_core. 207   shared members through a `core_` member of type when_all_core.
207   208  
208   @tparam StateType The state type (when_all_state or when_all_homogeneous_state). 209   @tparam StateType The state type (when_all_state or when_all_homogeneous_state).
209   */ 210   */
210   template<typename StateType> 211   template<typename StateType>
211   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_all_runner 212   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_all_runner
212   { 213   {
213   struct promise_type 214   struct promise_type
214   : frame_alloc_mixin 215   : frame_alloc_mixin
215   { 216   {
216   StateType* state_ = nullptr; 217   StateType* state_ = nullptr;
217   std::size_t index_ = 0; 218   std::size_t index_ = 0;
218   io_env env_; 219   io_env env_;
219   220  
HITCBC 220   174 when_all_runner get_return_object() noexcept 221   174 when_all_runner get_return_object() noexcept
221   { 222   {
222   return when_all_runner( 223   return when_all_runner(
HITCBC 223   174 std::coroutine_handle<promise_type>::from_promise(*this)); 224   174 std::coroutine_handle<promise_type>::from_promise(*this));
224   } 225   }
225   226  
HITCBC 226   174 std::suspend_always initial_suspend() noexcept 227   174 std::suspend_always initial_suspend() noexcept
227   { 228   {
HITCBC 228   174 return {}; 229   174 return {};
229   } 230   }
230   231  
HITCBC 231   174 auto final_suspend() noexcept 232   174 auto final_suspend() noexcept
232   { 233   {
233   struct awaiter 234   struct awaiter
234   { 235   {
235   promise_type* p_; 236   promise_type* p_;
HITCBC 236   174 bool await_ready() const noexcept { return false; } 237   174 bool await_ready() const noexcept { return false; }
HITCBC 237   174 auto await_suspend(std::coroutine_handle<> h) noexcept 238   174 auto await_suspend(std::coroutine_handle<> h) noexcept
238   { 239   {
HITCBC 239   174 auto& core = p_->state_->core_; 240   174 auto& core = p_->state_->core_;
HITCBC 240   174 auto* counter = &core.remaining_count_; 241   174 auto* counter = &core.remaining_count_;
HITCBC 241   174 auto* caller_env = core.caller_env_; 242   174 auto* caller_env = core.caller_env_;
HITCBC 242   174 auto& cont = core.continuation_; 243   174 auto& cont = core.continuation_;
243   244  
HITCBC 244   174 h.destroy(); 245   174 h.destroy();
245   246  
HITCBC 246   174 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel); 247   174 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
HITCBC 247   174 if(remaining == 1) 248   174 if(remaining == 1)
HITCBC 248   82 return detail::symmetric_transfer(caller_env->executor.dispatch(cont)); 249   82 return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
HITCBC 249   92 return detail::symmetric_transfer(std::noop_coroutine()); 250   92 return detail::symmetric_transfer(std::noop_coroutine());
250   } 251   }
251   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 252   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
252   }; 253   };
HITCBC 253   174 return awaiter{this}; 254   174 return awaiter{this};
254   } 255   }
255   256  
HITCBC 256   153 void return_void() noexcept {} 257   153 void return_void() noexcept {}
257   258  
HITCBC 258   21 void unhandled_exception() noexcept 259   21 void unhandled_exception() noexcept
259   { 260   {
HITCBC 260   21 state_->core_.capture_exception(std::current_exception()); 261   21 state_->core_.capture_exception(std::current_exception());
HITCBC 261   21 state_->core_.stop_source_.request_stop(); 262   21 state_->core_.stop_source_.request_stop();
HITCBC 262   21 } 263   21 }
263   264  
264   template<class Awaitable> 265   template<class Awaitable>
265   struct transform_awaiter 266   struct transform_awaiter
266   { 267   {
267   std::decay_t<Awaitable> a_; 268   std::decay_t<Awaitable> a_;
268   promise_type* p_; 269   promise_type* p_;
269   270  
HITCBC 270   174 bool await_ready() { return a_.await_ready(); } 271   174 bool await_ready() { return a_.await_ready(); }
HITCBC 271   174 decltype(auto) await_resume() { return a_.await_resume(); } 272   174 decltype(auto) await_resume() { return a_.await_resume(); }
272   273  
273   template<class Promise> 274   template<class Promise>
HITCBC 274   174 auto await_suspend(std::coroutine_handle<Promise> h) 275   174 auto await_suspend(std::coroutine_handle<Promise> h)
275   { 276   {
276   using R = decltype(a_.await_suspend(h, &p_->env_)); 277   using R = decltype(a_.await_suspend(h, &p_->env_));
277   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 278   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 278   174 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_)); 279   174 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
279   else 280   else
280   return a_.await_suspend(h, &p_->env_); 281   return a_.await_suspend(h, &p_->env_);
281   } 282   }
282   }; 283   };
283   284  
284   template<class Awaitable> 285   template<class Awaitable>
HITCBC 285   174 auto await_transform(Awaitable&& a) 286   174 auto await_transform(Awaitable&& a)
286   { 287   {
287   using A = std::decay_t<Awaitable>; 288   using A = std::decay_t<Awaitable>;
288   if constexpr (IoAwaitable<A>) 289   if constexpr (IoAwaitable<A>)
289   { 290   {
290   return transform_awaiter<Awaitable>{ 291   return transform_awaiter<Awaitable>{
HITCBC 291   348 std::forward<Awaitable>(a), this}; 292   348 std::forward<Awaitable>(a), this};
292   } 293   }
293   else 294   else
294   { 295   {
295   static_assert(sizeof(A) == 0, "requires IoAwaitable"); 296   static_assert(sizeof(A) == 0, "requires IoAwaitable");
296   } 297   }
HITCBC 297   174 } 298   174 }
298   }; 299   };
299   300  
300   std::coroutine_handle<promise_type> h_; 301   std::coroutine_handle<promise_type> h_;
301   302  
HITCBC 302   174 explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept 303   174 explicit when_all_runner(std::coroutine_handle<promise_type> h) noexcept
HITCBC 303   174 : h_(h) 304   174 : h_(h)
304   { 305   {
HITCBC 305   174 } 306   174 }
306   307  
307   // Enable move for all clang versions - some versions need it 308   // Enable move for all clang versions - some versions need it
308   when_all_runner(when_all_runner&& other) noexcept 309   when_all_runner(when_all_runner&& other) noexcept
309   : h_(std::exchange(other.h_, nullptr)) 310   : h_(std::exchange(other.h_, nullptr))
310   { 311   {
311   } 312   }
312   313  
313   when_all_runner(when_all_runner const&) = delete; 314   when_all_runner(when_all_runner const&) = delete;
314   when_all_runner& operator=(when_all_runner const&) = delete; 315   when_all_runner& operator=(when_all_runner const&) = delete;
315   when_all_runner& operator=(when_all_runner&&) = delete; 316   when_all_runner& operator=(when_all_runner&&) = delete;
316   317  
HITCBC 317   174 auto release() noexcept 318   174 auto release() noexcept
318   { 319   {
HITCBC 319   174 return std::exchange(h_, nullptr); 320   174 return std::exchange(h_, nullptr);
320   } 321   }
321   }; 322   };
322   323  
323   /** Create an io_result-aware runner for a single awaitable (range path). 324   /** Create an io_result-aware runner for a single awaitable (range path).
324   325  
325   Checks the error code, records errors and requests stop on failure, 326   Checks the error code, records errors and requests stop on failure,
326   or extracts the payload on success. 327   or extracts the payload on success.
327   */ 328   */
328   template<IoAwaitable Awaitable, typename StateType> 329   template<IoAwaitable Awaitable, typename StateType>
329   when_all_runner<StateType> 330   when_all_runner<StateType>
HITCBC 330   37 make_when_all_homogeneous_runner(Awaitable inner, StateType* state, std::size_t index) 331   37 make_when_all_homogeneous_runner(Awaitable inner, StateType* state, std::size_t index)
331   { 332   {
332   auto result = co_await std::move(inner); 333   auto result = co_await std::move(inner);
333   334  
334   if(result.ec) 335   if(result.ec)
335   { 336   {
336   state->record_error(result.ec); 337   state->record_error(result.ec);
337   state->core_.stop_source_.request_stop(); 338   state->core_.stop_source_.request_stop();
338   } 339   }
339   else 340   else
340   { 341   {
341   using PayloadT = io_result_payload_t< 342   using PayloadT = io_result_payload_t<
342   awaitable_result_t<Awaitable>>; 343   awaitable_result_t<Awaitable>>;
343   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>) 344   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
344   { 345   {
345   state->set_result(index, 346   state->set_result(index,
346   extract_io_payload(std::move(result))); 347   extract_io_payload(std::move(result)));
347   } 348   }
348   } 349   }
HITCBC 349   74 } 350   74 }
350   351  
351   /** Create a runner for io_result children that requests stop on ec. */ 352   /** Create a runner for io_result children that requests stop on ec. */
352   template<std::size_t Index, IoAwaitable Awaitable, typename... Ts> 353   template<std::size_t Index, IoAwaitable Awaitable, typename... Ts>
353   when_all_runner<when_all_state<Ts...>> 354   when_all_runner<when_all_state<Ts...>>
HITCBC 354   137 make_when_all_io_runner(Awaitable inner, when_all_state<Ts...>* state) 355   137 make_when_all_io_runner(Awaitable inner, when_all_state<Ts...>* state)
355   { 356   {
356   auto result = co_await std::move(inner); 357   auto result = co_await std::move(inner);
357   auto ec = result.ec; 358   auto ec = result.ec;
358   std::get<Index>(state->results_).set(std::move(result)); 359   std::get<Index>(state->results_).set(std::move(result));
359   360  
360   if(ec) 361   if(ec)
361   { 362   {
362   state->record_error(ec); 363   state->record_error(ec);
363   state->core_.stop_source_.request_stop(); 364   state->core_.stop_source_.request_stop();
364   } 365   }
HITCBC 365   274 } 366   274 }
366   367  
367   /** Launcher that uses io_result-aware runners. */ 368   /** Launcher that uses io_result-aware runners. */
368   template<IoAwaitable... Awaitables> 369   template<IoAwaitable... Awaitables>
369   class when_all_io_launcher 370   class when_all_io_launcher
370   { 371   {
371   using state_type = when_all_state<awaitable_result_t<Awaitables>...>; 372   using state_type = when_all_state<awaitable_result_t<Awaitables>...>;
372   373  
373   std::tuple<Awaitables...>* awaitables_; 374   std::tuple<Awaitables...>* awaitables_;
374   state_type* state_; 375   state_type* state_;
375   376  
376   public: 377   public:
HITCBC 377   66 when_all_io_launcher( 378   66 when_all_io_launcher(
378   std::tuple<Awaitables...>* awaitables, 379   std::tuple<Awaitables...>* awaitables,
379   state_type* state) 380   state_type* state)
HITCBC 380   66 : awaitables_(awaitables) 381   66 : awaitables_(awaitables)
HITCBC 381   66 , state_(state) 382   66 , state_(state)
382   { 383   {
HITCBC 383   66 } 384   66 }
384   385  
HITCBC 385   66 bool await_ready() const noexcept 386   66 bool await_ready() const noexcept
386   { 387   {
HITCBC 387   66 return sizeof...(Awaitables) == 0; 388   66 return sizeof...(Awaitables) == 0;
388   } 389   }
389   390  
HITCBC 390   66 std::coroutine_handle<> await_suspend( 391   66 std::coroutine_handle<> await_suspend(
391   std::coroutine_handle<> continuation, io_env const* caller_env) 392   std::coroutine_handle<> continuation, io_env const* caller_env)
392   { 393   {
HITCBC 393   66 state_->core_.continuation_.h = continuation; 394   66 state_->core_.continuation_.h = continuation;
HITCBC 394   66 state_->core_.caller_env_ = caller_env; 395   66 state_->core_.caller_env_ = caller_env;
395   396  
HITCBC 396   66 if(caller_env->stop_token.stop_possible()) 397   66 if(caller_env->stop_token.stop_possible())
397   { 398   {
HITCBC 398   4 state_->core_.parent_stop_callback_.emplace( 399   4 state_->core_.parent_stop_callback_.emplace(
HITCBC 399   2 caller_env->stop_token, 400   2 caller_env->stop_token,
HITCBC 400   2 when_all_core::stop_callback_fn{&state_->core_.stop_source_}); 401   2 when_all_core::stop_callback_fn{&state_->core_.stop_source_});
401   402  
HITCBC 402   2 if(caller_env->stop_token.stop_requested()) 403   2 if(caller_env->stop_token.stop_requested())
HITCBC 403   1 state_->core_.stop_source_.request_stop(); 404   1 state_->core_.stop_source_.request_stop();
404   } 405   }
405   406  
HITCBC 406   66 auto token = state_->core_.stop_source_.get_token(); 407   66 auto token = state_->core_.stop_source_.get_token();
HITCBC 407   66 launch_all(std::index_sequence_for<Awaitables...>{}, 408   66 launch_all(std::index_sequence_for<Awaitables...>{},
408   caller_env->executor, token); 409   caller_env->executor, token);
409   410  
HITCBC 410   132 return std::noop_coroutine(); 411   132 return std::noop_coroutine();
HITCBC 411   66 } 412   66 }
412   413  
HITCBC 413   66 void await_resume() const noexcept {} 414   66 void await_resume() const noexcept {}
414   415  
415   private: 416   private:
416   template<std::size_t... Is> 417   template<std::size_t... Is>
HITCBC 417   66 void launch_all(std::index_sequence<Is...>, 418   66 void launch_all(std::index_sequence<Is...>,
418   executor_ref ex, std::stop_token token) 419   executor_ref ex, std::stop_token token)
419   { 420   {
HITCBC 420   66 (..., launch_one<Is>(ex, token)); 421   66 (..., launch_one<Is>(ex, token));
HITCBC 421   66 } 422   66 }
422   423  
423   template<std::size_t I> 424   template<std::size_t I>
HITCBC 424   137 void launch_one(executor_ref caller_ex, std::stop_token token) 425   137 void launch_one(executor_ref caller_ex, std::stop_token token)
425   { 426   {
HITCBC 426   137 auto runner = make_when_all_io_runner<I>( 427   137 auto runner = make_when_all_io_runner<I>(
HITCBC 427   137 std::move(std::get<I>(*awaitables_)), state_); 428   137 std::move(std::get<I>(*awaitables_)), state_);
428   429  
HITCBC 429   137 auto h = runner.release(); 430   137 auto h = runner.release();
HITCBC 430   137 h.promise().state_ = state_; 431   137 h.promise().state_ = state_;
HITCBC 431   137 h.promise().env_ = io_env{caller_ex, token, 432   137 h.promise().env_ = io_env{caller_ex, token,
HITCBC 432   137 state_->core_.caller_env_->frame_allocator}; 433   137 state_->core_.caller_env_->frame_allocator};
433   434  
HITCBC 434   137 state_->runner_handles_[I].h = std::coroutine_handle<>{h}; 435   137 state_->runner_handles_[I].h = std::coroutine_handle<>{h};
HITCBC 435   137 state_->core_.caller_env_->executor.post(state_->runner_handles_[I]); 436   137 state_->core_.caller_env_->executor.post(state_->runner_handles_[I]);
HITCBC 436   274 } 437   274 }
437   }; 438   };
438   439  
439   /** Helper to extract a single result from state. 440   /** Helper to extract a single result from state.
440   This is a separate function to work around a GCC-11 ICE that occurs 441   This is a separate function to work around a GCC-11 ICE that occurs
441   when using nested immediately-invoked lambdas with pack expansion. 442   when using nested immediately-invoked lambdas with pack expansion.
442   */ 443   */
443   template<std::size_t I, typename... Ts> 444   template<std::size_t I, typename... Ts>
HITCBC 444   105 auto extract_single_result(when_all_state<Ts...>& state) 445   105 auto extract_single_result(when_all_state<Ts...>& state)
445   { 446   {
HITCBC 446   105 return std::move(std::get<I>(state.results_)).get(); 447   105 return std::move(std::get<I>(state.results_)).get();
447   } 448   }
448   449  
449   /** Extract all results from state as a tuple. 450   /** Extract all results from state as a tuple.
450   */ 451   */
451   template<typename... Ts> 452   template<typename... Ts>
HITCBC 452   50 auto extract_results(when_all_state<Ts...>& state) 453   50 auto extract_results(when_all_state<Ts...>& state)
453   { 454   {
HITCBC 454   82 return [&]<std::size_t... Is>(std::index_sequence<Is...>) { 455   82 return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
HITCBC 455   50 return std::tuple(extract_single_result<Is>(state)...); 456   50 return std::tuple(extract_single_result<Is>(state)...);
HITCBC 456   100 }(std::index_sequence_for<Ts...>{}); 457   100 }(std::index_sequence_for<Ts...>{});
457   } 458   }
458   459  
459 - /** Launches all homogeneous runners concurrently. 460 + /** Starts all homogeneous runners concurrently.
460   461  
461   Two-phase approach: create all runners first, then post all. 462   Two-phase approach: create all runners first, then post all.
462   This avoids lifetime issues if a task completes synchronously. 463   This avoids lifetime issues if a task completes synchronously.
463   */ 464   */
464   template<typename Range> 465   template<typename Range>
465   class when_all_homogeneous_launcher 466   class when_all_homogeneous_launcher
466   { 467   {
467   using Awaitable = std::ranges::range_value_t<Range>; 468   using Awaitable = std::ranges::range_value_t<Range>;
468   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>; 469   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
469   470  
470   Range* range_; 471   Range* range_;
471   when_all_homogeneous_state<PayloadT>* state_; 472   when_all_homogeneous_state<PayloadT>* state_;
472   473  
473   public: 474   public:
HITCBC 474   16 when_all_homogeneous_launcher( 475   16 when_all_homogeneous_launcher(
475   Range* range, 476   Range* range,
476   when_all_homogeneous_state<PayloadT>* state) 477   when_all_homogeneous_state<PayloadT>* state)
HITCBC 477   16 : range_(range) 478   16 : range_(range)
HITCBC 478   16 , state_(state) 479   16 , state_(state)
479   { 480   {
HITCBC 480   16 } 481   16 }
481   482  
HITCBC 482   16 bool await_ready() const noexcept 483   16 bool await_ready() const noexcept
483   { 484   {
HITCBC 484   16 return std::ranges::empty(*range_); 485   16 return std::ranges::empty(*range_);
485   } 486   }
486   487  
HITCBC 487   16 std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env) 488   16 std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation, io_env const* caller_env)
488   { 489   {
HITCBC 489   16 state_->core_.continuation_.h = continuation; 490   16 state_->core_.continuation_.h = continuation;
HITCBC 490   16 state_->core_.caller_env_ = caller_env; 491   16 state_->core_.caller_env_ = caller_env;
491   492  
HITCBC 492   16 if(caller_env->stop_token.stop_possible()) 493   16 if(caller_env->stop_token.stop_possible())
493   { 494   {
HITCBC 494   4 state_->core_.parent_stop_callback_.emplace( 495   4 state_->core_.parent_stop_callback_.emplace(
HITCBC 495   2 caller_env->stop_token, 496   2 caller_env->stop_token,
HITCBC 496   2 when_all_core::stop_callback_fn{&state_->core_.stop_source_}); 497   2 when_all_core::stop_callback_fn{&state_->core_.stop_source_});
497   498  
HITCBC 498   2 if(caller_env->stop_token.stop_requested()) 499   2 if(caller_env->stop_token.stop_requested())
HITCBC 499   1 state_->core_.stop_source_.request_stop(); 500   1 state_->core_.stop_source_.request_stop();
500   } 501   }
501   502  
HITCBC 502   16 auto token = state_->core_.stop_source_.get_token(); 503   16 auto token = state_->core_.stop_source_.get_token();
503   504  
504   // Phase 1: Create all runners without dispatching. 505   // Phase 1: Create all runners without dispatching.
HITCBC 505   16 std::size_t index = 0; 506   16 std::size_t index = 0;
HITCBC 506   53 for(auto&& a : *range_) 507   53 for(auto&& a : *range_)
507   { 508   {
HITCBC 508   37 auto runner = make_when_all_homogeneous_runner( 509   37 auto runner = make_when_all_homogeneous_runner(
HITCBC 509   37 std::move(a), state_, index); 510   37 std::move(a), state_, index);
510   511  
HITCBC 511   37 auto h = runner.release(); 512   37 auto h = runner.release();
HITCBC 512   37 h.promise().state_ = state_; 513   37 h.promise().state_ = state_;
HITCBC 513   37 h.promise().index_ = index; 514   37 h.promise().index_ = index;
HITCBC 514   37 h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator}; 515   37 h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator};
515   516  
HITCBC 516   37 state_->runner_handles_[index].h = std::coroutine_handle<>{h}; 517   37 state_->runner_handles_[index].h = std::coroutine_handle<>{h};
HITCBC 517   37 ++index; 518   37 ++index;
518   } 519   }
519   520  
520   // Phase 2: Post all runners. Any may complete synchronously. 521   // Phase 2: Post all runners. Any may complete synchronously.
521   // After last post, state_ and this may be destroyed. 522   // After last post, state_ and this may be destroyed.
HITCBC 522   16 auto* handles = state_->runner_handles_.get(); 523   16 auto* handles = state_->runner_handles_.get();
HITCBC 523   16 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed); 524   16 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
HITCBC 524   53 for(std::size_t i = 0; i < count; ++i) 525   53 for(std::size_t i = 0; i < count; ++i)
HITCBC 525   37 caller_env->executor.post(handles[i]); 526   37 caller_env->executor.post(handles[i]);
526   527  
HITCBC 527   32 return std::noop_coroutine(); 528   32 return std::noop_coroutine();
HITCBC 528   53 } 529   53 }
529   530  
HITCBC 530   16 void await_resume() const noexcept 531   16 void await_resume() const noexcept
531   { 532   {
HITCBC 532   16 } 533   16 }
533   }; 534   };
534   535  
535   } // namespace detail 536   } // namespace detail
536   537  
537   /** Execute a range of io_result-returning awaitables concurrently. 538   /** Execute a range of io_result-returning awaitables concurrently.
538   539  
539 - Launches all awaitables simultaneously and waits for all to complete. 540 + Starts all awaitables simultaneously and waits for all to complete.
540   On success, extracted payloads are collected in a vector preserving 541   On success, extracted payloads are collected in a vector preserving
541 - input order. The first error_code cancels siblings and is propagated 542 + input order. The first error_code makes a stop request that every
542 - in the outer io_result. Exceptions always beat error codes. 543 + sibling observes, and is propagated in the outer io_result.
  544 + Exceptions always beat error codes.
543   545  
544 - @li All child awaitables run concurrently on the caller's executor 546 + @li All child awaitables run concurrently on the caller's executor.
545 - @li Payloads are returned as a vector in input order 547 + @li Payloads are returned as a vector in input order.
546 - @li First error_code wins and cancels siblings 548 + @li First error_code wins and makes a stop request that siblings observe.
547 - @li Exception always beats error_code 549 + @li Exception always beats error_code.
548 - @li Completes only after all children have finished 550 + @li Completes only after all children have finished.
  551 +
  552 + @par Await-effects
  553 +
  554 + Takes ownership of the range, creates one wrapper coroutine per
  555 + element, then posts every wrapper to the caller's executor. All
  556 + children therefore run concurrently, each awaited with the caller's
  557 + executor and frame allocator and with a stop token owned by this
  558 + operation.
  559 +
  560 + Awaiting an empty range throws `std::invalid_argument` before any
  561 + child is started.
  562 +
  563 + A stop request is made on the operation's own stop token when:
  564 +
  565 + @li a child await-returns a non-zero `ec`, or
  566 + @li a child exits via an exception, or
  567 + @li the caller's stop token is triggered.
  568 +
  569 + Every sibling observes that request through the stop token it was
  570 + awaited with. The request does not end the operation: the await
  571 + completes only after every child has finished.
  572 +
  573 + @par Await-returns
  574 + An object of type `io_result<std::vector<PayloadT>>` destructuring as
  575 + `[ec, values]`, where `PayloadT` is the payload of one child's
  576 + `io_result`.
  577 +
  578 + `ec` is the first non-zero `ec` await-returned by a child, in
  579 + completion order rather than input order. The `ec` of every other
  580 + child is discarded.
  581 +
  582 + On success, `values` holds one payload per element of the input
  583 + range, in input order. If `ec` is set, `values` is empty: the
  584 + payloads of the children that did succeed are discarded.
  585 +
  586 + If any child exits via an exception, the first such exception is
  587 + rethrown instead of await-returning, even when a child also reported
  588 + an `ec`.
  589 +
  590 + @par Await-postcondition
  591 + Every child has finished. `ec` is success only if every child
  592 + await-returned success. If `ec` is success, `values` holds one
  593 + payload per input awaitable; otherwise `values` is empty.
  594 +
  595 + @par Remarks
  596 + Supports _IoAwaitable cancellation_.
549   597  
550   @par Thread Safety 598   @par Thread Safety
551   The returned task must be awaited from a single execution context. 599   The returned task must be awaited from a single execution context.
552   Child awaitables execute concurrently but complete through the caller's 600   Child awaitables execute concurrently but complete through the caller's
553   executor. 601   executor.
554   602  
555   @param awaitables Range of io_result-returning awaitables to execute 603   @param awaitables Range of io_result-returning awaitables to execute
556   concurrently (must not be empty). 604   concurrently (must not be empty).
557   605  
558   @return A task yielding io_result<vector<PayloadT>> where PayloadT 606   @return A task yielding io_result<vector<PayloadT>> where PayloadT
559   is the payload extracted from each child's io_result. 607   is the payload extracted from each child's io_result.
560   608  
561   @throws std::invalid_argument if range is empty (thrown before 609   @throws std::invalid_argument if range is empty (thrown before
562   coroutine suspends). 610   coroutine suspends).
563 - @throws Rethrows the first child exception after all children 611 +
564 - complete (exception beats error_code). 612 + @par Exception Safety
  613 + If a child throws, the first child exception is rethrown after
  614 + all children complete (exception beats error_code).
565   615  
566   @par Example 616   @par Example
567   @code 617   @code
568   task<void> example() 618   task<void> example()
569   { 619   {
570   std::vector<io_task<size_t>> reads; 620   std::vector<io_task<size_t>> reads;
571   for (auto& buf : buffers) 621   for (auto& buf : buffers)
572   reads.push_back(stream.read_some(buf)); 622   reads.push_back(stream.read_some(buf));
573   623  
574   auto [ec, counts] = co_await when_all(std::move(reads)); 624   auto [ec, counts] = co_await when_all(std::move(reads));
575   if (ec) { // handle error 625   if (ec) { // handle error
576   } 626   }
577   } 627   }
578   @endcode 628   @endcode
579   629  
580   @see IoAwaitableRange, when_all 630   @see IoAwaitableRange, when_all
581   */ 631   */
582   template<IoAwaitableRange R> 632   template<IoAwaitableRange R>
583   requires detail::is_io_result_v< 633   requires detail::is_io_result_v<
584   awaitable_result_t<std::ranges::range_value_t<R>>> 634   awaitable_result_t<std::ranges::range_value_t<R>>>
585   && (!std::is_same_v< 635   && (!std::is_same_v<
586   detail::io_result_payload_t< 636   detail::io_result_payload_t<
587   awaitable_result_t<std::ranges::range_value_t<R>>>, 637   awaitable_result_t<std::ranges::range_value_t<R>>>,
588   std::tuple<>>) 638   std::tuple<>>)
HITCBC 589   14 [[nodiscard]] auto when_all(R&& awaitables) 639   14 [[nodiscard]] auto when_all(R&& awaitables)
590   -> task<io_result<std::vector< 640   -> task<io_result<std::vector<
591   detail::io_result_payload_t< 641   detail::io_result_payload_t<
592   awaitable_result_t<std::ranges::range_value_t<R>>>>>> 642   awaitable_result_t<std::ranges::range_value_t<R>>>>>>
593   { 643   {
594   using Awaitable = std::ranges::range_value_t<R>; 644   using Awaitable = std::ranges::range_value_t<R>;
595   using PayloadT = detail::io_result_payload_t< 645   using PayloadT = detail::io_result_payload_t<
596   awaitable_result_t<Awaitable>>; 646   awaitable_result_t<Awaitable>>;
597   using OwnedRange = std::remove_cvref_t<R>; 647   using OwnedRange = std::remove_cvref_t<R>;
598   648  
599   auto count = std::ranges::size(awaitables); 649   auto count = std::ranges::size(awaitables);
600   if(count == 0) 650   if(count == 0)
601   throw std::invalid_argument("when_all requires at least one awaitable"); 651   throw std::invalid_argument("when_all requires at least one awaitable");
602   652  
603   OwnedRange owned_awaitables = std::forward<R>(awaitables); 653   OwnedRange owned_awaitables = std::forward<R>(awaitables);
604   654  
605   detail::when_all_homogeneous_state<PayloadT> state(count); 655   detail::when_all_homogeneous_state<PayloadT> state(count);
606   656  
607   co_await detail::when_all_homogeneous_launcher<OwnedRange>( 657   co_await detail::when_all_homogeneous_launcher<OwnedRange>(
608   &owned_awaitables, &state); 658   &owned_awaitables, &state);
609   659  
610   if(state.core_.first_exception_) 660   if(state.core_.first_exception_)
611   std::rethrow_exception(state.core_.first_exception_); 661   std::rethrow_exception(state.core_.first_exception_);
612   662  
613   if(state.has_error_.load(std::memory_order_relaxed)) 663   if(state.has_error_.load(std::memory_order_relaxed))
614   co_return io_result<std::vector<PayloadT>>{state.first_error_, {}}; 664   co_return io_result<std::vector<PayloadT>>{state.first_error_, {}};
615   665  
616   std::vector<PayloadT> results; 666   std::vector<PayloadT> results;
617   results.reserve(count); 667   results.reserve(count);
618   for(auto& opt : state.results_) 668   for(auto& opt : state.results_)
619   results.push_back(std::move(*opt)); 669   results.push_back(std::move(*opt));
620   670  
621   co_return io_result<std::vector<PayloadT>>{{}, std::move(results)}; 671   co_return io_result<std::vector<PayloadT>>{{}, std::move(results)};
HITCBC 622   28 } 672   28 }
623   673  
624   /** Execute a range of void io_result-returning awaitables concurrently. 674   /** Execute a range of void io_result-returning awaitables concurrently.
625   675  
626 - Launches all awaitables simultaneously and waits for all to complete. 676 + Starts all awaitables simultaneously and waits for all to complete.
627   Since all awaitables return io_result<>, no payload values are 677   Since all awaitables return io_result<>, no payload values are
628 - collected. The first error_code cancels siblings and is propagated. 678 + collected. The first error_code makes a stop request that every
629 - Exceptions always beat error codes. 679 + sibling observes, and is propagated. Exceptions always beat error
  680 + codes.
  681 +
  682 + @par Await-effects
  683 +
  684 + Takes ownership of the range, creates one wrapper coroutine per
  685 + element, then posts every wrapper to the caller's executor. All
  686 + children therefore run concurrently, each awaited with the caller's
  687 + executor and frame allocator and with a stop token owned by this
  688 + operation.
  689 +
  690 + Awaiting an empty range throws `std::invalid_argument` before any
  691 + child is started.
  692 +
  693 + A stop request is made on the operation's own stop token when:
  694 +
  695 + @li a child await-returns a non-zero `ec`, or
  696 + @li a child exits via an exception, or
  697 + @li the caller's stop token is triggered.
  698 +
  699 + Every sibling observes that request through the stop token it was
  700 + awaited with. The request does not end the operation: the await
  701 + completes only after every child has finished.
  702 +
  703 + @par Await-returns
  704 + An object of type `io_result<>` destructuring as `[ec]`. The children
  705 + have no payloads, so nothing else is reported.
  706 +
  707 + `ec` is the first non-zero `ec` await-returned by a child, in
  708 + completion order rather than input order. The `ec` of every other
  709 + child is discarded.
  710 +
  711 + If any child exits via an exception, the first such exception is
  712 + rethrown instead of await-returning, even when a child also reported
  713 + an `ec`.
  714 +
  715 + @par Await-postcondition
  716 + Every child has finished. `ec` is success only if every child
  717 + await-returned success.
  718 +
  719 + @par Remarks
  720 + Supports _IoAwaitable cancellation_.
  721 +
  722 + @par Thread Safety
  723 + The returned task must be awaited from a single execution context.
  724 + Child awaitables execute concurrently but complete through the caller's
  725 + executor.
630   726  
631   @param awaitables Range of io_result<>-returning awaitables to 727   @param awaitables Range of io_result<>-returning awaitables to
632   execute concurrently (must not be empty). 728   execute concurrently (must not be empty).
633   729  
634   @return A task yielding io_result<> whose ec is the first child 730   @return A task yielding io_result<> whose ec is the first child
635   error, or default-constructed on success. 731   error, or default-constructed on success.
636   732  
637   @throws std::invalid_argument if range is empty. 733   @throws std::invalid_argument if range is empty.
638 - @throws Rethrows the first child exception after all children 734 +
639 - complete (exception beats error_code). 735 + @par Exception Safety
  736 + If a child throws, the first child exception is rethrown after
  737 + all children complete (exception beats error_code).
640   738  
641   @par Example 739   @par Example
642   @code 740   @code
643   task<void> example() 741   task<void> example()
644   { 742   {
645   std::vector<io_task<>> jobs; 743   std::vector<io_task<>> jobs;
646   for (int i = 0; i < n; ++i) 744   for (int i = 0; i < n; ++i)
647   jobs.push_back(process(i)); 745   jobs.push_back(process(i));
648   746  
649   auto [ec] = co_await when_all(std::move(jobs)); 747   auto [ec] = co_await when_all(std::move(jobs));
650   } 748   }
651   @endcode 749   @endcode
652   750  
653   @see IoAwaitableRange, when_all 751   @see IoAwaitableRange, when_all
654   */ 752   */
655   template<IoAwaitableRange R> 753   template<IoAwaitableRange R>
656   requires detail::is_io_result_v< 754   requires detail::is_io_result_v<
657   awaitable_result_t<std::ranges::range_value_t<R>>> 755   awaitable_result_t<std::ranges::range_value_t<R>>>
658   && std::is_same_v< 756   && std::is_same_v<
659   detail::io_result_payload_t< 757   detail::io_result_payload_t<
660   awaitable_result_t<std::ranges::range_value_t<R>>>, 758   awaitable_result_t<std::ranges::range_value_t<R>>>,
661   std::tuple<>> 759   std::tuple<>>
HITCBC 662   4 [[nodiscard]] auto when_all(R&& awaitables) -> task<io_result<>> 760   4 [[nodiscard]] auto when_all(R&& awaitables) -> task<io_result<>>
663   { 761   {
664   using OwnedRange = std::remove_cvref_t<R>; 762   using OwnedRange = std::remove_cvref_t<R>;
665   763  
666   auto count = std::ranges::size(awaitables); 764   auto count = std::ranges::size(awaitables);
667   if(count == 0) 765   if(count == 0)
668   throw std::invalid_argument("when_all requires at least one awaitable"); 766   throw std::invalid_argument("when_all requires at least one awaitable");
669   767  
670   OwnedRange owned_awaitables = std::forward<R>(awaitables); 768   OwnedRange owned_awaitables = std::forward<R>(awaitables);
671   769  
672   detail::when_all_homogeneous_state<std::tuple<>> state(count); 770   detail::when_all_homogeneous_state<std::tuple<>> state(count);
673   771  
674   co_await detail::when_all_homogeneous_launcher<OwnedRange>( 772   co_await detail::when_all_homogeneous_launcher<OwnedRange>(
675   &owned_awaitables, &state); 773   &owned_awaitables, &state);
676   774  
677   if(state.core_.first_exception_) 775   if(state.core_.first_exception_)
678   std::rethrow_exception(state.core_.first_exception_); 776   std::rethrow_exception(state.core_.first_exception_);
679   777  
680   if(state.has_error_.load(std::memory_order_relaxed)) 778   if(state.has_error_.load(std::memory_order_relaxed))
681   co_return io_result<>{state.first_error_}; 779   co_return io_result<>{state.first_error_};
682   780  
683   co_return io_result<>{}; 781   co_return io_result<>{};
HITCBC 684   8 } 782   8 }
685   783  
686   /** Execute io_result-returning awaitables concurrently, inspecting error codes. 784   /** Execute io_result-returning awaitables concurrently, inspecting error codes.
687   785  
688   Overload selected when all children return io_result<Ts...>. 786   Overload selected when all children return io_result<Ts...>.
689   The error_code is lifted out of each child into a single outer 787   The error_code is lifted out of each child into a single outer
690   io_result. On success all values are returned; on failure the 788   io_result. On success all values are returned; on failure the
691   first error_code wins. 789   first error_code wins.
692   790  
  791 + @par Await-effects
  792 +
  793 + Creates and posts one wrapper coroutine per argument to the caller's
  794 + executor, in argument order. All children therefore run concurrently,
  795 + each awaited with the caller's executor and frame allocator and with
  796 + a stop token owned by this operation. The overload requires at least
  797 + one awaitable, so there is no empty case.
  798 +
  799 + A stop request is made on the operation's own stop token when:
  800 +
  801 + @li a child await-returns a non-zero `ec`, or
  802 + @li a child exits via an exception, or
  803 + @li the caller's stop token is triggered.
  804 +
  805 + Every sibling observes that request through the stop token it was
  806 + awaited with. The request does not end the operation: the await
  807 + completes only after every child has finished.
  808 +
  809 + @par Await-returns
  810 + An object of type `io_result<P1, ..., Pn>` destructuring as
  811 + `[ec, v1, ..., vn]`, where `Pi` is the payload of the i-th child's
  812 + `io_result`.
  813 +
  814 + `ec` is the first non-zero `ec` await-returned by a child, in
  815 + completion order rather than argument order. The `ec` of every other
  816 + child is discarded.
  817 +
  818 + Each `vi` is the payload the i-th child itself await-returned, even
  819 + when that child or a sibling reported an `ec`. A failed child
  820 + therefore still contributes whatever payload it produced. This
  821 + differs from the range overloads, which discard all payloads once any
  822 + child fails.
  823 +
  824 + If any child exits via an exception, the first such exception is
  825 + rethrown instead of await-returning, even when a child also reported
  826 + an `ec`.
  827 +
  828 + @par Await-postcondition
  829 + Every child has finished. Each `vi` holds the i-th child's payload,
  830 + and `ec` is success only if every child await-returned success.
  831 +
  832 + @par Remarks
  833 + Supports _IoAwaitable cancellation_.
  834 +
  835 + @par Thread Safety
  836 + The returned task must be awaited from a single execution context.
  837 + Child awaitables execute concurrently but complete through the caller's
  838 + executor.
  839 +
693   @par Exception Safety 840   @par Exception Safety
694 - Exception always beats error_code. If any child throws, the 841 + If a child throws, the first child exception is rethrown after
695 - exception is rethrown regardless of error_code results. 842 + all children complete (exception beats error_code).
696   843  
697   @param awaitables One or more awaitables each returning 844   @param awaitables One or more awaitables each returning
698   io_result<Ts...>. 845   io_result<Ts...>.
699   846  
700   @return A task yielding io_result<R1, R2, ..., Rn> where each Ri 847   @return A task yielding io_result<R1, R2, ..., Rn> where each Ri
701 -  
702 - @throws Rethrows the first child exception after all children  
703 - complete (exception beats error_code).  
704   follows the payload flattening rules. 848   follows the payload flattening rules.
705   */ 849   */
706   template<IoAwaitable... As> 850   template<IoAwaitable... As>
707   requires (sizeof...(As) > 0) 851   requires (sizeof...(As) > 0)
708   && detail::all_io_result_awaitables<As...> 852   && detail::all_io_result_awaitables<As...>
HITCBC 709   66 [[nodiscard]] auto when_all(As... awaitables) 853   66 [[nodiscard]] auto when_all(As... awaitables)
710   -> task<io_result< 854   -> task<io_result<
711   detail::io_result_payload_t<awaitable_result_t<As>>...>> 855   detail::io_result_payload_t<awaitable_result_t<As>>...>>
712   { 856   {
713   using result_type = io_result< 857   using result_type = io_result<
714   detail::io_result_payload_t<awaitable_result_t<As>>...>; 858   detail::io_result_payload_t<awaitable_result_t<As>>...>;
715   859  
716   detail::when_all_state<awaitable_result_t<As>...> state; 860   detail::when_all_state<awaitable_result_t<As>...> state;
717   std::tuple<As...> awaitable_tuple(std::move(awaitables)...); 861   std::tuple<As...> awaitable_tuple(std::move(awaitables)...);
718   862  
719   co_await detail::when_all_io_launcher<As...>(&awaitable_tuple, &state); 863   co_await detail::when_all_io_launcher<As...>(&awaitable_tuple, &state);
720   864  
721   // Exception always wins over error_code 865   // Exception always wins over error_code
722   if(state.core_.first_exception_) 866   if(state.core_.first_exception_)
723   std::rethrow_exception(state.core_.first_exception_); 867   std::rethrow_exception(state.core_.first_exception_);
724   868  
725   auto r = detail::build_when_all_io_result<result_type>( 869   auto r = detail::build_when_all_io_result<result_type>(
726   detail::extract_results(state)); 870   detail::extract_results(state));
727   if(state.has_error_.load(std::memory_order_relaxed)) 871   if(state.has_error_.load(std::memory_order_relaxed))
728   r.ec = state.first_error_; 872   r.ec = state.first_error_;
729   co_return r; 873   co_return r;
HITCBC 730   132 } 874   132 }
731   875  
732   } // namespace capy 876   } // namespace capy
733   } // namespace boost 877   } // namespace boost
734   878  
735   #endif 879   #endif