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_allocator.hpp>
22  
#include <boost/capy/ex/frame_allocator.hpp>
22  
#include <boost/capy/ex/io_env.hpp>
23  
#include <boost/capy/ex/io_env.hpp>
23  
#include <boost/capy/task.hpp>
24  
#include <boost/capy/task.hpp>
24  

25  

25  
#include <array>
26  
#include <array>
26  
#include <atomic>
27  
#include <atomic>
27  
#include <exception>
28  
#include <exception>
28  
#include <memory>
29  
#include <memory>
29  
#include <mutex>
30  
#include <mutex>
30  
#include <optional>
31  
#include <optional>
31  
#include <ranges>
32  
#include <ranges>
32  
#include <stdexcept>
33  
#include <stdexcept>
33  
#include <stop_token>
34  
#include <stop_token>
34  
#include <tuple>
35  
#include <tuple>
35  
#include <type_traits>
36  
#include <type_traits>
36  
#include <utility>
37  
#include <utility>
37  
#include <variant>
38  
#include <variant>
38  
#include <vector>
39  
#include <vector>
39  

40  

40  
/*
41  
/*
41  
   when_any - Race multiple io_result tasks, select first success
42  
   when_any - Race multiple io_result tasks, select first success
42  
   =============================================================
43  
   =============================================================
43  

44  

44  
   OVERVIEW:
45  
   OVERVIEW:
45  
   ---------
46  
   ---------
46  
   when_any launches N io_result-returning tasks concurrently. A task
47  
   when_any launches N io_result-returning tasks concurrently. A task
47  
   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
48  
   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
49  
   payload is returned. If no winner exists (all fail), the first
50  
   payload is returned. If no winner exists (all fail), the first
50  
   error_code is returned or the last exception is rethrown.
51  
   error_code is returned or the last exception is rethrown.
51  

52  

52  
   ARCHITECTURE:
53  
   ARCHITECTURE:
53  
   -------------
54  
   -------------
54  
   The design mirrors when_all but with inverted completion semantics:
55  
   The design mirrors when_all but with inverted completion semantics:
55  

56  

56  
     when_all:  complete when remaining_count reaches 0 (all done)
57  
     when_all:  complete when remaining_count reaches 0 (all done)
57  
     when_any:  complete when has_winner becomes true (first done)
58  
     when_any:  complete when has_winner becomes true (first done)
58  
                BUT still wait for remaining_count to reach 0 for cleanup
59  
                BUT still wait for remaining_count to reach 0 for cleanup
59  

60  

60  
   Key components:
61  
   Key components:
61  
     - when_any_core:    Shared state tracking winner and completion
62  
     - when_any_core:    Shared state tracking winner and completion
62  
     - when_any_io_runner: Wrapper coroutine for each child task
63  
     - when_any_io_runner: Wrapper coroutine for each child task
63  
     - when_any_io_launcher/when_any_io_homogeneous_launcher:
64  
     - when_any_io_launcher/when_any_io_homogeneous_launcher:
64  
                          Awaitables that start all runners concurrently
65  
                          Awaitables that start all runners concurrently
65  

66  

66  
   CRITICAL INVARIANTS:
67  
   CRITICAL INVARIANTS:
67  
   --------------------
68  
   --------------------
68  
   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)
69  
   2. All tasks must complete before parent resumes (cleanup safety)
70  
   2. All tasks must complete before parent resumes (cleanup safety)
70  
   3. Stop is requested immediately when winner is determined
71  
   3. Stop is requested immediately when winner is determined
71  
   4. Exceptions and errors do not claim winner status
72  
   4. Exceptions and errors do not claim winner status
72  

73  

73  
   POSITIONAL VARIANT:
74  
   POSITIONAL VARIANT:
74  
   -------------------
75  
   -------------------
75  
   The variadic overload returns std::variant<error_code, R1, R2, ..., Rn>.
76  
   The variadic overload returns std::variant<error_code, R1, R2, ..., Rn>.
76  
   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
77  
   winning child and carries its payload.
78  
   winning child and carries its payload.
78  

79  

79  
   RANGE OVERLOAD:
80  
   RANGE OVERLOAD:
80  
   ---------------
81  
   ---------------
81  
   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
82  
   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.
83  

84  

84  
   MEMORY MODEL:
85  
   MEMORY MODEL:
85  
   -------------
86  
   -------------
86  
   Synchronization chain from winner's write to parent's read:
87  
   Synchronization chain from winner's write to parent's read:
87  

88  

88  
   1. Winner thread writes result_ (non-atomic)
89  
   1. Winner thread writes result_ (non-atomic)
89  
   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_
90  
   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()
91  
      -> fetch_sub(acq_rel) on remaining_count_, observing count becomes 0
92  
      -> fetch_sub(acq_rel) on remaining_count_, observing count becomes 0
92  
   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer
93  
   4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer
93  
   5. Parent coroutine resumes and reads result_
94  
   5. Parent coroutine resumes and reads result_
94  

95  

95  
   Synchronization analysis:
96  
   Synchronization analysis:
96  
   - All fetch_sub operations on remaining_count_ form a release sequence
97  
   - All fetch_sub operations on remaining_count_ form a release sequence
97  
   - Winner's fetch_sub releases; subsequent fetch_sub operations participate
98  
   - Winner's fetch_sub releases; subsequent fetch_sub operations participate
98  
     in the modification order of remaining_count_
99  
     in the modification order of remaining_count_
99  
   - 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
100  
     modification order, establishing happens-before from winner's writes
101  
     modification order, establishing happens-before from winner's writes
101  
   - Executor dispatch() is expected to provide queue-based synchronization
102  
   - Executor dispatch() is expected to provide queue-based synchronization
102  
     (release-on-post, acquire-on-execute) completing the chain to parent
103  
     (release-on-post, acquire-on-execute) completing the chain to parent
103  
   - Even inline executors work (same thread = sequenced-before)
104  
   - Even inline executors work (same thread = sequenced-before)
104  

105  

105  
   EXCEPTION SEMANTICS:
106  
   EXCEPTION SEMANTICS:
106  
   --------------------
107  
   --------------------
107  
   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
108  
   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
109  
   all children complete without a winner does the combinator check: if
110  
   all children complete without a winner does the combinator check: if
110  
   any exception was recorded, it is rethrown (exception beats error_code).
111  
   any exception was recorded, it is rethrown (exception beats error_code).
111  
*/
112  
*/
112  

113  

