100.00% Lines (165/165) 100.00% Functions (41/41)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // Copyright (c) 2026 Steve Gerbino 3   // Copyright (c) 2026 Steve Gerbino
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // 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)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
9   // 9   //
10   10  
11   #ifndef BOOST_CAPY_WHEN_ANY_HPP 11   #ifndef BOOST_CAPY_WHEN_ANY_HPP
12   #define BOOST_CAPY_WHEN_ANY_HPP 12   #define BOOST_CAPY_WHEN_ANY_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/detail/io_result_combinators.hpp> 15   #include <boost/capy/detail/io_result_combinators.hpp>
16   #include <boost/capy/continuation.hpp> 16   #include <boost/capy/continuation.hpp>
17   #include <boost/capy/concept/executor.hpp> 17   #include <boost/capy/concept/executor.hpp>
18   #include <boost/capy/concept/io_awaitable.hpp> 18   #include <boost/capy/concept/io_awaitable.hpp>
19   #include <coroutine> 19   #include <coroutine>
20   #include <boost/capy/ex/executor_ref.hpp> 20   #include <boost/capy/ex/executor_ref.hpp>
21   #include <boost/capy/ex/frame_alloc_mixin.hpp> 21   #include <boost/capy/ex/frame_alloc_mixin.hpp>
22   #include <boost/capy/ex/frame_allocator.hpp> 22   #include <boost/capy/ex/frame_allocator.hpp>
23   #include <boost/capy/ex/io_env.hpp> 23   #include <boost/capy/ex/io_env.hpp>
24   #include <boost/capy/task.hpp> 24   #include <boost/capy/task.hpp>
25   25  
26   #include <array> 26   #include <array>
27   #include <atomic> 27   #include <atomic>
28   #include <exception> 28   #include <exception>
29   #include <memory> 29   #include <memory>
30   #include <mutex> 30   #include <mutex>
31   #include <optional> 31   #include <optional>
32   #include <ranges> 32   #include <ranges>
33   #include <stdexcept> 33   #include <stdexcept>
34   #include <stop_token> 34   #include <stop_token>
35   #include <tuple> 35   #include <tuple>
36   #include <type_traits> 36   #include <type_traits>
37   #include <utility> 37   #include <utility>
38   #include <variant> 38   #include <variant>
39   #include <vector> 39   #include <vector>
40   40  
41   /* 41   /*
42   when_any - Race multiple io_result tasks, select first success 42   when_any - Race multiple io_result tasks, select first success
43   ============================================================= 43   =============================================================
44   44  
45   OVERVIEW: 45   OVERVIEW:
46   --------- 46   ---------
47   when_any launches N io_result-returning tasks concurrently. A task 47   when_any launches N io_result-returning tasks concurrently. A task
48   wins by returning !ec; errors and exceptions do not win. Once a 48   wins by returning !ec; errors and exceptions do not win. Once a
49   winner is found, stop is requested for siblings and the winner's 49   winner is found, stop is requested for siblings and the winner's
50 - payload is returned. If no winner exists (all fail), the first 50 + payload is returned. If no winner exists (all fail), one of the
51 - error_code is returned or the last exception is rethrown. 51 + failures is surfaced (an error_code at variant index 0, or a child's
  52 + exception rethrown); which one is unspecified.
52   53  
53   ARCHITECTURE: 54   ARCHITECTURE:
54   ------------- 55   -------------
55   The design mirrors when_all but with inverted completion semantics: 56   The design mirrors when_all but with inverted completion semantics:
56   57  
57   when_all: complete when remaining_count reaches 0 (all done) 58   when_all: complete when remaining_count reaches 0 (all done)
58   when_any: complete when has_winner becomes true (first done) 59   when_any: complete when has_winner becomes true (first done)
59   BUT still wait for remaining_count to reach 0 for cleanup 60   BUT still wait for remaining_count to reach 0 for cleanup
60   61  
61   Key components: 62   Key components:
62   - when_any_core: Shared state tracking winner and completion 63   - when_any_core: Shared state tracking winner and completion
63   - when_any_io_runner: Wrapper coroutine for each child task 64   - when_any_io_runner: Wrapper coroutine for each child task
64   - when_any_io_launcher/when_any_io_homogeneous_launcher: 65   - when_any_io_launcher/when_any_io_homogeneous_launcher:
65   Awaitables that start all runners concurrently 66   Awaitables that start all runners concurrently
66   67  
67   CRITICAL INVARIANTS: 68   CRITICAL INVARIANTS:
68   -------------------- 69   --------------------
69   1. Only a task returning !ec can become the winner (via atomic CAS) 70   1. Only a task returning !ec can become the winner (via atomic CAS)
70   2. All tasks must complete before parent resumes (cleanup safety) 71   2. All tasks must complete before parent resumes (cleanup safety)
71   3. Stop is requested immediately when winner is determined 72   3. Stop is requested immediately when winner is determined
72   4. Exceptions and errors do not claim winner status 73   4. Exceptions and errors do not claim winner status
73   74  
74   POSITIONAL VARIANT: 75   POSITIONAL VARIANT:
75   ------------------- 76   -------------------
76   The variadic overload returns std::variant<error_code, R1, R2, ..., Rn>. 77   The variadic overload returns std::variant<error_code, R1, R2, ..., Rn>.
77   Index 0 is error_code (failure/no-winner). Index 1..N identifies the 78   Index 0 is error_code (failure/no-winner). Index 1..N identifies the
78   winning child and carries its payload. 79   winning child and carries its payload.
79   80  
80   RANGE OVERLOAD: 81   RANGE OVERLOAD:
81   --------------- 82   ---------------
82   The range overload returns variant<error_code, pair<size_t, T>> for 83   The range overload returns variant<error_code, pair<size_t, T>> for
83   non-void children or variant<error_code, size_t> for void children. 84   non-void children or variant<error_code, size_t> for void children.
84   85  
85   MEMORY MODEL: 86   MEMORY MODEL:
86   ------------- 87   -------------
87   Synchronization chain from winner's write to parent's read: 88   Synchronization chain from winner's write to parent's read:
88   89  
89   1. Winner thread writes result_ (non-atomic) 90   1. Winner thread writes result_ (non-atomic)
90   2. Winner thread calls signal_completion() -> fetch_sub(acq_rel) on remaining_count_ 91   2. Winner thread calls signal_completion() -> fetch_sub(acq_rel) on remaining_count_
91   3. Last task thread (may be winner or non-winner) calls signal_completion() 92   3. Last task thread (may be winner or non-winner) calls signal_completion()
92   -> fetch_sub(acq_rel) on remaining_count_, observing count becomes 0 93   -> fetch_sub(acq_rel) on remaining_count_, observing count becomes 0
93   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer 94   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer
94   5. Parent coroutine resumes and reads result_ 95   5. Parent coroutine resumes and reads result_
95   96  
96   Synchronization analysis: 97   Synchronization analysis:
97   - All fetch_sub operations on remaining_count_ form a release sequence 98   - All fetch_sub operations on remaining_count_ form a release sequence
98   - Winner's fetch_sub releases; subsequent fetch_sub operations participate 99   - Winner's fetch_sub releases; subsequent fetch_sub operations participate
99   in the modification order of remaining_count_ 100   in the modification order of remaining_count_
100   - Last task's fetch_sub(acq_rel) synchronizes-with prior releases in the 101   - Last task's fetch_sub(acq_rel) synchronizes-with prior releases in the
101   modification order, establishing happens-before from winner's writes 102   modification order, establishing happens-before from winner's writes
102   - Executor dispatch() is expected to provide queue-based synchronization 103   - Executor dispatch() is expected to provide queue-based synchronization
103   (release-on-post, acquire-on-execute) completing the chain to parent 104   (release-on-post, acquire-on-execute) completing the chain to parent
104   - Even inline executors work (same thread = sequenced-before) 105   - Even inline executors work (same thread = sequenced-before)
105   106  
106   EXCEPTION SEMANTICS: 107   EXCEPTION SEMANTICS:
107   -------------------- 108   --------------------
108   Exceptions do NOT claim winner status. If a child throws, the exception 109   Exceptions do NOT claim winner status. If a child throws, the exception
109   is recorded but the combinator keeps waiting for a success. Only when 110   is recorded but the combinator keeps waiting for a success. Only when
110 - all children complete without a winner does the combinator check: if 111 + all children complete without a winner is a failure surfaced. There is
111 - any exception was recorded, it is rethrown (exception beats error_code). 112 + no priority between errors and exceptions, and no guarantee about which
  113 + child's failure is reported: the result either returns an error_code at
  114 + variant index 0 or rethrows a child's exception.
112   */ 115   */
113   116  
114   namespace boost { 117   namespace boost {
115   namespace capy { 118   namespace capy {
116   119  
117   namespace detail { 120   namespace detail {
118   121  
119   /** Core shared state for when_any operations. 122   /** Core shared state for when_any operations.
120   123  
121   Contains all members and methods common to both heterogeneous (variadic) 124   Contains all members and methods common to both heterogeneous (variadic)
122   and homogeneous (range) when_any implementations. State classes embed 125   and homogeneous (range) when_any implementations. State classes embed
123   this via composition to avoid CRTP destructor ordering issues. 126   this via composition to avoid CRTP destructor ordering issues.
124   127  
125   @par Thread Safety 128   @par Thread Safety
126   Atomic operations protect winner selection and completion count. 129   Atomic operations protect winner selection and completion count.
127   */ 130   */
128   struct when_any_core 131   struct when_any_core
129   { 132   {
130   std::atomic<std::size_t> remaining_count_; 133   std::atomic<std::size_t> remaining_count_;
131   std::size_t winner_index_{0}; 134   std::size_t winner_index_{0};
132   std::exception_ptr winner_exception_; 135   std::exception_ptr winner_exception_;
133   std::stop_source stop_source_; 136   std::stop_source stop_source_;
134   137  
135   // Bridges parent's stop token to our stop_source 138   // Bridges parent's stop token to our stop_source
136   struct stop_callback_fn 139   struct stop_callback_fn
137   { 140   {
138   std::stop_source* source_; 141   std::stop_source* source_;
HITCBC 139   3 void operator()() const noexcept { source_->request_stop(); } 142   3 void operator()() const noexcept { source_->request_stop(); }
140   }; 143   };
141   using stop_callback_t = std::stop_callback<stop_callback_fn>; 144   using stop_callback_t = std::stop_callback<stop_callback_fn>;
142   std::optional<stop_callback_t> parent_stop_callback_; 145   std::optional<stop_callback_t> parent_stop_callback_;
143   146  
144   continuation continuation_; 147   continuation continuation_;
145   io_env const* caller_env_ = nullptr; 148   io_env const* caller_env_ = nullptr;
146   149  
147   // Placed last to avoid padding (1-byte atomic followed by 8-byte aligned members) 150   // Placed last to avoid padding (1-byte atomic followed by 8-byte aligned members)
148   std::atomic<bool> has_winner_{false}; 151   std::atomic<bool> has_winner_{false};
149   152  
HITCBC 150   34 explicit when_any_core(std::size_t count) noexcept 153   34 explicit when_any_core(std::size_t count) noexcept
HITCBC 151   34 : remaining_count_(count) 154   34 : remaining_count_(count)
152   { 155   {
HITCBC 153   34 } 156   34 }
154   157  
155   /** Atomically claim winner status; exactly one task succeeds. */ 158   /** Atomically claim winner status; exactly one task succeeds. */
HITCBC 156   53 bool try_win(std::size_t index) noexcept 159   53 bool try_win(std::size_t index) noexcept
157   { 160   {
HITCBC 158   53 bool expected = false; 161   53 bool expected = false;
HITCBC 159   53 if(has_winner_.compare_exchange_strong( 162   53 if(has_winner_.compare_exchange_strong(
160   expected, true, std::memory_order_acq_rel)) 163   expected, true, std::memory_order_acq_rel))
161   { 164   {
HITCBC 162   23 winner_index_ = index; 165   23 winner_index_ = index;
HITCBC 163   23 stop_source_.request_stop(); 166   23 stop_source_.request_stop();
HITCBC 164   23 return true; 167   23 return true;
165   } 168   }
HITCBC 166   30 return false; 169   30 return false;
167   } 170   }
168   171  
169   /** @pre try_win() returned true. */ 172   /** @pre try_win() returned true. */
HITCBC 170   1 void set_winner_exception(std::exception_ptr ep) noexcept 173   1 void set_winner_exception(std::exception_ptr ep) noexcept
171   { 174   {
HITCBC 172   1 winner_exception_ = ep; 175   1 winner_exception_ = ep;
HITCBC 173   1 } 176   1 }
174   177  
175   // Runners signal completion directly via final_suspend; no member function needed. 178   // Runners signal completion directly via final_suspend; no member function needed.
176   }; 179   };
177   180  
178   } // namespace detail 181   } // namespace detail
179   182  
180   namespace detail { 183   namespace detail {
181   184  
182   // State for io_result-aware when_any: only !ec wins. 185   // State for io_result-aware when_any: only !ec wins.
183   template<typename... Ts> 186   template<typename... Ts>
184   struct when_any_io_state 187   struct when_any_io_state
185   { 188   {
186   static constexpr std::size_t task_count = sizeof...(Ts); 189   static constexpr std::size_t task_count = sizeof...(Ts);
187   using variant_type = std::variant<std::error_code, Ts...>; 190   using variant_type = std::variant<std::error_code, Ts...>;
188   191  
189   when_any_core core_; 192   when_any_core core_;
190   std::optional<variant_type> result_; 193   std::optional<variant_type> result_;
191   std::array<continuation, task_count> runner_handles_{}; 194   std::array<continuation, task_count> runner_handles_{};
192   195  
193 - // Last failure (error or exception) for the all-fail case. 196 + // A failure (error or exception) for the all-fail case. record_error
194 - // Last writer wins — no priority between errors and exceptions. 197 + // and record_exception overwrite each other, so which one survives is
  198 + // unspecified (no priority between errors and exceptions).
195   std::mutex failure_mu_; 199   std::mutex failure_mu_;
196   std::error_code last_error_; 200   std::error_code last_error_;
197   std::exception_ptr last_exception_; 201   std::exception_ptr last_exception_;
198   202  
HITCBC 199   18 when_any_io_state() 203   18 when_any_io_state()
HITCBC 200   18 : core_(task_count) 204   18 : core_(task_count)
201   { 205   {
HITCBC 202   18 } 206   18 }
203   207  
HITCBC 204   14 void record_error(std::error_code ec) 208   14 void record_error(std::error_code ec)
205   { 209   {
HITCBC 206   14 std::lock_guard lk(failure_mu_); 210   14 std::lock_guard lk(failure_mu_);
HITCBC 207   14 last_error_ = ec; 211   14 last_error_ = ec;
HITCBC 208   14 last_exception_ = nullptr; 212   14 last_exception_ = nullptr;
HITCBC 209   14 } 213   14 }
210   214  
HITCBC 211   7 void record_exception(std::exception_ptr ep) 215   7 void record_exception(std::exception_ptr ep)
212   { 216   {
HITCBC 213   7 std::lock_guard lk(failure_mu_); 217   7 std::lock_guard lk(failure_mu_);
HITCBC 214   7 last_exception_ = ep; 218   7 last_exception_ = ep;
HITCBC 215   7 last_error_ = {}; 219   7 last_error_ = {};
HITCBC 216   7 } 220   7 }
217   }; 221   };
218   222  
219   // Wrapper coroutine for io_result-aware when_any children. 223   // Wrapper coroutine for io_result-aware when_any children.
220   // unhandled_exception records the exception but does NOT claim winner status. 224   // unhandled_exception records the exception but does NOT claim winner status.
221   template<typename StateType> 225   template<typename StateType>
222   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_any_io_runner 226   struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_any_io_runner
223   { 227   {
224   struct promise_type 228   struct promise_type
225   : frame_alloc_mixin 229   : frame_alloc_mixin
226   { 230   {
227   StateType* state_ = nullptr; 231   StateType* state_ = nullptr;
228   std::size_t index_ = 0; 232   std::size_t index_ = 0;
229   io_env env_; 233   io_env env_;
230   234  
HITCBC 231   87 when_any_io_runner get_return_object() noexcept 235   87 when_any_io_runner get_return_object() noexcept
232   { 236   {
233   return when_any_io_runner( 237   return when_any_io_runner(
HITCBC 234   87 std::coroutine_handle<promise_type>::from_promise(*this)); 238   87 std::coroutine_handle<promise_type>::from_promise(*this));
235   } 239   }
236   240  
HITCBC 237   87 std::suspend_always initial_suspend() noexcept { return {}; } 241   87 std::suspend_always initial_suspend() noexcept { return {}; }
238   242  
HITCBC 239   87 auto final_suspend() noexcept 243   87 auto final_suspend() noexcept
240   { 244   {
241   struct awaiter 245   struct awaiter
242   { 246   {
243   promise_type* p_; 247   promise_type* p_;
HITCBC 244   87 bool await_ready() const noexcept { return false; } 248   87 bool await_ready() const noexcept { return false; }
HITCBC 245   87 auto await_suspend(std::coroutine_handle<> h) noexcept 249   87 auto await_suspend(std::coroutine_handle<> h) noexcept
246   { 250   {
HITCBC 247   87 auto& core = p_->state_->core_; 251   87 auto& core = p_->state_->core_;
HITCBC 248   87 auto* counter = &core.remaining_count_; 252   87 auto* counter = &core.remaining_count_;
HITCBC 249   87 auto* caller_env = core.caller_env_; 253   87 auto* caller_env = core.caller_env_;
HITCBC 250   87 auto& cont = core.continuation_; 254   87 auto& cont = core.continuation_;
251   255  
HITCBC 252   87 h.destroy(); 256   87 h.destroy();
253   257  
HITCBC 254   87 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel); 258   87 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
HITCBC 255   87 if(remaining == 1) 259   87 if(remaining == 1)
HITCBC 256   34 return detail::symmetric_transfer(caller_env->executor.dispatch(cont)); 260   34 return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
HITCBC 257   53 return detail::symmetric_transfer(std::noop_coroutine()); 261   53 return detail::symmetric_transfer(std::noop_coroutine());
258   } 262   }
259   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 263   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
260   }; 264   };
HITCBC 261   87 return awaiter{this}; 265   87 return awaiter{this};
262   } 266   }
263   267  
HITCBC 264   74 void return_void() noexcept {} 268   74 void return_void() noexcept {}
265   269  
266   // Exceptions do NOT win in io_result when_any 270   // Exceptions do NOT win in io_result when_any
HITCBC 267   13 void unhandled_exception() noexcept 271   13 void unhandled_exception() noexcept
268   { 272   {
HITCBC 269   13 state_->record_exception(std::current_exception()); 273   13 state_->record_exception(std::current_exception());
HITCBC 270   13 } 274   13 }
271   275  
272   template<class Awaitable> 276   template<class Awaitable>
273   struct transform_awaiter 277   struct transform_awaiter
274   { 278   {
275   std::decay_t<Awaitable> a_; 279   std::decay_t<Awaitable> a_;
276   promise_type* p_; 280   promise_type* p_;
277   281  
HITCBC 278   87 bool await_ready() { return a_.await_ready(); } 282   87 bool await_ready() { return a_.await_ready(); }
HITCBC 279   87 decltype(auto) await_resume() { return a_.await_resume(); } 283   87 decltype(auto) await_resume() { return a_.await_resume(); }
280   284  
281   template<class Promise> 285   template<class Promise>
HITCBC 282   86 auto await_suspend(std::coroutine_handle<Promise> h) 286   86 auto await_suspend(std::coroutine_handle<Promise> h)
283   { 287   {
284   using R = decltype(a_.await_suspend(h, &p_->env_)); 288   using R = decltype(a_.await_suspend(h, &p_->env_));
285   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 289   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 286   86 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_)); 290   86 return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
287   else 291   else
288   return a_.await_suspend(h, &p_->env_); 292   return a_.await_suspend(h, &p_->env_);
289   } 293   }
290   }; 294   };
291   295  
292   template<class Awaitable> 296   template<class Awaitable>
HITCBC 293   87 auto await_transform(Awaitable&& a) 297   87 auto await_transform(Awaitable&& a)
294   { 298   {
295   using A = std::decay_t<Awaitable>; 299   using A = std::decay_t<Awaitable>;
296   if constexpr (IoAwaitable<A>) 300   if constexpr (IoAwaitable<A>)
297   { 301   {
298   return transform_awaiter<Awaitable>{ 302   return transform_awaiter<Awaitable>{
HITCBC 299   172 std::forward<Awaitable>(a), this}; 303   172 std::forward<Awaitable>(a), this};
300   } 304   }
301   else 305   else
302   { 306   {
303   static_assert(sizeof(A) == 0, "requires IoAwaitable"); 307   static_assert(sizeof(A) == 0, "requires IoAwaitable");
304   } 308   }
HITCBC 305   85 } 309   85 }
306   }; 310   };
307   311  
308   std::coroutine_handle<promise_type> h_; 312   std::coroutine_handle<promise_type> h_;
309   313  
HITCBC 310   87 explicit when_any_io_runner(std::coroutine_handle<promise_type> h) noexcept 314   87 explicit when_any_io_runner(std::coroutine_handle<promise_type> h) noexcept
HITCBC 311   87 : h_(h) 315   87 : h_(h)
312   { 316   {
HITCBC 313   87 } 317   87 }
314   318  
315   when_any_io_runner(when_any_io_runner&& other) noexcept 319   when_any_io_runner(when_any_io_runner&& other) noexcept
316   : h_(std::exchange(other.h_, nullptr)) 320   : h_(std::exchange(other.h_, nullptr))
317   { 321   {
318   } 322   }
319   323  
320   when_any_io_runner(when_any_io_runner const&) = delete; 324   when_any_io_runner(when_any_io_runner const&) = delete;
321   when_any_io_runner& operator=(when_any_io_runner const&) = delete; 325   when_any_io_runner& operator=(when_any_io_runner const&) = delete;
322   when_any_io_runner& operator=(when_any_io_runner&&) = delete; 326   when_any_io_runner& operator=(when_any_io_runner&&) = delete;
323   327  
HITCBC 324   87 auto release() noexcept 328   87 auto release() noexcept
325   { 329   {
HITCBC 326   87 return std::exchange(h_, nullptr); 330   87 return std::exchange(h_, nullptr);
327   } 331   }
328   }; 332   };
329   333  
330   // Runner coroutine: only tries to win when the child returns !ec. 334   // Runner coroutine: only tries to win when the child returns !ec.
331   template<std::size_t I, IoAwaitable Awaitable, typename StateType> 335   template<std::size_t I, IoAwaitable Awaitable, typename StateType>
332   when_any_io_runner<StateType> 336   when_any_io_runner<StateType>
HITCBC 333   33 make_when_any_io_runner(Awaitable inner, StateType* state) 337   33 make_when_any_io_runner(Awaitable inner, StateType* state)
334   { 338   {
335   auto result = co_await std::move(inner); 339   auto result = co_await std::move(inner);
336   340  
337   if(!result.ec) 341   if(!result.ec)
338   { 342   {
339   // Success: try to claim winner 343   // Success: try to claim winner
340   if(state->core_.try_win(I)) 344   if(state->core_.try_win(I))
341   { 345   {
342   try 346   try
343   { 347   {
344   state->result_.emplace( 348   state->result_.emplace(
345   std::in_place_index<I + 1>, 349   std::in_place_index<I + 1>,
346   detail::extract_io_payload(std::move(result))); 350   detail::extract_io_payload(std::move(result)));
347   } 351   }
348   catch(...) 352   catch(...)
349   { 353   {
350   state->core_.set_winner_exception(std::current_exception()); 354   state->core_.set_winner_exception(std::current_exception());
351   } 355   }
352   } 356   }
353   } 357   }
354   else 358   else
355   { 359   {
356   // Error: record but don't win 360   // Error: record but don't win
357   state->record_error(result.ec); 361   state->record_error(result.ec);
358   } 362   }
HITCBC 359   66 } 363   66 }
360   364  
361   // Launcher for io_result-aware when_any. 365   // Launcher for io_result-aware when_any.
362   template<IoAwaitable... Awaitables> 366   template<IoAwaitable... Awaitables>
363   class when_any_io_launcher 367   class when_any_io_launcher
364   { 368   {
365   using state_type = when_any_io_state< 369   using state_type = when_any_io_state<
366   io_result_payload_t<awaitable_result_t<Awaitables>>...>; 370   io_result_payload_t<awaitable_result_t<Awaitables>>...>;
367   371  
368   std::tuple<Awaitables...>* tasks_; 372   std::tuple<Awaitables...>* tasks_;
369   state_type* state_; 373   state_type* state_;
370   374  
371   public: 375   public:
HITCBC 372   18 when_any_io_launcher( 376   18 when_any_io_launcher(
373   std::tuple<Awaitables...>* tasks, 377   std::tuple<Awaitables...>* tasks,
374   state_type* state) 378   state_type* state)
HITCBC 375   18 : tasks_(tasks) 379   18 : tasks_(tasks)
HITCBC 376   18 , state_(state) 380   18 , state_(state)
377   { 381   {
HITCBC 378   18 } 382   18 }
379   383  
HITCBC 380   18 bool await_ready() const noexcept 384   18 bool await_ready() const noexcept
381   { 385   {
HITCBC 382   18 return sizeof...(Awaitables) == 0; 386   18 return sizeof...(Awaitables) == 0;
383   } 387   }
384   388  
HITCBC 385   18 std::coroutine_handle<> await_suspend( 389   18 std::coroutine_handle<> await_suspend(
386   std::coroutine_handle<> continuation, io_env const* caller_env) 390   std::coroutine_handle<> continuation, io_env const* caller_env)
387   { 391   {
HITCBC 388   18 state_->core_.continuation_.h = continuation; 392   18 state_->core_.continuation_.h = continuation;
HITCBC 389   18 state_->core_.caller_env_ = caller_env; 393   18 state_->core_.caller_env_ = caller_env;
390   394  
HITCBC 391   18 if(caller_env->stop_token.stop_possible()) 395   18 if(caller_env->stop_token.stop_possible())
392   { 396   {
HITCBC 393   4 state_->core_.parent_stop_callback_.emplace( 397   4 state_->core_.parent_stop_callback_.emplace(
HITCBC 394   2 caller_env->stop_token, 398   2 caller_env->stop_token,
HITCBC 395   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_}); 399   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_});
396   400  
HITCBC 397   2 if(caller_env->stop_token.stop_requested()) 401   2 if(caller_env->stop_token.stop_requested())
HITCBC 398   1 state_->core_.stop_source_.request_stop(); 402   1 state_->core_.stop_source_.request_stop();
399   } 403   }
400   404  
HITCBC 401   18 auto token = state_->core_.stop_source_.get_token(); 405   18 auto token = state_->core_.stop_source_.get_token();
HITCBC 402   18 launch_all(std::index_sequence_for<Awaitables...>{}, 406   18 launch_all(std::index_sequence_for<Awaitables...>{},
403   caller_env->executor, token); 407   caller_env->executor, token);
404   408  
HITCBC 405   36 return std::noop_coroutine(); 409   36 return std::noop_coroutine();
HITCBC 406   18 } 410   18 }
407   411  
HITCBC 408   18 void await_resume() const noexcept {} 412   18 void await_resume() const noexcept {}
409   413  
410   private: 414   private:
411   template<std::size_t... Is> 415   template<std::size_t... Is>
HITCBC 412   18 void launch_all(std::index_sequence<Is...>, 416   18 void launch_all(std::index_sequence<Is...>,
413   executor_ref ex, std::stop_token token) 417   executor_ref ex, std::stop_token token)
414   { 418   {
HITCBC 415   18 (..., launch_one<Is>(ex, token)); 419   18 (..., launch_one<Is>(ex, token));
HITCBC 416   18 } 420   18 }
417   421  
418   template<std::size_t I> 422   template<std::size_t I>
HITCBC 419   33 void launch_one(executor_ref caller_ex, std::stop_token token) 423   33 void launch_one(executor_ref caller_ex, std::stop_token token)
420   { 424   {
HITCBC 421   33 auto runner = make_when_any_io_runner<I>( 425   33 auto runner = make_when_any_io_runner<I>(
HITCBC 422   33 std::move(std::get<I>(*tasks_)), state_); 426   33 std::move(std::get<I>(*tasks_)), state_);
423   427  
HITCBC 424   33 auto h = runner.release(); 428   33 auto h = runner.release();
HITCBC 425   33 h.promise().state_ = state_; 429   33 h.promise().state_ = state_;
HITCBC 426   33 h.promise().index_ = I; 430   33 h.promise().index_ = I;
HITCBC 427   33 h.promise().env_ = io_env{caller_ex, token, 431   33 h.promise().env_ = io_env{caller_ex, token,
HITCBC 428   33 state_->core_.caller_env_->frame_allocator}; 432   33 state_->core_.caller_env_->frame_allocator};
429   433  
HITCBC 430   33 state_->runner_handles_[I].h = std::coroutine_handle<>{h}; 434   33 state_->runner_handles_[I].h = std::coroutine_handle<>{h};
HITCBC 431   33 caller_ex.post(state_->runner_handles_[I]); 435   33 caller_ex.post(state_->runner_handles_[I]);
HITCBC 432   66 } 436   66 }
433   }; 437   };
434   438  
435   /** Shared state for homogeneous io_result-aware when_any (range overload). 439   /** Shared state for homogeneous io_result-aware when_any (range overload).
436   440  
437   @tparam T The payload type extracted from io_result. 441   @tparam T The payload type extracted from io_result.
438   */ 442   */
439   template<typename T> 443   template<typename T>
440   struct when_any_io_homogeneous_state 444   struct when_any_io_homogeneous_state
441   { 445   {
442   when_any_core core_; 446   when_any_core core_;
443   std::optional<T> result_; 447   std::optional<T> result_;
444   std::unique_ptr<continuation[]> runner_handles_; 448   std::unique_ptr<continuation[]> runner_handles_;
445   449  
446   std::mutex failure_mu_; 450   std::mutex failure_mu_;
447   std::error_code last_error_; 451   std::error_code last_error_;
448   std::exception_ptr last_exception_; 452   std::exception_ptr last_exception_;
449   453  
HITCBC 450   13 explicit when_any_io_homogeneous_state(std::size_t count) 454   13 explicit when_any_io_homogeneous_state(std::size_t count)
HITCBC 451   13 : core_(count) 455   13 : core_(count)
HITCBC 452   13 , runner_handles_(std::make_unique<continuation[]>(count)) 456   13 , runner_handles_(std::make_unique<continuation[]>(count))
453   { 457   {
HITCBC 454   13 } 458   13 }
455   459  
HITCBC 456   6 void record_error(std::error_code ec) 460   6 void record_error(std::error_code ec)
457   { 461   {
HITCBC 458   6 std::lock_guard lk(failure_mu_); 462   6 std::lock_guard lk(failure_mu_);
HITCBC 459   6 last_error_ = ec; 463   6 last_error_ = ec;
HITCBC 460   6 last_exception_ = nullptr; 464   6 last_exception_ = nullptr;
HITCBC 461   6 } 465   6 }
462   466  
HITCBC 463   4 void record_exception(std::exception_ptr ep) 467   4 void record_exception(std::exception_ptr ep)
464   { 468   {
HITCBC 465   4 std::lock_guard lk(failure_mu_); 469   4 std::lock_guard lk(failure_mu_);
HITCBC 466   4 last_exception_ = ep; 470   4 last_exception_ = ep;
HITCBC 467   4 last_error_ = {}; 471   4 last_error_ = {};
HITCBC 468   4 } 472   4 }
469   }; 473   };
470   474  
471   /** Specialization for void io_result children (no payload storage). */ 475   /** Specialization for void io_result children (no payload storage). */
472   template<> 476   template<>
473   struct when_any_io_homogeneous_state<std::tuple<>> 477   struct when_any_io_homogeneous_state<std::tuple<>>
474   { 478   {
475   when_any_core core_; 479   when_any_core core_;
476   std::unique_ptr<continuation[]> runner_handles_; 480   std::unique_ptr<continuation[]> runner_handles_;
477   481  
478   std::mutex failure_mu_; 482   std::mutex failure_mu_;
479   std::error_code last_error_; 483   std::error_code last_error_;
480   std::exception_ptr last_exception_; 484   std::exception_ptr last_exception_;
481   485  
HITCBC 482   3 explicit when_any_io_homogeneous_state(std::size_t count) 486   3 explicit when_any_io_homogeneous_state(std::size_t count)
HITCBC 483   3 : core_(count) 487   3 : core_(count)
HITCBC 484   3 , runner_handles_(std::make_unique<continuation[]>(count)) 488   3 , runner_handles_(std::make_unique<continuation[]>(count))
485   { 489   {
HITCBC 486   3 } 490   3 }
487   491  
HITCBC 488   1 void record_error(std::error_code ec) 492   1 void record_error(std::error_code ec)
489   { 493   {
HITCBC 490   1 std::lock_guard lk(failure_mu_); 494   1 std::lock_guard lk(failure_mu_);
HITCBC 491   1 last_error_ = ec; 495   1 last_error_ = ec;
HITCBC 492   1 last_exception_ = nullptr; 496   1 last_exception_ = nullptr;
HITCBC 493   1 } 497   1 }
494   498  
HITCBC 495   2 void record_exception(std::exception_ptr ep) 499   2 void record_exception(std::exception_ptr ep)
496   { 500   {
HITCBC 497   2 std::lock_guard lk(failure_mu_); 501   2 std::lock_guard lk(failure_mu_);
HITCBC 498   2 last_exception_ = ep; 502   2 last_exception_ = ep;
HITCBC 499   2 last_error_ = {}; 503   2 last_error_ = {};
HITCBC 500   2 } 504   2 }
501   }; 505   };
502   506  
503   /** Create an io_result-aware runner for homogeneous when_any (range path). 507   /** Create an io_result-aware runner for homogeneous when_any (range path).
504   508  
505   Only tries to win when the child returns !ec. 509   Only tries to win when the child returns !ec.
506   */ 510   */
507   template<IoAwaitable Awaitable, typename StateType> 511   template<IoAwaitable Awaitable, typename StateType>
508   when_any_io_runner<StateType> 512   when_any_io_runner<StateType>
HITCBC 509   54 make_when_any_io_homogeneous_runner( 513   54 make_when_any_io_homogeneous_runner(
510   Awaitable inner, StateType* state, std::size_t index) 514   Awaitable inner, StateType* state, std::size_t index)
511   { 515   {
512   auto result = co_await std::move(inner); 516   auto result = co_await std::move(inner);
513   517  
514   if(!result.ec) 518   if(!result.ec)
515   { 519   {
516   if(state->core_.try_win(index)) 520   if(state->core_.try_win(index))
517   { 521   {
518   using PayloadT = io_result_payload_t< 522   using PayloadT = io_result_payload_t<
519   awaitable_result_t<Awaitable>>; 523   awaitable_result_t<Awaitable>>;
520   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>) 524   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
521   { 525   {
522   try 526   try
523   { 527   {
524   state->result_.emplace( 528   state->result_.emplace(
525   extract_io_payload(std::move(result))); 529   extract_io_payload(std::move(result)));
526   } 530   }
527   catch(...) 531   catch(...)
528   { 532   {
529   state->core_.set_winner_exception( 533   state->core_.set_winner_exception(
530   std::current_exception()); 534   std::current_exception());
531   } 535   }
532   } 536   }
533   } 537   }
534   } 538   }
535   else 539   else
536   { 540   {
537   state->record_error(result.ec); 541   state->record_error(result.ec);
538   } 542   }
HITCBC 539   108 } 543   108 }
540   544  
541   /** Launches all io_result-aware homogeneous runners concurrently. */ 545   /** Launches all io_result-aware homogeneous runners concurrently. */
542   template<IoAwaitableRange Range> 546   template<IoAwaitableRange Range>
543   class when_any_io_homogeneous_launcher 547   class when_any_io_homogeneous_launcher
544   { 548   {
545   using Awaitable = std::ranges::range_value_t<Range>; 549   using Awaitable = std::ranges::range_value_t<Range>;
546   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>; 550   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
547   551  
548   Range* range_; 552   Range* range_;
549   when_any_io_homogeneous_state<PayloadT>* state_; 553   when_any_io_homogeneous_state<PayloadT>* state_;
550   554  
551   public: 555   public:
HITCBC 552   16 when_any_io_homogeneous_launcher( 556   16 when_any_io_homogeneous_launcher(
553   Range* range, 557   Range* range,
554   when_any_io_homogeneous_state<PayloadT>* state) 558   when_any_io_homogeneous_state<PayloadT>* state)
HITCBC 555   16 : range_(range) 559   16 : range_(range)
HITCBC 556   16 , state_(state) 560   16 , state_(state)
557   { 561   {
HITCBC 558   16 } 562   16 }
559   563  
HITCBC 560   16 bool await_ready() const noexcept 564   16 bool await_ready() const noexcept
561   { 565   {
HITCBC 562   16 return std::ranges::empty(*range_); 566   16 return std::ranges::empty(*range_);
563   } 567   }
564   568  
HITCBC 565   16 std::coroutine_handle<> await_suspend( 569   16 std::coroutine_handle<> await_suspend(
566   std::coroutine_handle<> continuation, io_env const* caller_env) 570   std::coroutine_handle<> continuation, io_env const* caller_env)
567   { 571   {
HITCBC 568   16 state_->core_.continuation_.h = continuation; 572   16 state_->core_.continuation_.h = continuation;
HITCBC 569   16 state_->core_.caller_env_ = caller_env; 573   16 state_->core_.caller_env_ = caller_env;
570   574  
HITCBC 571   16 if(caller_env->stop_token.stop_possible()) 575   16 if(caller_env->stop_token.stop_possible())
572   { 576   {
HITCBC 573   4 state_->core_.parent_stop_callback_.emplace( 577   4 state_->core_.parent_stop_callback_.emplace(
HITCBC 574   2 caller_env->stop_token, 578   2 caller_env->stop_token,
HITCBC 575   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_}); 579   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_});
576   580  
HITCBC 577   2 if(caller_env->stop_token.stop_requested()) 581   2 if(caller_env->stop_token.stop_requested())
HITCBC 578   1 state_->core_.stop_source_.request_stop(); 582   1 state_->core_.stop_source_.request_stop();
579   } 583   }
580   584  
HITCBC 581   16 auto token = state_->core_.stop_source_.get_token(); 585   16 auto token = state_->core_.stop_source_.get_token();
582   586  
583   // Phase 1: Create all runners without dispatching. 587   // Phase 1: Create all runners without dispatching.
HITCBC 584   16 std::size_t index = 0; 588   16 std::size_t index = 0;
HITCBC 585   70 for(auto&& a : *range_) 589   70 for(auto&& a : *range_)
586   { 590   {
HITCBC 587   54 auto runner = make_when_any_io_homogeneous_runner( 591   54 auto runner = make_when_any_io_homogeneous_runner(
HITCBC 588   54 std::move(a), state_, index); 592   54 std::move(a), state_, index);
589   593  
HITCBC 590   54 auto h = runner.release(); 594   54 auto h = runner.release();
HITCBC 591   54 h.promise().state_ = state_; 595   54 h.promise().state_ = state_;
HITCBC 592   54 h.promise().index_ = index; 596   54 h.promise().index_ = index;
HITCBC 593   54 h.promise().env_ = io_env{caller_env->executor, token, 597   54 h.promise().env_ = io_env{caller_env->executor, token,
HITCBC 594   54 caller_env->frame_allocator}; 598   54 caller_env->frame_allocator};
595   599  
HITCBC 596   54 state_->runner_handles_[index].h = std::coroutine_handle<>{h}; 600   54 state_->runner_handles_[index].h = std::coroutine_handle<>{h};
HITCBC 597   54 ++index; 601   54 ++index;
598   } 602   }
599   603  
600   // Phase 2: Post all runners. Any may complete synchronously. 604   // Phase 2: Post all runners. Any may complete synchronously.
HITCBC 601   16 auto* handles = state_->runner_handles_.get(); 605   16 auto* handles = state_->runner_handles_.get();
HITCBC 602   16 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed); 606   16 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
HITCBC 603   70 for(std::size_t i = 0; i < count; ++i) 607   70 for(std::size_t i = 0; i < count; ++i)
HITCBC 604   54 caller_env->executor.post(handles[i]); 608   54 caller_env->executor.post(handles[i]);
605   609  
HITCBC 606   32 return std::noop_coroutine(); 610   32 return std::noop_coroutine();
HITCBC 607   70 } 611   70 }
608   612  
HITCBC 609   16 void await_resume() const noexcept {} 613   16 void await_resume() const noexcept {}
610   }; 614   };
611   615  
612   } // namespace detail 616   } // namespace detail
613   617  
614   /** Race a range of io_result-returning awaitables (non-void payloads). 618   /** Race a range of io_result-returning awaitables (non-void payloads).
615   619  
616   Only a child returning !ec can win. Errors and exceptions do not 620   Only a child returning !ec can win. Errors and exceptions do not
617 - claim winner status. If all children fail, the last failure 621 + claim winner status. If all children fail, an unspecified one of
618 - is reported — either the last error_code at variant index 0, 622 + the failures is reported — either an error_code at variant index 0,
619 - or the last exception rethrown. 623 + or a child's exception rethrown.
620   624  
621   @param awaitables Range of io_result-returning awaitables (must 625   @param awaitables Range of io_result-returning awaitables (must
622   not be empty). 626   not be empty).
623   627  
624   @return A task yielding variant<error_code, pair<size_t, PayloadT>> 628   @return A task yielding variant<error_code, pair<size_t, PayloadT>>
625   where index 0 is failure and index 1 carries the winner's 629   where index 0 is failure and index 1 carries the winner's
626   index and payload. 630   index and payload.
627   631  
628   @throws std::invalid_argument if range is empty. 632   @throws std::invalid_argument if range is empty.
629 - @throws Rethrows last exception when no winner and the last 633 + @throws Rethrows the winner's exception if extracting or
630 - failure was an exception. 634 + move-constructing the winning payload throws (a winner was
  635 + found but its result could not be produced).
  636 + @throws Rethrows a child's exception when all children fail and the
  637 + reported failure is an exception (which child is unspecified).
631   638  
632   @par Example 639   @par Example
633   @code 640   @code
634   task<void> example() 641   task<void> example()
635   { 642   {
636   std::vector<io_task<size_t>> reads; 643   std::vector<io_task<size_t>> reads;
637   for (auto& buf : buffers) 644   for (auto& buf : buffers)
638   reads.push_back(stream.read_some(buf)); 645   reads.push_back(stream.read_some(buf));
639   646  
640   auto result = co_await when_any(std::move(reads)); 647   auto result = co_await when_any(std::move(reads));
641   if (result.index() == 1) 648   if (result.index() == 1)
642   { 649   {
643   auto [idx, n] = std::get<1>(result); 650   auto [idx, n] = std::get<1>(result);
644   } 651   }
645   } 652   }
646   @endcode 653   @endcode
647   654  
648   @see IoAwaitableRange, when_any 655   @see IoAwaitableRange, when_any
649   */ 656   */
650   template<IoAwaitableRange R> 657   template<IoAwaitableRange R>
651   requires detail::is_io_result_v< 658   requires detail::is_io_result_v<
652   awaitable_result_t<std::ranges::range_value_t<R>>> 659   awaitable_result_t<std::ranges::range_value_t<R>>>
653   && (!std::is_same_v< 660   && (!std::is_same_v<
654   detail::io_result_payload_t< 661   detail::io_result_payload_t<
655   awaitable_result_t<std::ranges::range_value_t<R>>>, 662   awaitable_result_t<std::ranges::range_value_t<R>>>,
656   std::tuple<>>) 663   std::tuple<>>)
HITCBC 657   14 [[nodiscard]] auto when_any(R&& awaitables) 664   14 [[nodiscard]] auto when_any(R&& awaitables)
658   -> task<std::variant<std::error_code, 665   -> task<std::variant<std::error_code,
659   std::pair<std::size_t, 666   std::pair<std::size_t,
660   detail::io_result_payload_t< 667   detail::io_result_payload_t<
661   awaitable_result_t<std::ranges::range_value_t<R>>>>>> 668   awaitable_result_t<std::ranges::range_value_t<R>>>>>>
662   { 669   {
663   using Awaitable = std::ranges::range_value_t<R>; 670   using Awaitable = std::ranges::range_value_t<R>;
664   using PayloadT = detail::io_result_payload_t< 671   using PayloadT = detail::io_result_payload_t<
665   awaitable_result_t<Awaitable>>; 672   awaitable_result_t<Awaitable>>;
666   using result_type = std::variant<std::error_code, 673   using result_type = std::variant<std::error_code,
667   std::pair<std::size_t, PayloadT>>; 674   std::pair<std::size_t, PayloadT>>;
668   using OwnedRange = std::remove_cvref_t<R>; 675   using OwnedRange = std::remove_cvref_t<R>;
669   676  
670   auto count = std::ranges::size(awaitables); 677   auto count = std::ranges::size(awaitables);
671   if(count == 0) 678   if(count == 0)
672   throw std::invalid_argument("when_any requires at least one awaitable"); 679   throw std::invalid_argument("when_any requires at least one awaitable");
673   680  
674   OwnedRange owned_awaitables = std::forward<R>(awaitables); 681   OwnedRange owned_awaitables = std::forward<R>(awaitables);
675   682  
676   detail::when_any_io_homogeneous_state<PayloadT> state(count); 683   detail::when_any_io_homogeneous_state<PayloadT> state(count);
677   684  
678   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>( 685   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
679   &owned_awaitables, &state); 686   &owned_awaitables, &state);
680   687  
681   // Winner found 688   // Winner found
682   if(state.core_.has_winner_.load(std::memory_order_acquire)) 689   if(state.core_.has_winner_.load(std::memory_order_acquire))
683   { 690   {
684   if(state.core_.winner_exception_) 691   if(state.core_.winner_exception_)
685   std::rethrow_exception(state.core_.winner_exception_); 692   std::rethrow_exception(state.core_.winner_exception_);
686   co_return result_type{std::in_place_index<1>, 693   co_return result_type{std::in_place_index<1>,
687   std::pair{state.core_.winner_index_, std::move(*state.result_)}}; 694   std::pair{state.core_.winner_index_, std::move(*state.result_)}};
688   } 695   }
689   696  
690 - // No winner — report last failure 697 + // No winner — report the recorded failure
691   if(state.last_exception_) 698   if(state.last_exception_)
692   std::rethrow_exception(state.last_exception_); 699   std::rethrow_exception(state.last_exception_);
693   co_return result_type{std::in_place_index<0>, state.last_error_}; 700   co_return result_type{std::in_place_index<0>, state.last_error_};
HITCBC 694   28 } 701   28 }
695   702  
696   /** Race a range of void io_result-returning awaitables. 703   /** Race a range of void io_result-returning awaitables.
697   704  
698   Only a child returning !ec can win. Returns the winner's index 705   Only a child returning !ec can win. Returns the winner's index
699   at variant index 1, or error_code at index 0 on all-fail. 706   at variant index 1, or error_code at index 0 on all-fail.
700   707  
701   @param awaitables Range of io_result<>-returning awaitables (must 708   @param awaitables Range of io_result<>-returning awaitables (must
702   not be empty). 709   not be empty).
703   710  
704   @return A task yielding variant<error_code, size_t> where index 0 711   @return A task yielding variant<error_code, size_t> where index 0
705   is failure and index 1 carries the winner's index. 712   is failure and index 1 carries the winner's index.
706   713  
707   @throws std::invalid_argument if range is empty. 714   @throws std::invalid_argument if range is empty.
708 - @throws Rethrows first exception when no winner and at least one 715 + @throws Rethrows a child's exception when all children fail and the
709 - child threw. 716 + reported failure is an exception (which child is unspecified).
710   717  
711   @par Example 718   @par Example
712   @code 719   @code
713   task<void> example() 720   task<void> example()
714   { 721   {
715   std::vector<io_task<>> jobs; 722   std::vector<io_task<>> jobs;
716   jobs.push_back(background_work_a()); 723   jobs.push_back(background_work_a());
717   jobs.push_back(background_work_b()); 724   jobs.push_back(background_work_b());
718   725  
719   auto result = co_await when_any(std::move(jobs)); 726   auto result = co_await when_any(std::move(jobs));
720   if (result.index() == 1) 727   if (result.index() == 1)
721   { 728   {
722   auto winner = std::get<1>(result); 729   auto winner = std::get<1>(result);
723   } 730   }
724   } 731   }
725   @endcode 732   @endcode
726   733  
727   @see IoAwaitableRange, when_any 734   @see IoAwaitableRange, when_any
728   */ 735   */
729   template<IoAwaitableRange R> 736   template<IoAwaitableRange R>
730   requires detail::is_io_result_v< 737   requires detail::is_io_result_v<
731   awaitable_result_t<std::ranges::range_value_t<R>>> 738   awaitable_result_t<std::ranges::range_value_t<R>>>
732   && std::is_same_v< 739   && std::is_same_v<
733   detail::io_result_payload_t< 740   detail::io_result_payload_t<
734   awaitable_result_t<std::ranges::range_value_t<R>>>, 741   awaitable_result_t<std::ranges::range_value_t<R>>>,
735   std::tuple<>> 742   std::tuple<>>
HITCBC 736   3 [[nodiscard]] auto when_any(R&& awaitables) 743   3 [[nodiscard]] auto when_any(R&& awaitables)
737   -> task<std::variant<std::error_code, std::size_t>> 744   -> task<std::variant<std::error_code, std::size_t>>
738   { 745   {
739   using OwnedRange = std::remove_cvref_t<R>; 746   using OwnedRange = std::remove_cvref_t<R>;
740   using result_type = std::variant<std::error_code, std::size_t>; 747   using result_type = std::variant<std::error_code, std::size_t>;
741   748  
742   auto count = std::ranges::size(awaitables); 749   auto count = std::ranges::size(awaitables);
743   if(count == 0) 750   if(count == 0)
744   throw std::invalid_argument("when_any requires at least one awaitable"); 751   throw std::invalid_argument("when_any requires at least one awaitable");
745   752  
746   OwnedRange owned_awaitables = std::forward<R>(awaitables); 753   OwnedRange owned_awaitables = std::forward<R>(awaitables);
747   754  
748   detail::when_any_io_homogeneous_state<std::tuple<>> state(count); 755   detail::when_any_io_homogeneous_state<std::tuple<>> state(count);
749   756  
750   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>( 757   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
751   &owned_awaitables, &state); 758   &owned_awaitables, &state);
752   759  
753   // Winner found 760   // Winner found
754   if(state.core_.has_winner_.load(std::memory_order_acquire)) 761   if(state.core_.has_winner_.load(std::memory_order_acquire))
755   { 762   {
756   if(state.core_.winner_exception_) 763   if(state.core_.winner_exception_)
757   std::rethrow_exception(state.core_.winner_exception_); 764   std::rethrow_exception(state.core_.winner_exception_);
758   co_return result_type{std::in_place_index<1>, 765   co_return result_type{std::in_place_index<1>,
759   state.core_.winner_index_}; 766   state.core_.winner_index_};
760   } 767   }
761   768  
762 - // No winner — report last failure 769 + // No winner — report the recorded failure
763   if(state.last_exception_) 770   if(state.last_exception_)
764   std::rethrow_exception(state.last_exception_); 771   std::rethrow_exception(state.last_exception_);
765   co_return result_type{std::in_place_index<0>, state.last_error_}; 772   co_return result_type{std::in_place_index<0>, state.last_error_};
HITCBC 766   6 } 773   6 }
767   774  
768   /** Race io_result-returning awaitables, selecting the first success. 775   /** Race io_result-returning awaitables, selecting the first success.
769   776  
770   Overload selected when all children return io_result<Ts...>. 777   Overload selected when all children return io_result<Ts...>.
771   Only a child returning !ec can win. Errors and exceptions do 778   Only a child returning !ec can win. Errors and exceptions do
772   not claim winner status. 779   not claim winner status.
773   780  
774   @param as The awaitables to race. Each must satisfy @ref 781   @param as The awaitables to race. Each must satisfy @ref
775   IoAwaitable and is consumed (moved-from) when `when_any` 782   IoAwaitable and is consumed (moved-from) when `when_any`
776   is awaited. 783   is awaited.
777   784  
778   @return A task yielding variant<error_code, R1, ..., Rn> where 785   @return A task yielding variant<error_code, R1, ..., Rn> where
779   index 0 is the failure/no-winner case and index i+1 786   index 0 is the failure/no-winner case and index i+1
780 - identifies the winning child. 787 + identifies the winning child. On all-fail, index 0 holds
  788 + an error_code from one of the failed children (unspecified
  789 + which; no priority between errors and exceptions).
  790 +
  791 + @throws Rethrows the winner's exception if extracting or
  792 + constructing the winning payload throws (a winner was found
  793 + but its result could not be produced).
  794 + @throws Rethrows a child's exception when all children fail and the
  795 + reported failure is an exception (which child is unspecified).
781   796  
782   @note A failing child does not cancel its siblings; `when_any` 797   @note A failing child does not cancel its siblings; `when_any`
783   waits for a success or for every child to finish. To make a 798   waits for a success or for every child to finish. To make a
784   benign error (e.g. @c cond::canceled) count as a win, wrap 799   benign error (e.g. @c cond::canceled) count as a win, wrap
785   the child to translate the error into success. See the 800   the child to translate the error into success. See the
786   Concurrent Composition tutorial. 801   Concurrent Composition tutorial.
787   */ 802   */
788   template<IoAwaitable... As> 803   template<IoAwaitable... As>
789   requires (sizeof...(As) > 0) 804   requires (sizeof...(As) > 0)
790   && detail::all_io_result_awaitables<As...> 805   && detail::all_io_result_awaitables<As...>
HITCBC 791   18 [[nodiscard]] auto when_any(As... as) 806   18 [[nodiscard]] auto when_any(As... as)
792   -> task<std::variant< 807   -> task<std::variant<
793   std::error_code, 808   std::error_code,
794   detail::io_result_payload_t<awaitable_result_t<As>>...>> 809   detail::io_result_payload_t<awaitable_result_t<As>>...>>
795   { 810   {
796   using result_type = std::variant< 811   using result_type = std::variant<
797   std::error_code, 812   std::error_code,
798   detail::io_result_payload_t<awaitable_result_t<As>>...>; 813   detail::io_result_payload_t<awaitable_result_t<As>>...>;
799   814  
800   detail::when_any_io_state< 815   detail::when_any_io_state<
801   detail::io_result_payload_t<awaitable_result_t<As>>...> state; 816   detail::io_result_payload_t<awaitable_result_t<As>>...> state;
802   std::tuple<As...> awaitable_tuple(std::move(as)...); 817   std::tuple<As...> awaitable_tuple(std::move(as)...);
803   818  
804   co_await detail::when_any_io_launcher<As...>( 819   co_await detail::when_any_io_launcher<As...>(
805   &awaitable_tuple, &state); 820   &awaitable_tuple, &state);
806   821  
807   // Winner found: return their result 822   // Winner found: return their result
808   if(state.result_.has_value()) 823   if(state.result_.has_value())
809   co_return std::move(*state.result_); 824   co_return std::move(*state.result_);
810   825  
811   // Winner claimed but payload construction failed 826   // Winner claimed but payload construction failed
812   if(state.core_.winner_exception_) 827   if(state.core_.winner_exception_)
813   std::rethrow_exception(state.core_.winner_exception_); 828   std::rethrow_exception(state.core_.winner_exception_);
814   829  
815 - // No winner — report last failure 830 + // No winner — report the recorded failure
816   if(state.last_exception_) 831   if(state.last_exception_)
817   std::rethrow_exception(state.last_exception_); 832   std::rethrow_exception(state.last_exception_);
818   co_return result_type{std::in_place_index<0>, state.last_error_}; 833   co_return result_type{std::in_place_index<0>, state.last_error_};
HITCBC 819   36 } 834   36 }
820   835  
821   } // namespace capy 836   } // namespace capy
822   } // namespace boost 837   } // namespace boost
823   838  
824   #endif 839   #endif