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