From 2210d338806419c5e07ca87ff362f6e563a85ddd Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Fri, 19 Jun 2026 09:16:38 -0600 Subject: [PATCH] docs(when_any): clarify wait_for_one_success semantics and patterns when_any selects a winner by success (!ec); a failing child neither wins nor cancels its siblings. This was reported as a bug (cppalliance/capy#265) but is by design. Document the behavior explicitly and the two patterns for treating an error as a win, and replace the dangling "see Racing Tasks" reference (which pointed at an unpublished doc/unlisted/ page). - doc/.../4f.composition.adoc: add "Errors Do Not Win" and "Treating an Error as a Win" subsections under when_any; drop the dead reference. - include/boost/capy/when_any.hpp: add a @note to the variadic when_any docstring pointing readers to the patterns. --- .../pages/4.coroutines/4f.composition.adoc | 40 ++++++++++++++++++- include/boost/capy/when_any.hpp | 6 +++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc b/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc index d4750b19b..0078bba92 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc @@ -175,7 +175,45 @@ task<> example() The result is a `variant` with `error_code` at index 0 (failure/no winner) and one alternative per input task at indices 1..N. Only tasks returning `!ec` can win; errors and exceptions do not count as winning. When a winner is found, stop is requested for all siblings. All tasks complete before `when_any` returns. -For detailed coverage including error handling, cancellation, and the range overload, see Racing Tasks. +=== Errors Do Not Win (wait_for_one_success) + +A child that returns a non-zero `ec` (or throws) does *not* win, and it does *not* cancel its siblings. `when_any` keeps waiting until some child succeeds or until every child has finished. Only when *all* children fail does the result settle at index 0, holding an `error_code`. + +If you need "complete on the first child to *finish*, success or error," that behavior is opt-in — wrap the child as shown below. + +=== Treating an Error as a Win + +To make a child win on an error, wrap it so the error becomes a success before `when_any` sees it. + +The first pattern translates a specific, benign error into success. Other errors propagate unchanged, so they still do not win: + +[source,cpp] +---- +// canceled is benign here: translate it to success so when_any picks this child. +io_task<> wrapped() +{ + auto [ec] = co_await inner(); + if (ec == cond::canceled) + co_return io_result<>{}; // success: when_any sees a winner + co_return io_result<>{ec}; // propagate other errors unchanged +} +---- + +The second pattern lifts the inner `ec` into the payload. The wrapper always succeeds, so it wins on its first completion, carrying the original error code to the caller: + +[source,cpp] +---- +// Always succeeds; the winner's payload carries the original ec. +io_task wrapped() +{ + auto [ec] = co_await inner(); + co_return io_result{{}, ec}; +} + +// when_any(wrapped(), ...) -> variant +// index 0: every child failed +// index i: child i won; std::get(result) is its original ec +---- == Practical Patterns diff --git a/include/boost/capy/when_any.hpp b/include/boost/capy/when_any.hpp index b123a743b..a23202326 100644 --- a/include/boost/capy/when_any.hpp +++ b/include/boost/capy/when_any.hpp @@ -778,6 +778,12 @@ template @return A task yielding variant where index 0 is the failure/no-winner case and index i+1 identifies the winning child. + + @note A failing child does not cancel its siblings; `when_any` + waits for a success or for every child to finish. To make a + benign error (e.g. @c cond::canceled) count as a win, wrap + the child to translate the error into success. See the + Concurrent Composition tutorial. */ template requires (sizeof...(As) > 0)