LCOV - code coverage report
Current view: top level - capy - when_any.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 191 191
Test Date: 2026-10-16 22:02:34 Functions: 93.0 % 341 317 24

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

Generated by: LCOV version 2.3