113  
namespace boost {
114  
namespace boost {
114  
namespace capy {
115  
namespace capy {
115  

116  

116  
namespace detail {
117  
namespace detail {
117  

118  

118  
/** Core shared state for when_any operations.
119  
/** Core shared state for when_any operations.
119  

120  

120  
    Contains all members and methods common to both heterogeneous (variadic)
121  
    Contains all members and methods common to both heterogeneous (variadic)
121  
    and homogeneous (range) when_any implementations. State classes embed
122  
    and homogeneous (range) when_any implementations. State classes embed
122  
    this via composition to avoid CRTP destructor ordering issues.
123  
    this via composition to avoid CRTP destructor ordering issues.
123  

124  

124  
    @par Thread Safety
125  
    @par Thread Safety
125  
    Atomic operations protect winner selection and completion count.
126  
    Atomic operations protect winner selection and completion count.
126  
*/
127  
*/
127  
struct when_any_core
128  
struct when_any_core
128  
{
129  
{
129  
    std::atomic<std::size_t> remaining_count_;
130  
    std::atomic<std::size_t> remaining_count_;
130  
    std::size_t winner_index_{0};
131  
    std::size_t winner_index_{0};
131  
    std::exception_ptr winner_exception_;
132  
    std::exception_ptr winner_exception_;
132  
    std::stop_source stop_source_;
133  
    std::stop_source stop_source_;
133  

134  

134  
    // Bridges parent's stop token to our stop_source
135  
    // Bridges parent's stop token to our stop_source
135  
    struct stop_callback_fn
136  
    struct stop_callback_fn
136  
    {
137  
    {
137  
        std::stop_source* source_;
138  
        std::stop_source* source_;
138  
        void operator()() const noexcept { source_->request_stop(); }
139  
        void operator()() const noexcept { source_->request_stop(); }
139  
    };
140  
    };
140  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
141  
    using stop_callback_t = std::stop_callback<stop_callback_fn>;
141  
    std::optional<stop_callback_t> parent_stop_callback_;
142  
    std::optional<stop_callback_t> parent_stop_callback_;
142  

143  

143  
    continuation continuation_;
144  
    continuation continuation_;
144  
    io_env const* caller_env_ = nullptr;
145  
    io_env const* caller_env_ = nullptr;
145  

146  

146  
    // 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)
147  
    std::atomic<bool> has_winner_{false};
148  
    std::atomic<bool> has_winner_{false};
148  

149  

149  
    explicit when_any_core(std::size_t count) noexcept
150  
    explicit when_any_core(std::size_t count) noexcept
150  
        : remaining_count_(count)
151  
        : remaining_count_(count)
151  
    {
152  
    {
152  
    }
153  
    }
153  

154  

154  
    /** Atomically claim winner status; exactly one task succeeds. */
155  
    /** Atomically claim winner status; exactly one task succeeds. */
155  
    bool try_win(std::size_t index) noexcept
156  
    bool try_win(std::size_t index) noexcept
156  
    {
157  
    {
157  
        bool expected = false;
158  
        bool expected = false;
158  
        if(has_winner_.compare_exchange_strong(
159  
        if(has_winner_.compare_exchange_strong(
159  
            expected, true, std::memory_order_acq_rel))
160  
            expected, true, std::memory_order_acq_rel))
160  
        {
161  
        {
161  
            winner_index_ = index;
162  
            winner_index_ = index;
162  
            stop_source_.request_stop();
163  
            stop_source_.request_stop();
163  
            return true;
164  
            return true;
164  
        }
165  
        }
165  
        return false;
166  
        return false;
166  
    }
167  
    }
167  

168  

168  
    /** @pre try_win() returned true. */
169  
    /** @pre try_win() returned true. */
169  
    void set_winner_exception(std::exception_ptr ep) noexcept
170  
    void set_winner_exception(std::exception_ptr ep) noexcept
170  
    {
171  
    {
171  
        winner_exception_ = ep;
172  
        winner_exception_ = ep;
172  
    }
173  
    }
173  

174  

174  
    // Runners signal completion directly via final_suspend; no member function needed.
175  
    // Runners signal completion directly via final_suspend; no member function needed.
175  
};
176  
};
176  

177  

177  
} // namespace detail
178  
} // namespace detail
178  

179  

179  
namespace detail {
180  
namespace detail {
180  

181  

181  
// State for io_result-aware when_any: only !ec wins.
182  
// State for io_result-aware when_any: only !ec wins.
182  
template<typename... Ts>
183  
template<typename... Ts>
183  
struct when_any_io_state
184  
struct when_any_io_state
184  
{
185  
{
185  
    static constexpr std::size_t task_count = sizeof...(Ts);
186  
    static constexpr std::size_t task_count = sizeof...(Ts);
186  
    using variant_type = std::variant<std::error_code, Ts...>;
187  
    using variant_type = std::variant<std::error_code, Ts...>;
187  

188  

188  
    when_any_core core_;
189  
    when_any_core core_;
189  
    std::optional<variant_type> result_;
190  
    std::optional<variant_type> result_;
190  
    std::array<continuation, task_count> runner_handles_{};
191  
    std::array<continuation, task_count> runner_handles_{};
191  

192  

192  
    // Last failure (error or exception) for the all-fail case.
193  
    // Last failure (error or exception) for the all-fail case.
193  
    // Last writer wins — no priority between errors and exceptions.
194  
    // Last writer wins — no priority between errors and exceptions.
194  
    std::mutex failure_mu_;
195  
    std::mutex failure_mu_;
195  
    std::error_code last_error_;
196  
    std::error_code last_error_;
196  
    std::exception_ptr last_exception_;
197  
    std::exception_ptr last_exception_;
197  

198  

198  
    when_any_io_state()
199  
    when_any_io_state()
199  
        : core_(task_count)
200  
        : core_(task_count)
200  
    {
201  
    {
201  
    }
202  
    }
202  

203  

203  
    void record_error(std::error_code ec)
204  
    void record_error(std::error_code ec)
204  
    {
205  
    {
205  
        std::lock_guard lk(failure_mu_);
206  
        std::lock_guard lk(failure_mu_);
206  
        last_error_ = ec;
207  
        last_error_ = ec;
207  
        last_exception_ = nullptr;
208  
        last_exception_ = nullptr;
208  
    }
209  
    }
209  

210  

210  
    void record_exception(std::exception_ptr ep)
211  
    void record_exception(std::exception_ptr ep)
211  
    {
212  
    {
212  
        std::lock_guard lk(failure_mu_);
213  
        std::lock_guard lk(failure_mu_);
213  
        last_exception_ = ep;
214  
        last_exception_ = ep;
214  
        last_error_ = {};
215  
        last_error_ = {};
215  
    }
216  
    }
216  
};
217  
};
217  

218  

218  
// Wrapper coroutine for io_result-aware when_any children.
219  
// Wrapper coroutine for io_result-aware when_any children.
219  
// unhandled_exception records the exception but does NOT claim winner status.
220  
// unhandled_exception records the exception but does NOT claim winner status.
220  
template<typename StateType>
221  
template<typename StateType>
221 -
struct when_any_io_runner
222 +
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE when_any_io_runner
222  
{
223  
{
223  
    struct promise_type
224  
    struct promise_type
 
225 +
        : frame_alloc_mixin
224  
    {
226  
    {
225  
        StateType* state_ = nullptr;
227  
        StateType* state_ = nullptr;
226  
        std::size_t index_ = 0;
228  
        std::size_t index_ = 0;
227  
        io_env env_;
229  
        io_env env_;
228  

230  

229  
        when_any_io_runner get_return_object() noexcept
231  
        when_any_io_runner get_return_object() noexcept
230  
        {
232  
        {
231  
            return when_any_io_runner(
233  
            return when_any_io_runner(
232  
                std::coroutine_handle<promise_type>::from_promise(*this));
234  
                std::coroutine_handle<promise_type>::from_promise(*this));
233  
        }
235  
        }
234  

236  

235  
        std::suspend_always initial_suspend() noexcept { return {}; }
237  
        std::suspend_always initial_suspend() noexcept { return {}; }
236  

238  

237  
        auto final_suspend() noexcept
239  
        auto final_suspend() noexcept
238  
        {
240  
        {
239  
            struct awaiter
241  
            struct awaiter
240  
            {
242  
            {
241  
                promise_type* p_;
243  
                promise_type* p_;
242  
                bool await_ready() const noexcept { return false; }
244  
                bool await_ready() const noexcept { return false; }
243  
                auto await_suspend(std::coroutine_handle<> h) noexcept
245  
                auto await_suspend(std::coroutine_handle<> h) noexcept
244  
                {
246  
                {
245  
                    auto& core = p_->state_->core_;
247  
                    auto& core = p_->state_->core_;
246  
                    auto* counter = &core.remaining_count_;
248  
                    auto* counter = &core.remaining_count_;
247  
                    auto* caller_env = core.caller_env_;
249  
                    auto* caller_env = core.caller_env_;
248  
                    auto& cont = core.continuation_;
250  
                    auto& cont = core.continuation_;
249  

251  

250  
                    h.destroy();
252  
                    h.destroy();
251  

253  

252  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
254  
                    auto remaining = counter->fetch_sub(1, std::memory_order_acq_rel);
253  
                    if(remaining == 1)
255  
                    if(remaining == 1)
254  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
256  
                        return detail::symmetric_transfer(caller_env->executor.dispatch(cont));
255  
                    return detail::symmetric_transfer(std::noop_coroutine());
257  
                    return detail::symmetric_transfer(std::noop_coroutine());
256  
                }
258  
                }
257  
                void await_resume() const noexcept {}
259  
                void await_resume() const noexcept {}
258  
            };
260  
            };
259  
            return awaiter{this};
261  
            return awaiter{this};
260  
        }
262  
        }
261  

263  

262  
        void return_void() noexcept {}
264  
        void return_void() noexcept {}
263  

265  

264  
        // Exceptions do NOT win in io_result when_any
266  
        // Exceptions do NOT win in io_result when_any
265 -
        void unhandled_exception()
267 +
        void unhandled_exception() noexcept
266  
        {
268  
        {
267  
            state_->record_exception(std::current_exception());
269  
            state_->record_exception(std::current_exception());
268  
        }
270  
        }
269  

271  

270  
        template<class Awaitable>
272  
        template<class Awaitable>
271  
        struct transform_awaiter
273  
        struct transform_awaiter
272  
        {
274  
        {
273  
            std::decay_t<Awaitable> a_;
275  
            std::decay_t<Awaitable> a_;
274  
            promise_type* p_;
276  
            promise_type* p_;
275  

277  

276  
            bool await_ready() { return a_.await_ready(); }
278  
            bool await_ready() { return a_.await_ready(); }
277  
            decltype(auto) await_resume() { return a_.await_resume(); }
279  
            decltype(auto) await_resume() { return a_.await_resume(); }
278  

280  

279  
            template<class Promise>
281  
            template<class Promise>
280  
            auto await_suspend(std::coroutine_handle<Promise> h)
282  
            auto await_suspend(std::coroutine_handle<Promise> h)
281  
            {
283  
            {
282  
                using R = decltype(a_.await_suspend(h, &p_->env_));
284  
                using R = decltype(a_.await_suspend(h, &p_->env_));
283  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
285  
                if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
284  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
286  
                    return detail::symmetric_transfer(a_.await_suspend(h, &p_->env_));
285  
                else
287  
                else
286  
                    return a_.await_suspend(h, &p_->env_);
288  
                    return a_.await_suspend(h, &p_->env_);
287  
            }
289  
            }
288  
        };
290  
        };
289  

291  

290  
        template<class Awaitable>
292  
        template<class Awaitable>
291  
        auto await_transform(Awaitable&& a)
293  
        auto await_transform(Awaitable&& a)
292  
        {
294  
        {
293  
            using A = std::decay_t<Awaitable>;
295  
            using A = std::decay_t<Awaitable>;
294  
            if constexpr (IoAwaitable<A>)
296  
            if constexpr (IoAwaitable<A>)
295  
            {
297  
            {
296  
                return transform_awaiter<Awaitable>{
298  
                return transform_awaiter<Awaitable>{
297  
                    std::forward<Awaitable>(a), this};
299  
                    std::forward<Awaitable>(a), this};
298  
            }
300  
            }
299  
            else
301  
            else
300  
            {
302  
            {
301  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
303  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
302  
            }
304  
            }
303  
        }
305  
        }
304  
    };
306  
    };
305  

307  

306  
    std::coroutine_handle<promise_type> h_;
308  
    std::coroutine_handle<promise_type> h_;
307  

309  

308  
    explicit when_any_io_runner(std::coroutine_handle<promise_type> h) noexcept
310  
    explicit when_any_io_runner(std::coroutine_handle<promise_type> h) noexcept
309  
        : h_(h)
311  
        : h_(h)
310  
    {
312  
    {
311  
    }
313  
    }
312  

314  

313  
    when_any_io_runner(when_any_io_runner&& other) noexcept
315  
    when_any_io_runner(when_any_io_runner&& other) noexcept
314  
        : h_(std::exchange(other.h_, nullptr))
316  
        : h_(std::exchange(other.h_, nullptr))
315  
    {
317  
    {
316  
    }
318  
    }
317  

319  

318  
    when_any_io_runner(when_any_io_runner const&) = delete;
320  
    when_any_io_runner(when_any_io_runner const&) = delete;
319  
    when_any_io_runner& operator=(when_any_io_runner const&) = delete;
321  
    when_any_io_runner& operator=(when_any_io_runner const&) = delete;
320  
    when_any_io_runner& operator=(when_any_io_runner&&) = delete;
322  
    when_any_io_runner& operator=(when_any_io_runner&&) = delete;
321  

323  

322  
    auto release() noexcept
324  
    auto release() noexcept
323  
    {
325  
    {
324  
        return std::exchange(h_, nullptr);
326  
        return std::exchange(h_, nullptr);
325  
    }
327  
    }
326  
};
328  
};
327  

329  

328  
// Runner coroutine: only tries to win when the child returns !ec.
330  
// Runner coroutine: only tries to win when the child returns !ec.
329  
template<std::size_t I, IoAwaitable Awaitable, typename StateType>
331  
template<std::size_t I, IoAwaitable Awaitable, typename StateType>
330  
when_any_io_runner<StateType>
332  
when_any_io_runner<StateType>
331  
make_when_any_io_runner(Awaitable inner, StateType* state)
333  
make_when_any_io_runner(Awaitable inner, StateType* state)
332  
{
334  
{
333  
    auto result = co_await std::move(inner);
335  
    auto result = co_await std::move(inner);
334  

336  

335  
    if(!result.ec)
337  
    if(!result.ec)
336  
    {
338  
    {
337  
        // Success: try to claim winner
339  
        // Success: try to claim winner
338  
        if(state->core_.try_win(I))
340  
        if(state->core_.try_win(I))
339  
        {
341  
        {
340  
            try
342  
            try
341  
            {
343  
            {
342  
                state->result_.emplace(
344  
                state->result_.emplace(
343  
                    std::in_place_index<I + 1>,
345  
                    std::in_place_index<I + 1>,
344  
                    detail::extract_io_payload(std::move(result)));
346  
                    detail::extract_io_payload(std::move(result)));
345  
            }
347  
            }
346  
            catch(...)
348  
            catch(...)
347  
            {
349  
            {
348  
                state->core_.set_winner_exception(std::current_exception());
350  
                state->core_.set_winner_exception(std::current_exception());
349  
            }
351  
            }
350  
        }
352  
        }
351  
    }
353  
    }
352  
    else
354  
    else
353  
    {
355  
    {
354  
        // Error: record but don't win
356  
        // Error: record but don't win
355  
        state->record_error(result.ec);
357  
        state->record_error(result.ec);
356  
    }
358  
    }
357  
}
359  
}
358  

360  

359  
// Launcher for io_result-aware when_any.
361  
// Launcher for io_result-aware when_any.
360  
template<IoAwaitable... Awaitables>
362  
template<IoAwaitable... Awaitables>
361  
class when_any_io_launcher
363  
class when_any_io_launcher
362  
{
364  
{
363  
    using state_type = when_any_io_state<
365  
    using state_type = when_any_io_state<
364  
        io_result_payload_t<awaitable_result_t<Awaitables>>...>;
366  
        io_result_payload_t<awaitable_result_t<Awaitables>>...>;
365  

367  

366  
    std::tuple<Awaitables...>* tasks_;
368  
    std::tuple<Awaitables...>* tasks_;
367  
    state_type* state_;
369  
    state_type* state_;
368  

370  

369  
public:
371  
public:
370  
    when_any_io_launcher(
372  
    when_any_io_launcher(
371  
        std::tuple<Awaitables...>* tasks,
373  
        std::tuple<Awaitables...>* tasks,
372  
        state_type* state)
374  
        state_type* state)
373  
        : tasks_(tasks)
375  
        : tasks_(tasks)
374  
        , state_(state)
376  
        , state_(state)
375  
    {
377  
    {
376  
    }
378  
    }
377  

379  

378  
    bool await_ready() const noexcept
380  
    bool await_ready() const noexcept
379  
    {
381  
    {
380  
        return sizeof...(Awaitables) == 0;
382  
        return sizeof...(Awaitables) == 0;
381  
    }
383  
    }
382  

384  

383  
    std::coroutine_handle<> await_suspend(
385  
    std::coroutine_handle<> await_suspend(
384  
        std::coroutine_handle<> continuation, io_env const* caller_env)
386  
        std::coroutine_handle<> continuation, io_env const* caller_env)
385  
    {
387  
    {
386  
        state_->core_.continuation_.h = continuation;
388  
        state_->core_.continuation_.h = continuation;
387  
        state_->core_.caller_env_ = caller_env;
389  
        state_->core_.caller_env_ = caller_env;
388  

390  

389  
        if(caller_env->stop_token.stop_possible())
391  
        if(caller_env->stop_token.stop_possible())
390  
        {
392  
        {
391  
            state_->core_.parent_stop_callback_.emplace(
393  
            state_->core_.parent_stop_callback_.emplace(
392  
                caller_env->stop_token,
394  
                caller_env->stop_token,
393  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
395  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
394  

396  

395  
            if(caller_env->stop_token.stop_requested())
397  
            if(caller_env->stop_token.stop_requested())
396  
                state_->core_.stop_source_.request_stop();
398  
                state_->core_.stop_source_.request_stop();
397  
        }
399  
        }
398  

400  

399  
        auto token = state_->core_.stop_source_.get_token();
401  
        auto token = state_->core_.stop_source_.get_token();
400  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
402  
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
401  
            (..., launch_one<Is>(caller_env->executor, token));
403  
            (..., launch_one<Is>(caller_env->executor, token));
402  
        }(std::index_sequence_for<Awaitables...>{});
404  
        }(std::index_sequence_for<Awaitables...>{});
403  

405  

404  
        return std::noop_coroutine();
406  
        return std::noop_coroutine();
405  
    }
407  
    }
406  

408  

407  
    void await_resume() const noexcept {}
409  
    void await_resume() const noexcept {}
408  

410  

409  
private:
411  
private:
410  
    template<std::size_t I>
412  
    template<std::size_t I>
411  
    void launch_one(executor_ref caller_ex, std::stop_token token)
413  
    void launch_one(executor_ref caller_ex, std::stop_token token)
412  
    {
414  
    {
413  
        auto runner = make_when_any_io_runner<I>(
415  
        auto runner = make_when_any_io_runner<I>(
414  
            std::move(std::get<I>(*tasks_)), state_);
416  
            std::move(std::get<I>(*tasks_)), state_);
415  

417  

416  
        auto h = runner.release();
418  
        auto h = runner.release();
417  
        h.promise().state_ = state_;
419  
        h.promise().state_ = state_;
418  
        h.promise().index_ = I;
420  
        h.promise().index_ = I;
419  
        h.promise().env_ = io_env{caller_ex, token,
421  
        h.promise().env_ = io_env{caller_ex, token,
420  
            state_->core_.caller_env_->frame_allocator};
422  
            state_->core_.caller_env_->frame_allocator};
421  

423  

422  
        state_->runner_handles_[I].h = std::coroutine_handle<>{h};
424  
        state_->runner_handles_[I].h = std::coroutine_handle<>{h};
423  
        caller_ex.post(state_->runner_handles_[I]);
425  
        caller_ex.post(state_->runner_handles_[I]);
424  
    }
426  
    }
425  
};
427  
};
426  

428  

427  
/** Shared state for homogeneous io_result-aware when_any (range overload).
429  
/** Shared state for homogeneous io_result-aware when_any (range overload).
428  

430  

429  
    @tparam T The payload type extracted from io_result.
431  
    @tparam T The payload type extracted from io_result.
430  
*/
432  
*/
431  
template<typename T>
433  
template<typename T>
432  
struct when_any_io_homogeneous_state
434  
struct when_any_io_homogeneous_state
433  
{
435  
{
434  
    when_any_core core_;
436  
    when_any_core core_;
435  
    std::optional<T> result_;
437  
    std::optional<T> result_;
436  
    std::unique_ptr<continuation[]> runner_handles_;
438  
    std::unique_ptr<continuation[]> runner_handles_;
437  

439  

438  
    std::mutex failure_mu_;
440  
    std::mutex failure_mu_;
439  
    std::error_code last_error_;
441  
    std::error_code last_error_;
440  
    std::exception_ptr last_exception_;
442  
    std::exception_ptr last_exception_;
441  

443  

442  
    explicit when_any_io_homogeneous_state(std::size_t count)
444  
    explicit when_any_io_homogeneous_state(std::size_t count)
443  
        : core_(count)
445  
        : core_(count)
444  
        , runner_handles_(std::make_unique<continuation[]>(count))
446  
        , runner_handles_(std::make_unique<continuation[]>(count))
445  
    {
447  
    {
446  
    }
448  
    }
447  

449  

448  
    void record_error(std::error_code ec)
450  
    void record_error(std::error_code ec)
449  
    {
451  
    {
450  
        std::lock_guard lk(failure_mu_);
452  
        std::lock_guard lk(failure_mu_);
451  
        last_error_ = ec;
453  
        last_error_ = ec;
452  
        last_exception_ = nullptr;
454  
        last_exception_ = nullptr;
453  
    }
455  
    }
454  

456  

455  
    void record_exception(std::exception_ptr ep)
457  
    void record_exception(std::exception_ptr ep)
456  
    {
458  
    {
457  
        std::lock_guard lk(failure_mu_);
459  
        std::lock_guard lk(failure_mu_);
458  
        last_exception_ = ep;
460  
        last_exception_ = ep;
459  
        last_error_ = {};
461  
        last_error_ = {};
460  
    }
462  
    }
461  
};
463  
};
462  

464  

463  
/** Specialization for void io_result children (no payload storage). */
465  
/** Specialization for void io_result children (no payload storage). */
464  
template<>
466  
template<>
465  
struct when_any_io_homogeneous_state<std::tuple<>>
467  
struct when_any_io_homogeneous_state<std::tuple<>>
466  
{
468  
{
467  
    when_any_core core_;
469  
    when_any_core core_;
468  
    std::unique_ptr<continuation[]> runner_handles_;
470  
    std::unique_ptr<continuation[]> runner_handles_;
469  

471  

470  
    std::mutex failure_mu_;
472  
    std::mutex failure_mu_;
471  
    std::error_code last_error_;
473  
    std::error_code last_error_;
472  
    std::exception_ptr last_exception_;
474  
    std::exception_ptr last_exception_;
473  

475  

474  
    explicit when_any_io_homogeneous_state(std::size_t count)
476  
    explicit when_any_io_homogeneous_state(std::size_t count)
475  
        : core_(count)
477  
        : core_(count)
476  
        , runner_handles_(std::make_unique<continuation[]>(count))
478  
        , runner_handles_(std::make_unique<continuation[]>(count))
477  
    {
479  
    {
478  
    }
480  
    }
479  

481  

480  
    void record_error(std::error_code ec)
482  
    void record_error(std::error_code ec)
481  
    {
483  
    {
482  
        std::lock_guard lk(failure_mu_);
484  
        std::lock_guard lk(failure_mu_);
483  
        last_error_ = ec;
485  
        last_error_ = ec;
484  
        last_exception_ = nullptr;
486  
        last_exception_ = nullptr;
485  
    }
487  
    }
486  

488  

487  
    void record_exception(std::exception_ptr ep)
489  
    void record_exception(std::exception_ptr ep)
488  
    {
490  
    {
489  
        std::lock_guard lk(failure_mu_);
491  
        std::lock_guard lk(failure_mu_);
490  
        last_exception_ = ep;
492  
        last_exception_ = ep;
491  
        last_error_ = {};
493  
        last_error_ = {};
492  
    }
494  
    }
493  
};
495  
};
494  

496  

495  
/** 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).
496  

498  

497  
    Only tries to win when the child returns !ec.
499  
    Only tries to win when the child returns !ec.
498  
*/
500  
*/
499  
template<IoAwaitable Awaitable, typename StateType>
501  
template<IoAwaitable Awaitable, typename StateType>
500  
when_any_io_runner<StateType>
502  
when_any_io_runner<StateType>
501  
make_when_any_io_homogeneous_runner(
503  
make_when_any_io_homogeneous_runner(
502  
    Awaitable inner, StateType* state, std::size_t index)
504  
    Awaitable inner, StateType* state, std::size_t index)
503  
{
505  
{
504  
    auto result = co_await std::move(inner);
506  
    auto result = co_await std::move(inner);
505  

507  

506  
    if(!result.ec)
508  
    if(!result.ec)
507  
    {
509  
    {
508  
        if(state->core_.try_win(index))
510  
        if(state->core_.try_win(index))
509  
        {
511  
        {
510  
            using PayloadT = io_result_payload_t<
512  
            using PayloadT = io_result_payload_t<
511  
                awaitable_result_t<Awaitable>>;
513  
                awaitable_result_t<Awaitable>>;
512  
            if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
514  
            if constexpr (!std::is_same_v<PayloadT, std::tuple<>>)
513  
            {
515  
            {
514  
                try
516  
                try
515  
                {
517  
                {
516  
                    state->result_.emplace(
518  
                    state->result_.emplace(
517  
                        extract_io_payload(std::move(result)));
519  
                        extract_io_payload(std::move(result)));
518  
                }
520  
                }
519  
                catch(...)
521  
                catch(...)
520  
                {
522  
                {
521  
                    state->core_.set_winner_exception(
523  
                    state->core_.set_winner_exception(
522  
                        std::current_exception());
524  
                        std::current_exception());
523  
                }
525  
                }
524  
            }
526  
            }
525  
        }
527  
        }
526  
    }
528  
    }
527  
    else
529  
    else
528  
    {
530  
    {
529  
        state->record_error(result.ec);
531  
        state->record_error(result.ec);
530  
    }
532  
    }
531  
}
533  
}
532  

534  

533  
/** Launches all io_result-aware homogeneous runners concurrently. */
535  
/** Launches all io_result-aware homogeneous runners concurrently. */
534  
template<IoAwaitableRange Range>
536  
template<IoAwaitableRange Range>
535  
class when_any_io_homogeneous_launcher
537  
class when_any_io_homogeneous_launcher
536  
{
538  
{
537  
    using Awaitable = std::ranges::range_value_t<Range>;
539  
    using Awaitable = std::ranges::range_value_t<Range>;
538  
    using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
540  
    using PayloadT = io_result_payload_t<awaitable_result_t<Awaitable>>;
539  

541  

540  
    Range* range_;
542  
    Range* range_;
541  
    when_any_io_homogeneous_state<PayloadT>* state_;
543  
    when_any_io_homogeneous_state<PayloadT>* state_;
542  

544  

543  
public:
545  
public:
544  
    when_any_io_homogeneous_launcher(
546  
    when_any_io_homogeneous_launcher(
545  
        Range* range,
547  
        Range* range,
546  
        when_any_io_homogeneous_state<PayloadT>* state)
548  
        when_any_io_homogeneous_state<PayloadT>* state)
547  
        : range_(range)
549  
        : range_(range)
548  
        , state_(state)
550  
        , state_(state)
549  
    {
551  
    {
550  
    }
552  
    }
551  

553  

552  
    bool await_ready() const noexcept
554  
    bool await_ready() const noexcept
553  
    {
555  
    {
554  
        return std::ranges::empty(*range_);
556  
        return std::ranges::empty(*range_);
555  
    }
557  
    }
556  

558  

557  
    std::coroutine_handle<> await_suspend(
559  
    std::coroutine_handle<> await_suspend(
558  
        std::coroutine_handle<> continuation, io_env const* caller_env)
560  
        std::coroutine_handle<> continuation, io_env const* caller_env)
559  
    {
561  
    {
560  
        state_->core_.continuation_.h = continuation;
562  
        state_->core_.continuation_.h = continuation;
561  
        state_->core_.caller_env_ = caller_env;
563  
        state_->core_.caller_env_ = caller_env;
562  

564  

563  
        if(caller_env->stop_token.stop_possible())
565  
        if(caller_env->stop_token.stop_possible())
564  
        {
566  
        {
565  
            state_->core_.parent_stop_callback_.emplace(
567  
            state_->core_.parent_stop_callback_.emplace(
566  
                caller_env->stop_token,
568  
                caller_env->stop_token,
567  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
569  
                when_any_core::stop_callback_fn{&state_->core_.stop_source_});
568  

570  

569  
            if(caller_env->stop_token.stop_requested())
571  
            if(caller_env->stop_token.stop_requested())
570  
                state_->core_.stop_source_.request_stop();
572  
                state_->core_.stop_source_.request_stop();
571  
        }
573  
        }
572  

574  

573  
        auto token = state_->core_.stop_source_.get_token();
575  
        auto token = state_->core_.stop_source_.get_token();
574  

576  

575  
        // Phase 1: Create all runners without dispatching.
577  
        // Phase 1: Create all runners without dispatching.
576  
        std::size_t index = 0;
578  
        std::size_t index = 0;
577  
        for(auto&& a : *range_)
579  
        for(auto&& a : *range_)
578  
        {
580  
        {
579  
            auto runner = make_when_any_io_homogeneous_runner(
581  
            auto runner = make_when_any_io_homogeneous_runner(
580  
                std::move(a), state_, index);
582  
                std::move(a), state_, index);
581  

583  

582  
            auto h = runner.release();
584  
            auto h = runner.release();
583  
            h.promise().state_ = state_;
585  
            h.promise().state_ = state_;
584  
            h.promise().index_ = index;
586  
            h.promise().index_ = index;
585  
            h.promise().env_ = io_env{caller_env->executor, token,
587  
            h.promise().env_ = io_env{caller_env->executor, token,
586  
                caller_env->frame_allocator};
588  
                caller_env->frame_allocator};
587  

589  

588  
            state_->runner_handles_[index].h = std::coroutine_handle<>{h};
590  
            state_->runner_handles_[index].h = std::coroutine_handle<>{h};
589  
            ++index;
591  
            ++index;
590  
        }
592  
        }
591  

593  

592  
        // Phase 2: Post all runners. Any may complete synchronously.
594  
        // Phase 2: Post all runners. Any may complete synchronously.
593  
        auto* handles = state_->runner_handles_.get();
595  
        auto* handles = state_->runner_handles_.get();
594  
        std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
596  
        std::size_t count = state_->core_.remaining_count_.load(std::memory_order_relaxed);
595  
        for(std::size_t i = 0; i < count; ++i)
597  
        for(std::size_t i = 0; i < count; ++i)
596  
            caller_env->executor.post(handles[i]);
598  
            caller_env->executor.post(handles[i]);
597  

599  

598  
        return std::noop_coroutine();
600  
        return std::noop_coroutine();
599  
    }
601  
    }
600  

602  

601  
    void await_resume() const noexcept {}
603  
    void await_resume() const noexcept {}
602  
};
604  
};
603  

605  

604  
} // namespace detail
606  
} // namespace detail
605  

607  

606  
/** Race a range of io_result-returning awaitables (non-void payloads).
608  
/** Race a range of io_result-returning awaitables (non-void payloads).
607  

609  

608  
    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
609  
    claim winner status. If all children fail, the last failure
611  
    claim winner status. If all children fail, the last failure
610  
    is reported — either the last error_code at variant index 0,
612  
    is reported — either the last error_code at variant index 0,
611  
    or the last exception rethrown.
613  
    or the last exception rethrown.
612  

614  

613  
    @param awaitables Range of io_result-returning awaitables (must
615  
    @param awaitables Range of io_result-returning awaitables (must
614  
        not be empty).
616  
        not be empty).
615  

617  

616  
    @return A task yielding variant<error_code, pair<size_t, PayloadT>>
618  
    @return A task yielding variant<error_code, pair<size_t, PayloadT>>
617  
        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
618  
        index and payload.
620  
        index and payload.
619  

621  

620  
    @throws std::invalid_argument if range is empty.
622  
    @throws std::invalid_argument if range is empty.
621  
    @throws Rethrows last exception when no winner and the last
623  
    @throws Rethrows last exception when no winner and the last
622  
        failure was an exception.
624  
        failure was an exception.
623  

625  

624  
    @par Example
626  
    @par Example
625  
    @code
627  
    @code
626  
    task<void> example()
628  
    task<void> example()
627  
    {
629  
    {
628  
        std::vector<io_task<size_t>> reads;
630  
        std::vector<io_task<size_t>> reads;
629  
        for (auto& buf : buffers)
631  
        for (auto& buf : buffers)
630  
            reads.push_back(stream.read_some(buf));
632  
            reads.push_back(stream.read_some(buf));
631  

633  

632  
        auto result = co_await when_any(std::move(reads));
634  
        auto result = co_await when_any(std::move(reads));
633  
        if (result.index() == 1)
635  
        if (result.index() == 1)
634  
        {
636  
        {
635  
            auto [idx, n] = std::get<1>(result);
637  
            auto [idx, n] = std::get<1>(result);
636  
        }
638  
        }
637  
    }
639  
    }
638  
    @endcode
640  
    @endcode
639  

641  

640  
    @see IoAwaitableRange, when_any
642  
    @see IoAwaitableRange, when_any
641  
*/
643  
*/
642  
template<IoAwaitableRange R>
644  
template<IoAwaitableRange R>
643  
    requires detail::is_io_result_v<
645  
    requires detail::is_io_result_v<
644  
        awaitable_result_t<std::ranges::range_value_t<R>>>
646  
        awaitable_result_t<std::ranges::range_value_t<R>>>
645  
    && (!std::is_same_v<
647  
    && (!std::is_same_v<
646  
            detail::io_result_payload_t<
648  
            detail::io_result_payload_t<
647  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
649  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
648  
            std::tuple<>>)
650  
            std::tuple<>>)
649  
[[nodiscard]] auto when_any(R&& awaitables)
651  
[[nodiscard]] auto when_any(R&& awaitables)
650  
    -> task<std::variant<std::error_code,
652  
    -> task<std::variant<std::error_code,
651  
        std::pair<std::size_t,
653  
        std::pair<std::size_t,
652  
            detail::io_result_payload_t<
654  
            detail::io_result_payload_t<
653  
                awaitable_result_t<std::ranges::range_value_t<R>>>>>>
655  
                awaitable_result_t<std::ranges::range_value_t<R>>>>>>
654  
{
656  
{
655  
    using Awaitable = std::ranges::range_value_t<R>;
657  
    using Awaitable = std::ranges::range_value_t<R>;
656  
    using PayloadT = detail::io_result_payload_t<
658  
    using PayloadT = detail::io_result_payload_t<
657  
        awaitable_result_t<Awaitable>>;
659  
        awaitable_result_t<Awaitable>>;
658  
    using result_type = std::variant<std::error_code,
660  
    using result_type = std::variant<std::error_code,
659  
        std::pair<std::size_t, PayloadT>>;
661  
        std::pair<std::size_t, PayloadT>>;
660  
    using OwnedRange = std::remove_cvref_t<R>;
662  
    using OwnedRange = std::remove_cvref_t<R>;
661  

663  

662  
    auto count = std::ranges::size(awaitables);
664  
    auto count = std::ranges::size(awaitables);
663  
    if(count == 0)
665  
    if(count == 0)
664  
        throw std::invalid_argument("when_any requires at least one awaitable");
666  
        throw std::invalid_argument("when_any requires at least one awaitable");
665  

667  

666  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
668  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
667  

669  

668  
    detail::when_any_io_homogeneous_state<PayloadT> state(count);
670  
    detail::when_any_io_homogeneous_state<PayloadT> state(count);
669  

671  

670  
    co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
672  
    co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
671  
        &owned_awaitables, &state);
673  
        &owned_awaitables, &state);
672  

674  

673  
    // Winner found
675  
    // Winner found
674  
    if(state.core_.has_winner_.load(std::memory_order_acquire))
676  
    if(state.core_.has_winner_.load(std::memory_order_acquire))
675  
    {
677  
    {
676  
        if(state.core_.winner_exception_)
678  
        if(state.core_.winner_exception_)
677  
            std::rethrow_exception(state.core_.winner_exception_);
679  
            std::rethrow_exception(state.core_.winner_exception_);
678  
        co_return result_type{std::in_place_index<1>,
680  
        co_return result_type{std::in_place_index<1>,
679  
            std::pair{state.core_.winner_index_, std::move(*state.result_)}};
681  
            std::pair{state.core_.winner_index_, std::move(*state.result_)}};
680  
    }
682  
    }
681  

683  

682  
    // No winner — report last failure
684  
    // No winner — report last failure
683  
    if(state.last_exception_)
685  
    if(state.last_exception_)
684  
        std::rethrow_exception(state.last_exception_);
686  
        std::rethrow_exception(state.last_exception_);
685  
    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_};
686  
}
688  
}
687  

689  

688  
/** Race a range of void io_result-returning awaitables.
690  
/** Race a range of void io_result-returning awaitables.
689  

691  

690  
    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
691  
    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.
692  

694  

693  
    @param awaitables Range of io_result<>-returning awaitables (must
695  
    @param awaitables Range of io_result<>-returning awaitables (must
694  
        not be empty).
696  
        not be empty).
695  

697  

696  
    @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
697  
        is failure and index 1 carries the winner's index.
699  
        is failure and index 1 carries the winner's index.
698  

700  

699  
    @throws std::invalid_argument if range is empty.
701  
    @throws std::invalid_argument if range is empty.
700  
    @throws Rethrows first exception when no winner and at least one
702  
    @throws Rethrows first exception when no winner and at least one
701  
        child threw.
703  
        child threw.
702  

704  

703  
    @par Example
705  
    @par Example
704  
    @code
706  
    @code
705  
    task<void> example()
707  
    task<void> example()
706  
    {
708  
    {
707  
        std::vector<io_task<>> jobs;
709  
        std::vector<io_task<>> jobs;
708  
        jobs.push_back(background_work_a());
710  
        jobs.push_back(background_work_a());
709  
        jobs.push_back(background_work_b());
711  
        jobs.push_back(background_work_b());
710  

712  

711  
        auto result = co_await when_any(std::move(jobs));
713  
        auto result = co_await when_any(std::move(jobs));
712  
        if (result.index() == 1)
714  
        if (result.index() == 1)
713  
        {
715  
        {
714  
            auto winner = std::get<1>(result);
716  
            auto winner = std::get<1>(result);
715  
        }
717  
        }
716  
    }
718  
    }
717  
    @endcode
719  
    @endcode
718  

720  

719  
    @see IoAwaitableRange, when_any
721  
    @see IoAwaitableRange, when_any
720  
*/
722  
*/
721  
template<IoAwaitableRange R>
723  
template<IoAwaitableRange R>
722  
    requires detail::is_io_result_v<
724  
    requires detail::is_io_result_v<
723  
        awaitable_result_t<std::ranges::range_value_t<R>>>
725  
        awaitable_result_t<std::ranges::range_value_t<R>>>
724  
    && std::is_same_v<
726  
    && std::is_same_v<
725  
            detail::io_result_payload_t<
727  
            detail::io_result_payload_t<
726  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
728  
                awaitable_result_t<std::ranges::range_value_t<R>>>,
727  
            std::tuple<>>
729  
            std::tuple<>>
728  
[[nodiscard]] auto when_any(R&& awaitables)
730  
[[nodiscard]] auto when_any(R&& awaitables)
729  
    -> task<std::variant<std::error_code, std::size_t>>
731  
    -> task<std::variant<std::error_code, std::size_t>>
730  
{
732  
{
731  
    using OwnedRange = std::remove_cvref_t<R>;
733  
    using OwnedRange = std::remove_cvref_t<R>;
732  
    using result_type = std::variant<std::error_code, std::size_t>;
734  
    using result_type = std::variant<std::error_code, std::size_t>;
733  

735  

734  
    auto count = std::ranges::size(awaitables);
736  
    auto count = std::ranges::size(awaitables);
735  
    if(count == 0)
737  
    if(count == 0)
736  
        throw std::invalid_argument("when_any requires at least one awaitable");
738  
        throw std::invalid_argument("when_any requires at least one awaitable");
737  

739  

738  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
740  
    OwnedRange owned_awaitables = std::forward<R>(awaitables);
739  

741  

740  
    detail::when_any_io_homogeneous_state<std::tuple<>> state(count);
742  
    detail::when_any_io_homogeneous_state<std::tuple<>> state(count);
741  

743  

742  
    co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
744  
    co_await detail::when_any_io_homogeneous_launcher<OwnedRange>(
743  
        &owned_awaitables, &state);
745  
        &owned_awaitables, &state);
744  

746  

745  
    // Winner found
747  
    // Winner found
746  
    if(state.core_.has_winner_.load(std::memory_order_acquire))
748  
    if(state.core_.has_winner_.load(std::memory_order_acquire))
747  
    {
749  
    {
748  
        if(state.core_.winner_exception_)
750  
        if(state.core_.winner_exception_)
749  
            std::rethrow_exception(state.core_.winner_exception_);
751  
            std::rethrow_exception(state.core_.winner_exception_);
750  
        co_return result_type{std::in_place_index<1>,
752  
        co_return result_type{std::in_place_index<1>,
751  
            state.core_.winner_index_};
753  
            state.core_.winner_index_};
752  
    }
754  
    }
753  

755  

754  
    // No winner — report last failure
756  
    // No winner — report last failure
755  
    if(state.last_exception_)
757  
    if(state.last_exception_)
756  
        std::rethrow_exception(state.last_exception_);
758  
        std::rethrow_exception(state.last_exception_);
757  
    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_};
758  
}
760  
}
759  

