93.94% Lines (155/165) 92.86% Functions (39/42)
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   2 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   31 explicit when_any_core(std::size_t count) noexcept
HITCBC 151   34 : remaining_count_(count) 151   31 : remaining_count_(count)
152   { 152   {
HITCBC 153   34 } 153   31 }
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   52 bool try_win(std::size_t index) noexcept
157   { 157   {
HITCBC 158   53 bool expected = false; 158   52 bool expected = false;
HITCBC 159   53 if(has_winner_.compare_exchange_strong( 159   52 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   22 winner_index_ = index;
HITCBC 163   23 stop_source_.request_stop(); 163   22 stop_source_.request_stop();
HITCBC 164   23 return true; 164   22 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. */
MISLBC 170   1 void set_winner_exception(std::exception_ptr ep) noexcept 170   void set_winner_exception(std::exception_ptr ep) noexcept
171   { 171   {
MISLBC 172   1 winner_exception_ = ep; 172   winner_exception_ = ep;
MISLBC 173   1 } 173   }
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   16 when_any_io_state()
HITCBC 200   18 : core_(task_count) 200   16 : core_(task_count)
201   { 201   {
HITCBC 202   18 } 202   16 }
203   203  
HITCBC 204   14 void record_error(std::error_code ec) 204   12 void record_error(std::error_code ec)
205   { 205   {
HITCBC 206   14 std::lock_guard lk(failure_mu_); 206   12 std::lock_guard lk(failure_mu_);
HITCBC 207   14 last_error_ = ec; 207   12 last_error_ = ec;
HITCBC 208   14 last_exception_ = nullptr; 208   12 last_exception_ = nullptr;
HITCBC 209   14 } 209   12 }
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   82 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   82 std::coroutine_handle<promise_type>::from_promise(*this));
235   } 235   }
236   236  
HITCBC 237   87 std::suspend_always initial_suspend() noexcept { return {}; } 237   82 std::suspend_always initial_suspend() noexcept { return {}; }
238   238  
HITCBC 239   87 auto final_suspend() noexcept 239   82 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   82 bool await_ready() const noexcept { return false; }
HITCBC 245   87 auto await_suspend(std::coroutine_handle<> h) noexcept 245   82 auto await_suspend(std::coroutine_handle<> h) noexcept
246   { 246   {
HITCBC 247   87 auto& core = p_->state_->core_; 247   82 auto& core = p_->state_->core_;
HITCBC 248   87 auto* counter = &core.remaining_count_; 248   82 auto* counter = &core.remaining_count_;
HITCBC 249   87 auto* caller_env = core.caller_env_; 249   82 auto* caller_env = core.caller_env_;
HITCBC 250   87 auto& cont = core.continuation_; 250   82 auto& cont = core.continuation_;
251   251  
HITCBC 252   87 h.destroy(); 252   82 h.destroy();
253   253  
HITCBC 254   87 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel); 254   82 auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
HITCBC 255   87 if(remaining == 1) 255   82 if(remaining == 1)
HITCBC 256   34 return detail::symmetric_transfer(caller_env->executor.dispatch(cont)); 256   31 return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
HITCBC 257   53 return detail::symmetric_transfer(std::noop_coroutine()); 257   51 return detail::symmetric_transfer(std::noop_coroutine());
258   } 258   }
MISUIC 259 - void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 259 + void await_resume() const noexcept {}
260   }; 260   };
HITCBC 261   87 return awaiter{this}; 261   82 return awaiter{this};
262   } 262   }
263   263  
HITCBC 264   74 void return_void() noexcept {} 264   71 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   11 void unhandled_exception() noexcept
268   { 268   {
HITCBC 269   13 state_->record_exception(std::current_exception()); 269   11 state_->record_exception(std::current_exception());
HITCBC 270   13 } 270   11 }
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   82 bool await_ready() { return a_.await_ready(); }
HITCBC 279   87 decltype(auto) await_resume() { return a_.await_resume(); } 279   82 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   81 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   81 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   82 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   163 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   81 }
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   82 explicit when_any_io_runner(std::coroutine_handle<promise_type> h) noexcept
HITCBC 311   87 : h_(h) 311   82 : h_(h)
312   { 312   {
HITCBC 313   87 } 313   82 }
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   82 auto release() noexcept
325   { 325   {
HITCBC 326   87 return std::exchange(h_, nullptr); 326   82 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   30 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   60 }
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   16 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   16 : tasks_(tasks)
HITCBC 376   18 , state_(state) 376   16 , state_(state)
377   { 377   {
HITCBC 378   18 } 378   16 }
379   379  
HITCBC 380   18 bool await_ready() const noexcept 380   16 bool await_ready() const noexcept
381   { 381   {
HITCBC 382   18 return sizeof...(Awaitables) == 0; 382   16 return sizeof...(Awaitables) == 0;
383   } 383   }
384   384  
HITCBC 385   18 std::coroutine_handle<> await_suspend( 385   16 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   16 state_->core_.continuation_.h = continuation;
HITCBC 389   18 state_->core_.caller_env_ = caller_env; 389   16 state_->core_.caller_env_ = caller_env;
390   390  
HITCBC 391   18 if(caller_env->stop_token.stop_possible()) 391   16 if(caller_env->stop_token.stop_possible())
392   { 392   {
HITCBC 393   4 state_->core_.parent_stop_callback_.emplace( 393   2 state_->core_.parent_stop_callback_.emplace(
HITCBC 394   2 caller_env->stop_token, 394   1 caller_env->stop_token,
HITCBC 395   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_}); 395   1 when_any_core::stop_callback_fn{&state_->core_.stop_source_});
396   396  
HITCBC 397   2 if(caller_env->stop_token.stop_requested()) 397   1 if(caller_env->stop_token.stop_requested())
MISLBC 398   1 state_->core_.stop_source_.request_stop(); 398   state_->core_.stop_source_.request_stop();
399   } 399   }
400   400  
HITCBC 401   18 auto token = state_->core_.stop_source_.get_token(); 401   16 auto token = state_->core_.stop_source_.get_token();
HITCBC 402 - 18 launch_all(std::index_sequence_for<Awaitables...>{}, 402 + 28 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
HITGIC 403 - caller_env->executor, token); 403 + 16 (..., launch_one<Is>(caller_env->executor, token));
HITGNC   404 + 16 }(std::index_sequence_for<Awaitables...>{});
404   405  
HITCBC 405   36 return std::noop_coroutine(); 406   32 return std::noop_coroutine();
HITCBC 406   18 } 407   16 }
407   408  
HITCBC 408   18 void await_resume() const noexcept {} 409   16 void await_resume() const noexcept {}
409   410  
410 - template<std::size_t... Is>  
411 - void launch_all(std::index_sequence<Is...>,  
DCB 412 - 18 executor_ref ex, std::stop_token token)  
413 - {  
414 - (..., launch_one<Is>(ex, token));  
DCB 415 - 18 }  
DCB 416 - 18  
417   private: 411   private:
418   template<std::size_t I> 412   template<std::size_t I>
HITCBC 419   33 void launch_one(executor_ref caller_ex, std::stop_token token) 413   30 void launch_one(executor_ref caller_ex, std::stop_token token)
420   { 414   {
HITCBC 421   33 auto runner = make_when_any_io_runner<I>( 415   30 auto runner = make_when_any_io_runner<I>(
HITCBC 422   33 std::move(std::get<I>(*tasks_)), state_); 416   30 std::move(std::get<I>(*tasks_)), state_);
423   417  
HITCBC 424   33 auto h = runner.release(); 418   30 auto h = runner.release();
HITCBC 425   33 h.promise().state_ = state_; 419   30 h.promise().state_ = state_;
HITCBC 426   33 h.promise().index_ = I; 420   30 h.promise().index_ = I;
HITCBC 427   33 h.promise().env_ = io_env{caller_ex, token, 421   30 h.promise().env_ = io_env{caller_ex, token,
HITCBC 428   33 state_->core_.caller_env_->frame_allocator}; 422   30 state_->core_.caller_env_->frame_allocator};
429   423  
HITCBC 430   33 state_->runner_handles_[I].h = std::coroutine_handle<>{h}; 424   30 state_->runner_handles_[I].h = std::coroutine_handle<>{h};
HITCBC 431   33 caller_ex.post(state_->runner_handles_[I]); 425   30 caller_ex.post(state_->runner_handles_[I]);
HITCBC 432   66 } 426   60 }
433   }; 427   };
434   428  
435   /** Shared state for homogeneous io_result-aware when_any (range overload). 429   /** Shared state for homogeneous io_result-aware when_any (range overload).
436   430  
437   @tparam T The payload type extracted from io_result. 431   @tparam T The payload type extracted from io_result.
438   */ 432   */
439   template<typename T> 433   template<typename T>
440   struct when_any_io_homogeneous_state 434   struct when_any_io_homogeneous_state
441   { 435   {
442   when_any_core core_; 436   when_any_core core_;
443   std::optional<T> result_; 437   std::optional<T> result_;
444   std::unique_ptr<continuation[]> runner_handles_; 438   std::unique_ptr<continuation[]> runner_handles_;
445   439  
446   std::mutex failure_mu_; 440   std::mutex failure_mu_;
447   std::error_code last_error_; 441   std::error_code last_error_;
448   std::exception_ptr last_exception_; 442   std::exception_ptr last_exception_;
449   443  
HITCBC 450   13 explicit when_any_io_homogeneous_state(std::size_t count) 444   13 explicit when_any_io_homogeneous_state(std::size_t count)
HITCBC 451   13 : core_(count) 445   13 : core_(count)
HITCBC 452   13 , runner_handles_(std::make_unique<continuation[]>(count)) 446   13 , runner_handles_(std::make_unique<continuation[]>(count))
453   { 447   {
HITCBC 454   13 } 448   13 }
455   449  
HITCBC 456   6 void record_error(std::error_code ec) 450   6 void record_error(std::error_code ec)
457   { 451   {
HITCBC 458   6 std::lock_guard lk(failure_mu_); 452   6 std::lock_guard lk(failure_mu_);
HITCBC 459   6 last_error_ = ec; 453   6 last_error_ = ec;
HITCBC 460   6 last_exception_ = nullptr; 454   6 last_exception_ = nullptr;
HITCBC 461   6 } 455   6 }
462   456  
HITCBC 463   4 void record_exception(std::exception_ptr ep) 457   4 void record_exception(std::exception_ptr ep)
464   { 458   {
HITCBC 465   4 std::lock_guard lk(failure_mu_); 459   4 std::lock_guard lk(failure_mu_);
HITCBC 466   4 last_exception_ = ep; 460   4 last_exception_ = ep;
HITCBC 467   4 last_error_ = {}; 461   4 last_error_ = {};
HITCBC 468   4 } 462   4 }
469   }; 463   };
470   464  
471   /** Specialization for void io_result children (no payload storage). */ 465   /** Specialization for void io_result children (no payload storage). */
472   template<> 466   template<>
473   struct when_any_io_homogeneous_state<std::tuple<>> 467   struct when_any_io_homogeneous_state<std::tuple<>>
474   { 468   {
475   when_any_core core_; 469   when_any_core core_;
476   std::unique_ptr<continuation[]> runner_handles_; 470   std::unique_ptr<continuation[]> runner_handles_;
477   471  
478   std::mutex failure_mu_; 472   std::mutex failure_mu_;
479   std::error_code last_error_; 473   std::error_code last_error_;
480   std::exception_ptr last_exception_; 474   std::exception_ptr last_exception_;
481   475  
HITCBC 482   3 explicit when_any_io_homogeneous_state(std::size_t count) 476   2 explicit when_any_io_homogeneous_state(std::size_t count)
HITCBC 483   3 : core_(count) 477   2 : core_(count)
HITCBC 484   3 , runner_handles_(std::make_unique<continuation[]>(count)) 478   2 , runner_handles_(std::make_unique<continuation[]>(count))
485   { 479   {
HITCBC 486   3 } 480   2 }
487   481  
HITCBC 488   1 void record_error(std::error_code ec) 482   1 void record_error(std::error_code ec)
489   { 483   {
HITCBC 490   1 std::lock_guard lk(failure_mu_); 484   1 std::lock_guard lk(failure_mu_);
HITCBC 491   1 last_error_ = ec; 485   1 last_error_ = ec;
HITCBC 492   1 last_exception_ = nullptr; 486   1 last_exception_ = nullptr;
HITCBC 493   1 } 487   1 }
494   488  
MISLBC 495   2 void record_exception(std::exception_ptr ep) 489   void record_exception(std::exception_ptr ep)
496   { 490   {
MISLBC 497   2 std::lock_guard lk(failure_mu_); 491   std::lock_guard lk(failure_mu_);
MISLBC 498   2 last_exception_ = ep; 492   last_exception_ = ep;
MISLBC 499   2 last_error_ = {}; 493   last_error_ = {};
MISLBC 500   2 } 494   }
501   }; 495   };
502   496  
503   /** Create an io_result-aware runner for homogeneous when_any (range path). 497   /** Create an io_result-aware runner for homogeneous when_any (range path).
504   498  
505   Only tries to win when the child returns !ec. 499   Only tries to win when the child returns !ec.
506   */ 500   */
507   template<IoAwaitable Awaitable, typename StateType> 501   template<IoAwaitable Awaitable, typename StateType>
508   when_any_io_runner<StateType> 502   when_any_io_runner<StateType>
HITCBC 509   54 make_when_any_io_homogeneous_runner( 503   52 make_when_any_io_homogeneous_runner(
510   Awaitable inner, StateType* state, std::size_t index) 504   Awaitable inner, StateType* state, std::size_t index)
511   { 505   {
512   auto result = co_await std::move(inner); 506   auto result = co_await std::move(inner);
513   507  
514   if(!result.ec) 508   if(!result.ec)
515   { 509   {
516   if(state->core_.try_win(index)) 510   if(state->core_.try_win(index))
517   { 511   {
518   using PayloadT = io_result_payload_t< 512   using PayloadT = io_result_payload_t<
519   awaitable_result_t<Awaitable>>; 513   awaitable_result_t<Awaitable>>;
520   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>) 514   if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
521   { 515   {
522   try 516   try
523   { 517   {
524   state->result_.emplace( 518   state->result_.emplace(
525   extract_io_payload(std::move(result))); 519   extract_io_payload(std::move(result)));
526   } 520   }
527   catch(...) 521   catch(...)
528   { 522   {
529   state->core_.set_winner_exception( 523   state->core_.set_winner_exception(
530   std::current_exception()); 524   std::current_exception());
531   } 525   }
532   } 526   }
533   } 527   }
534   } 528   }
535   else 529   else
536   { 530   {
537   state->record_error(result.ec); 531   state->record_error(result.ec);
538   } 532   }
HITCBC 539   108 } 533   104 }
540   534  
541   /** Launches all io_result-aware homogeneous runners concurrently. */ 535   /** Launches all io_result-aware homogeneous runners concurrently. */
542   template<IoAwaitableRange Range> 536   template<IoAwaitableRange Range>
543   class when_any_io_homogeneous_launcher 537   class when_any_io_homogeneous_launcher
544   { 538   {
545   using Awaitable = std::ranges::range_value_t<Range>; 539   using Awaitable = std::ranges::range_value_t<Range>;
546   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>; 540   using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
547   541  
548   Range* range_; 542   Range* range_;
549   when_any_io_homogeneous_state<PayloadT>* state_; 543   when_any_io_homogeneous_state<PayloadT>* state_;
550   544  
551   public: 545   public:
HITCBC 552   16 when_any_io_homogeneous_launcher( 546   15 when_any_io_homogeneous_launcher(
553   Range* range, 547   Range* range,
554   when_any_io_homogeneous_state<PayloadT>* state) 548   when_any_io_homogeneous_state<PayloadT>* state)
HITCBC 555   16 : range_(range) 549   15 : range_(range)
HITCBC 556   16 , state_(state) 550   15 , state_(state)
557   { 551   {
HITCBC 558   16 } 552   15 }
559   553  
HITCBC 560   16 bool await_ready() const noexcept 554   15 bool await_ready() const noexcept
561   { 555   {
HITCBC 562   16 return std::ranges::empty(*range_); 556   15 return std::ranges::empty(*range_);
563   } 557   }
564   558  
HITCBC 565   16 std::coroutine_handle<> await_suspend( 559   15 std::coroutine_handle<> await_suspend(
566   std::coroutine_handle<> continuation, io_env const* caller_env) 560   std::coroutine_handle<> continuation, io_env const* caller_env)
567   { 561   {
HITCBC 568   16 state_->core_.continuation_.h = continuation; 562   15 state_->core_.continuation_.h = continuation;
HITCBC 569   16 state_->core_.caller_env_ = caller_env; 563   15 state_->core_.caller_env_ = caller_env;
570   564  
HITCBC 571   16 if(caller_env->stop_token.stop_possible()) 565   15 if(caller_env->stop_token.stop_possible())
572   { 566   {
HITCBC 573   4 state_->core_.parent_stop_callback_.emplace( 567   4 state_->core_.parent_stop_callback_.emplace(
HITCBC 574   2 caller_env->stop_token, 568   2 caller_env->stop_token,
HITCBC 575   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_}); 569   2 when_any_core::stop_callback_fn{&state_->core_.stop_source_});
576   570  
HITCBC 577   2 if(caller_env->stop_token.stop_requested()) 571   2 if(caller_env->stop_token.stop_requested())
HITCBC 578   1 state_->core_.stop_source_.request_stop(); 572   1 state_->core_.stop_source_.request_stop();
579   } 573   }
580   574  
HITCBC 581   16 auto token = state_->core_.stop_source_.get_token(); 575   15 auto token = state_->core_.stop_source_.get_token();
582   576  
583   // Phase 1: Create all runners without dispatching. 577   // Phase 1: Create all runners without dispatching.
HITCBC 584   16 std::size_t index = 0; 578   15 std::size_t index = 0;
HITCBC 585   70 for(auto&& a : *range_) 579   67 for(auto&& a : *range_)
586   { 580   {
HITCBC 587   54 auto runner = make_when_any_io_homogeneous_runner( 581   52 auto runner = make_when_any_io_homogeneous_runner(
HITCBC 588   54 std::move(a), state_, index); 582   52 std::move(a), state_, index);
589   583  
HITCBC 590   54 auto h = runner.release(); 584   52 auto h = runner.release();
HITCBC 591   54 h.promise().state_ = state_; 585   52 h.promise().state_ = state_;
HITCBC 592   54 h.promise().index_ = index; 586   52 h.promise().index_ = index;
HITCBC 593   54 h.promise().env_ = io_env{caller_env->executor, token, 587   52 h.promise().env_ = io_env{caller_env->executor, token,
HITCBC 594   54 caller_env->frame_allocator}; 588   52 caller_env->frame_allocator};
595   589  
HITCBC 596   54 state_->runner_handles_[index].h = std::coroutine_handle<>{h}; 590   52 state_->runner_handles_[index].h = std::coroutine_handle<>{h};
HITCBC 597   54 ++index; 591   52 ++index;
598   } 592   }
599   593  
600   // Phase 2: Post all runners. Any may complete synchronously. 594   // Phase 2: Post all runners. Any may complete synchronously.
HITCBC 601   16 auto* handles = state_->runner_handles_.get(); 595   15 auto* handles = state_->runner_handles_.get();
HITCBC 602   16 std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed); 596   15 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) 597   67 for(std::size_t i = 0; i < count; ++i)
HITCBC 604   54 caller_env->executor.post(handles[i]); 598   52 caller_env->executor.post(handles[i]);
605   599  
HITCBC 606   32 return std::noop_coroutine(); 600   30 return std::noop_coroutine();
HITCBC 607   70 } 601   67 }
608   602  
HITCBC 609   16 void await_resume() const noexcept {} 603   15 void await_resume() const noexcept {}
610   }; 604   };
611   605  
612   } // namespace detail 606   } // namespace detail
613   607  
614   /** Race a range of io_result-returning awaitables (non-void payloads). 608   /** Race a range of io_result-returning awaitables (non-void payloads).
615   609  
616   Only a child returning !ec can win. Errors and exceptions do not 610   Only a child returning !ec can win. Errors and exceptions do not
617   claim winner status. If all children fail, the last failure 611   claim winner status. If all children fail, the last failure
618   is reported — either the last error_code at variant index 0, 612   is reported — either the last error_code at variant index 0,
619   or the last exception rethrown. 613   or the last exception rethrown.
620   614  
621   @param awaitables Range of io_result-returning awaitables (must 615   @param awaitables Range of io_result-returning awaitables (must
622   not be empty). 616   not be empty).
623   617  
624   @return A task yielding variant<error_code, pair<size_t, PayloadT>> 618   @return A task yielding variant<error_code, pair<size_t, PayloadT>>
625   where index 0 is failure and index 1 carries the winner's 619   where index 0 is failure and index 1 carries the winner's
626   index and payload. 620   index and payload.
627   621  
628   @throws std::invalid_argument if range is empty. 622   @throws std::invalid_argument if range is empty.
629   @throws Rethrows last exception when no winner and the last 623   @throws Rethrows last exception when no winner and the last
630   failure was an exception. 624   failure was an exception.
631   625  
632   @par Example 626   @par Example
633   @code 627   @code
634   task<void> example() 628   task<void> example()
635   { 629   {
636   std::vector<io_task<size_t>> reads; 630   std::vector<io_task<size_t>> reads;
637   for (auto& buf : buffers) 631   for (auto& buf : buffers)
638   reads.push_back(stream.read_some(buf)); 632   reads.push_back(stream.read_some(buf));
639   633  
640   auto result = co_await when_any(std::move(reads)); 634   auto result = co_await when_any(std::move(reads));
641   if (result.index() == 1) 635   if (result.index() == 1)
642   { 636   {
643   auto [idx, n] = std::get<1>(result); 637   auto [idx, n] = std::get<1>(result);
644   } 638   }
645   } 639   }
646   @endcode 640   @endcode
647   641  
648   @see IoAwaitableRange, when_any 642   @see IoAwaitableRange, when_any
649   */ 643   */
650   template<IoAwaitableRange R> 644   template<IoAwaitableRange R>
651   requires detail::is_io_result_v< 645   requires detail::is_io_result_v<
652   awaitable_result_t<std::ranges::range_value_t<R>>> 646   awaitable_result_t<std::ranges::range_value_t<R>>>
653   && (!std::is_same_v< 647   && (!std::is_same_v<
654   detail::io_result_payload_t< 648   detail::io_result_payload_t<
655   awaitable_result_t<std::ranges::range_value_t<R>>>, 649   awaitable_result_t<std::ranges::range_value_t<R>>>,
656   std::tuple<>>) 650   std::tuple<>>)
HITCBC 657   14 [[nodiscard]] auto when_any(R&& awaitables) 651   14 [[nodiscard]] auto when_any(R&& awaitables)
658   -> task<std::variant<std::error_code, 652   -> task<std::variant<std::error_code,
659   std::pair<std::size_t, 653   std::pair<std::size_t,
660   detail::io_result_payload_t< 654   detail::io_result_payload_t<
661   awaitable_result_t<std::ranges::range_value_t<R>>>>>> 655   awaitable_result_t<std::ranges::range_value_t<R>>>>>>
662   { 656   {
663   using Awaitable = std::ranges::range_value_t<R>; 657   using Awaitable = std::ranges::range_value_t<R>;
664   using PayloadT = detail::io_result_payload_t< 658   using PayloadT = detail::io_result_payload_t<
665   awaitable_result_t<Awaitable>>; 659   awaitable_result_t<Awaitable>>;
666   using result_type = std::variant<std::error_code, 660   using result_type = std::variant<std::error_code,
667   std::pair<std::size_t, PayloadT>>; 661   std::pair<std::size_t, PayloadT>>;
668   using OwnedRange = std::remove_cvref_t<R>; 662   using OwnedRange = std::remove_cvref_t<R>;
669   663  
670   auto count = std::ranges::size(awaitables); 664   auto count = std::ranges::size(awaitables);
671   if(count == 0) 665   if(count == 0)
672   throw std::invalid_argument("when_any requires at least one awaitable"); 666   throw std::invalid_argument("when_any requires at least one awaitable");
673   667  
674   OwnedRange owned_awaitables = std::forward<R>(awaitables); 668   OwnedRange owned_awaitables = std::forward<R>(awaitables);
675   669  
676   detail::when_any_io_homogeneous_state<PayloadT> state(count); 670   detail::when_any_io_homogeneous_state<PayloadT> state(count);
677   671  
678   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>( 672   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
679   &owned_awaitables, &state); 673   &owned_awaitables, &state);
680   674  
681   // Winner found 675   // Winner found
682   if(state.core_.has_winner_.load(std::memory_order_acquire)) 676   if(state.core_.has_winner_.load(std::memory_order_acquire))
683   { 677   {
684   if(state.core_.winner_exception_) 678   if(state.core_.winner_exception_)
685   std::rethrow_exception(state.core_.winner_exception_); 679   std::rethrow_exception(state.core_.winner_exception_);
686   co_return result_type{std::in_place_index<1>, 680   co_return result_type{std::in_place_index<1>,
687   std::pair{state.core_.winner_index_, std::move(*state.result_)}}; 681   std::pair{state.core_.winner_index_, std::move(*state.result_)}};
688   } 682   }
689   683  
690   // No winner — report last failure 684   // No winner — report last failure
691   if(state.last_exception_) 685   if(state.last_exception_)
692   std::rethrow_exception(state.last_exception_); 686   std::rethrow_exception(state.last_exception_);
693   co_return result_type{std::in_place_index<0>, state.last_error_}; 687   co_return result_type{std::in_place_index<0>, state.last_error_};
HITCBC 694   28 } 688   28 }
695   689  
696   /** Race a range of void io_result-returning awaitables. 690   /** Race a range of void io_result-returning awaitables.
697   691  
698   Only a child returning !ec can win. Returns the winner's index 692   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. 693   at variant index 1, or error_code at index 0 on all-fail.
700   694  
701   @param awaitables Range of io_result<>-returning awaitables (must 695   @param awaitables Range of io_result<>-returning awaitables (must
702   not be empty). 696   not be empty).
703   697  
704   @return A task yielding variant<error_code, size_t> where index 0 698   @return A task yielding variant<error_code, size_t> where index 0
705   is failure and index 1 carries the winner's index. 699   is failure and index 1 carries the winner's index.
706   700  
707   @throws std::invalid_argument if range is empty. 701   @throws std::invalid_argument if range is empty.
708   @throws Rethrows first exception when no winner and at least one 702   @throws Rethrows first exception when no winner and at least one
709   child threw. 703   child threw.
710   704  
711   @par Example 705   @par Example
712   @code 706   @code
713   task<void> example() 707   task<void> example()
714   { 708   {
715   std::vector<io_task<>> jobs; 709   std::vector<io_task<>> jobs;
716   jobs.push_back(background_work_a()); 710   jobs.push_back(background_work_a());
717   jobs.push_back(background_work_b()); 711   jobs.push_back(background_work_b());
718   712  
719   auto result = co_await when_any(std::move(jobs)); 713   auto result = co_await when_any(std::move(jobs));
720   if (result.index() == 1) 714   if (result.index() == 1)
721   { 715   {
722   auto winner = std::get<1>(result); 716   auto winner = std::get<1>(result);
723   } 717   }
724   } 718   }
725   @endcode 719   @endcode
726   720  
727   @see IoAwaitableRange, when_any 721   @see IoAwaitableRange, when_any
728   */ 722   */
729   template<IoAwaitableRange R> 723   template<IoAwaitableRange R>
730   requires detail::is_io_result_v< 724   requires detail::is_io_result_v<
731   awaitable_result_t<std::ranges::range_value_t<R>>> 725   awaitable_result_t<std::ranges::range_value_t<R>>>
732   && std::is_same_v< 726   && std::is_same_v<
733   detail::io_result_payload_t< 727   detail::io_result_payload_t<
734   awaitable_result_t<std::ranges::range_value_t<R>>>, 728   awaitable_result_t<std::ranges::range_value_t<R>>>,
735   std::tuple<>> 729   std::tuple<>>
HITCBC 736   3 [[nodiscard]] auto when_any(R&& awaitables) 730   2 [[nodiscard]] auto when_any(R&& awaitables)
737   -> task<std::variant<std::error_code, std::size_t>> 731   -> task<std::variant<std::error_code, std::size_t>>
738   { 732   {
739   using OwnedRange = std::remove_cvref_t<R>; 733   using OwnedRange = std::remove_cvref_t<R>;
740   using result_type = std::variant<std::error_code, std::size_t>; 734   using result_type = std::variant<std::error_code, std::size_t>;
741   735  
742   auto count = std::ranges::size(awaitables); 736   auto count = std::ranges::size(awaitables);
743   if(count == 0) 737   if(count == 0)
744   throw std::invalid_argument("when_any requires at least one awaitable"); 738   throw std::invalid_argument("when_any requires at least one awaitable");
745   739  
746   OwnedRange owned_awaitables = std::forward<R>(awaitables); 740   OwnedRange owned_awaitables = std::forward<R>(awaitables);
747   741  
748   detail::when_any_io_homogeneous_state<std::tuple<>> state(count); 742   detail::when_any_io_homogeneous_state<std::tuple<>> state(count);
749   743  
750   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>( 744   co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
751   &owned_awaitables, &state); 745   &owned_awaitables, &state);
752   746  
753   // Winner found 747   // Winner found
754   if(state.core_.has_winner_.load(std::memory_order_acquire)) 748   if(state.core_.has_winner_.load(std::memory_order_acquire))
755   { 749   {
756   if(state.core_.winner_exception_) 750   if(state.core_.winner_exception_)
757   std::rethrow_exception(state.core_.winner_exception_); 751   std::rethrow_exception(state.core_.winner_exception_);
758   co_return result_type{std::in_place_index<1>, 752   co_return result_type{std::in_place_index<1>,
759   state.core_.winner_index_}; 753   state.core_.winner_index_};
760   } 754   }
761   755  
762   // No winner — report last failure 756   // No winner — report last failure
763   if(state.last_exception_) 757   if(state.last_exception_)
764   std::rethrow_exception(state.last_exception_); 758   std::rethrow_exception(state.last_exception_);
765   co_return result_type{std::in_place_index<0>, state.last_error_}; 759   co_return result_type{std::in_place_index<0>, state.last_error_};
HITCBC 766   6 } 760   4 }
767   761  
768   /** Race io_result-returning awaitables, selecting the first success. 762   /** Race io_result-returning awaitables, selecting the first success.
769   763  
770   Overload selected when all children return io_result<Ts...>. 764   Overload selected when all children return io_result<Ts...>.
771   Only a child returning !ec can win. Errors and exceptions do 765   Only a child returning !ec can win. Errors and exceptions do
772   not claim winner status. 766   not claim winner status.
773   767  
774   @return A task yielding variant<error_code, R1, ..., Rn> where 768   @return A task yielding variant<error_code, R1, ..., Rn> where
775   index 0 is the failure/no-winner case and index i+1 769   index 0 is the failure/no-winner case and index i+1
776   identifies the winning child. 770   identifies the winning child.
777   */ 771   */
778   template<IoAwaitable... As> 772   template<IoAwaitable... As>
779   requires (sizeof...(As) > 0) 773   requires (sizeof...(As) > 0)
780   && detail::all_io_result_awaitables<As...> 774   && detail::all_io_result_awaitables<As...>
HITCBC 781   18 [[nodiscard]] auto when_any(As... as) 775   16 [[nodiscard]] auto when_any(As... as)
782   -> task<std::variant< 776   -> task<std::variant<
783   std::error_code, 777   std::error_code,
784   detail::io_result_payload_t<awaitable_result_t<As>>...>> 778   detail::io_result_payload_t<awaitable_result_t<As>>...>>
785   { 779   {
786   using result_type = std::variant< 780   using result_type = std::variant<
787   std::error_code, 781   std::error_code,
788   detail::io_result_payload_t<awaitable_result_t<As>>...>; 782   detail::io_result_payload_t<awaitable_result_t<As>>...>;
789   783  
790   detail::when_any_io_state< 784   detail::when_any_io_state<
791   detail::io_result_payload_t<awaitable_result_t<As>>...> state; 785   detail::io_result_payload_t<awaitable_result_t<As>>...> state;
792   std::tuple<As...> awaitable_tuple(std::move(as)...); 786   std::tuple<As...> awaitable_tuple(std::move(as)...);
793   787  
794   co_await detail::when_any_io_launcher<As...>( 788   co_await detail::when_any_io_launcher<As...>(
795   &awaitable_tuple, &state); 789   &awaitable_tuple, &state);
796   790  
797   // Winner found: return their result 791   // Winner found: return their result
798   if(state.result_.has_value()) 792   if(state.result_.has_value())
799   co_return std::move(*state.result_); 793   co_return std::move(*state.result_);
800   794  
801   // Winner claimed but payload construction failed 795   // Winner claimed but payload construction failed
802   if(state.core_.winner_exception_) 796   if(state.core_.winner_exception_)
803   std::rethrow_exception(state.core_.winner_exception_); 797   std::rethrow_exception(state.core_.winner_exception_);
804   798  
805   // No winner — report last failure 799   // No winner — report last failure
806   if(state.last_exception_) 800   if(state.last_exception_)
807   std::rethrow_exception(state.last_exception_); 801   std::rethrow_exception(state.last_exception_);
808   co_return result_type{std::in_place_index<0>, state.last_error_}; 802   co_return result_type{std::in_place_index<0>, state.last_error_};
HITCBC 809   36 } 803   32 }
810   804  
811   } // namespace capy 805   } // namespace capy
812   } // namespace boost 806   } // namespace boost
813   807  
814   #endif 808   #endif