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