761  

760  
/** Race io_result-returning awaitables, selecting the first success.
762  
/** Race io_result-returning awaitables, selecting the first success.
761  

763  

762  
    Overload selected when all children return io_result<Ts...>.
764  
    Overload selected when all children return io_result<Ts...>.
763  
    Only a child returning !ec can win. Errors and exceptions do
765  
    Only a child returning !ec can win. Errors and exceptions do
764  
    not claim winner status.
766  
    not claim winner status.
765  

767  

766  
    @return A task yielding variant<error_code, R1, ..., Rn> where
768  
    @return A task yielding variant<error_code, R1, ..., Rn> where
767  
        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
768  
        identifies the winning child.
770  
        identifies the winning child.
769  
*/
771  
*/
770  
template<IoAwaitable... As>
772  
template<IoAwaitable... As>
771  
    requires (sizeof...(As) > 0)
773  
    requires (sizeof...(As) > 0)
772  
          && detail::all_io_result_awaitables<As...>
774  
          && detail::all_io_result_awaitables<As...>
773  
[[nodiscard]] auto when_any(As... as)
775  
[[nodiscard]] auto when_any(As... as)
774  
    -> task<std::variant<
776  
    -> task<std::variant<
775  
        std::error_code,
777  
        std::error_code,
776  
        detail::io_result_payload_t<awaitable_result_t<As>>...>>
778  
        detail::io_result_payload_t<awaitable_result_t<As>>...>>
777  
{
779  
{
778  
    using result_type = std::variant<
780  
    using result_type = std::variant<
779  
        std::error_code,
781  
        std::error_code,
780  
        detail::io_result_payload_t<awaitable_result_t<As>>...>;
782  
        detail::io_result_payload_t<awaitable_result_t<As>>...>;
781  

783  

782  
    detail::when_any_io_state<
784  
    detail::when_any_io_state<
783  
        detail::io_result_payload_t<awaitable_result_t<As>>...> state;
785  
        detail::io_result_payload_t<awaitable_result_t<As>>...> state;
784  
    std::tuple<As...> awaitable_tuple(std::move(as)...);
786  
    std::tuple<As...> awaitable_tuple(std::move(as)...);
785  

787  

786  
    co_await detail::when_any_io_launcher<As...>(
788  
    co_await detail::when_any_io_launcher<As...>(
787  
        &awaitable_tuple, &state);
789  
        &awaitable_tuple, &state);
788  

790  

789  
    // Winner found: return their result
791  
    // Winner found: return their result
790  
    if(state.result_.has_value())
792  
    if(state.result_.has_value())
791  
        co_return std::move(*state.result_);
793  
        co_return std::move(*state.result_);
792  

794  

793  
    // Winner claimed but payload construction failed
795  
    // Winner claimed but payload construction failed
794  
    if(state.core_.winner_exception_)
796  
    if(state.core_.winner_exception_)
795  
        std::rethrow_exception(state.core_.winner_exception_);
797  
        std::rethrow_exception(state.core_.winner_exception_);
796  

798  

797  
    // No winner — report last failure
799  
    // No winner — report last failure
798  
    if(state.last_exception_)
800  
    if(state.last_exception_)
799  
        std::rethrow_exception(state.last_exception_);
801  
        std::rethrow_exception(state.last_exception_);
800  
    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_};
801  
}
803  
}
802  

804  

803  
} // namespace capy
805  
} // namespace capy
804  
} // namespace boost
806  
} // namespace boost
805  

807  

806  
#endif
808  
#endif