GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 90.8% 1388 / 0 / 1529
Functions: 91.6% 370 / 0 / 404
Branches: 81.1% 300 / 0 / 370

libs/capy/include/boost/capy/when_any.hpp
Line Branch Exec Source
1 //
2 // Copyright (c) 2026 Michael Vandeberg
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/capy
8 //
9
10 #ifndef BOOST_CAPY_WHEN_ANY_HPP
11 #define BOOST_CAPY_WHEN_ANY_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/concept/executor.hpp>
15 #include <boost/capy/io_awaitable.hpp>
16 #include <boost/capy/coro.hpp>
17 #include <boost/capy/ex/executor_ref.hpp>
18 #include <boost/capy/ex/frame_allocator.hpp>
19 #include <boost/capy/task.hpp>
20
21 #include <array>
22 #include <atomic>
23 #include <exception>
24 #include <optional>
25 #include <stdexcept>
26 #include <stop_token>
27 #include <tuple>
28 #include <type_traits>
29 #include <utility>
30 #include <variant>
31 #include <vector>
32
33 /*
34 * when_any - Race multiple tasks, return first completion
35 * ========================================================
36 *
37 * OVERVIEW:
38 * ---------
39 * when_any launches N tasks concurrently and completes when the FIRST task
40 * finishes (success or failure). It then requests stop for all siblings and
41 * waits for them to acknowledge before returning.
42 *
43 * ARCHITECTURE:
44 * -------------
45 * The design mirrors when_all but with inverted completion semantics:
46 *
47 * when_all: complete when remaining_count reaches 0 (all done)
48 * when_any: complete when has_winner becomes true (first done)
49 * BUT still wait for remaining_count to reach 0 for cleanup
50 *
51 * Key components:
52 * - when_any_state: Shared state tracking winner and completion
53 * - when_any_runner: Wrapper coroutine for each child task
54 * - when_any_launcher: Awaitable that starts all runners concurrently
55 *
56 * CRITICAL INVARIANTS:
57 * --------------------
58 * 1. Exactly one task becomes the winner (via atomic compare_exchange)
59 * 2. All tasks must complete before parent resumes (cleanup safety)
60 * 3. Stop is requested immediately when winner is determined
61 * 4. Only the winner's result/exception is stored
62 *
63 * TYPE DEDUPLICATION:
64 * -------------------
65 * std::variant requires unique alternative types. Since when_any can race
66 * tasks with identical return types (e.g., three task<int>), we must
67 * deduplicate types before constructing the variant.
68 *
69 * Example: when_any(task<int>, task<string>, task<int>)
70 * - Raw types after void->monostate: int, string, int
71 * - Deduplicated variant: std::variant<int, string>
72 * - Return: pair<size_t, variant<int, string>>
73 *
74 * The winner_index tells you which task won (0, 1, or 2), while the variant
75 * holds the result. Use the index to determine how to interpret the variant.
76 *
77 * VOID HANDLING:
78 * --------------
79 * void tasks contribute std::monostate to the variant (then deduplicated).
80 * All-void tasks result in: pair<size_t, variant<monostate>>
81 *
82 * MEMORY MODEL:
83 * -------------
84 * Synchronization chain from winner's write to parent's read:
85 *
86 * 1. Winner thread writes result_/winner_exception_ (non-atomic)
87 * 2. Winner thread calls signal_completion() → fetch_sub(acq_rel) on remaining_count_
88 * 3. Last task thread (may be winner or non-winner) calls signal_completion()
89 * → fetch_sub(acq_rel) on remaining_count_, observing count becomes 0
90 * 4. Last task returns caller_ex_.dispatch(continuation_) via symmetric transfer
91 * 5. Parent coroutine resumes and reads result_/winner_exception_
92 *
93 * Synchronization analysis:
94 * - All fetch_sub operations on remaining_count_ form a release sequence
95 * - Winner's fetch_sub releases; subsequent fetch_sub operations participate
96 * in the modification order of remaining_count_
97 * - Last task's fetch_sub(acq_rel) synchronizes-with prior releases in the
98 * modification order, establishing happens-before from winner's writes
99 * - Executor dispatch() is expected to provide queue-based synchronization
100 * (release-on-post, acquire-on-execute) completing the chain to parent
101 * - Even inline executors work (same thread = sequenced-before)
102 *
103 * Alternative considered: Adding winner_ready_ atomic (set with release after
104 * storing winner data, acquired before reading) would make synchronization
105 * self-contained and not rely on executor implementation details. Current
106 * approach is correct but requires careful reasoning about release sequences
107 * and executor behavior.
108 *
109 * EXCEPTION SEMANTICS:
110 * --------------------
111 * Unlike when_all (which captures first exception, discards others), when_any
112 * treats exceptions as valid completions. If the winning task threw, that
113 * exception is rethrown. Exceptions from non-winners are silently discarded.
114 */
115
116 namespace boost {
117 namespace capy {
118
119 namespace detail {
120
121 /** Convert void to monostate for variant storage.
122
123 std::variant<void, ...> is ill-formed, so void tasks contribute
124 std::monostate to the result variant instead. Non-void types
125 pass through unchanged.
126
127 @tparam T The type to potentially convert (void becomes monostate).
128 */
129 template<typename T>
130 using void_to_monostate_t = std::conditional_t<std::is_void_v<T>, std::monostate, T>;
131
132 /** Type deduplication for variant construction.
133
134 std::variant requires unique alternative types. These metafunctions
135 deduplicate a type list while preserving order of first occurrence.
136
137 @par Algorithm
138 Fold left over the type list, appending each type to the accumulator
139 only if not already present. O(N^2) in number of types but N is
140 typically small (number of when_any arguments).
141 */
142 /** Primary template for appending a type to a variant if not already present.
143
144 @tparam Variant The accumulated variant type.
145 @tparam T The type to potentially append.
146 */
147 template<typename Variant, typename T>
148 struct variant_append_if_unique;
149
150 /** Specialization that checks for type uniqueness and appends if needed.
151
152 @tparam Vs Types already in the variant.
153 @tparam T The type to potentially append.
154 */
155 template<typename... Vs, typename T>
156 struct variant_append_if_unique<std::variant<Vs...>, T>
157 {
158 /** Result type: original variant if T is duplicate, extended variant otherwise. */
159 using type = std::conditional_t<
160 (std::is_same_v<T, Vs> || ...),
161 std::variant<Vs...>,
162 std::variant<Vs..., T>>;
163 };
164
165 /** Primary template for type list deduplication.
166
167 @tparam Accumulated The variant accumulating unique types.
168 @tparam Remaining Types still to be processed.
169 */
170 template<typename Accumulated, typename... Remaining>
171 struct deduplicate_impl;
172
173 /** Base case: no more types to process.
174
175 @tparam Accumulated The final deduplicated variant type.
176 */
177 template<typename Accumulated>
178 struct deduplicate_impl<Accumulated>
179 {
180 /** The final deduplicated variant type. */
181 using type = Accumulated;
182 };
183
184 /** Recursive case: add T if unique, then process rest.
185
186 @tparam Accumulated The variant accumulated so far.
187 @tparam T The current type to potentially add.
188 @tparam Rest Remaining types to process.
189 */
190 template<typename Accumulated, typename T, typename... Rest>
191 struct deduplicate_impl<Accumulated, T, Rest...>
192 {
193 /** Intermediate type after potentially appending T. */
194 using next = typename variant_append_if_unique<Accumulated, T>::type;
195
196 /** Final result after processing all remaining types. */
197 using type = typename deduplicate_impl<next, Rest...>::type;
198 };
199
200 /** Deduplicated variant from a list of types.
201
202 Constructs a std::variant containing unique types from the input list.
203 Void types are converted to std::monostate before deduplication.
204 The first type T0 seeds the accumulator, ensuring the variant is well-formed.
205
206 @tparam T0 First result type (required, seeds the deduplication).
207 @tparam Ts Remaining result types (void is converted to monostate).
208 */
209 template<typename T0, typename... Ts>
210 using unique_variant_t = typename deduplicate_impl<
211 std::variant<void_to_monostate_t<T0>>,
212 void_to_monostate_t<Ts>...>::type;
213
214 /** Result type for when_any: (winner_index, deduplicated_variant).
215
216 The first element is the zero-based index of the winning task in the
217 original argument order. The second element is a variant holding the
218 winner's result by type. When multiple tasks share the same return type,
219 use the index to determine which task actually won.
220
221 @tparam T0 First task's result type.
222 @tparam Ts Remaining tasks' result types.
223 */
224 template<typename T0, typename... Ts>
225 using when_any_result_t = std::pair<std::size_t, unique_variant_t<T0, Ts...>>;
226
227 /** Shared state for when_any operation.
228
229 Coordinates winner selection, result storage, and completion tracking
230 for all child tasks in a when_any operation.
231
232 @par Lifetime
233 Allocated on the parent coroutine's frame, outlives all runners.
234
235 @par Thread Safety
236 Atomic operations protect winner selection and completion count.
237 Result storage is written only by the winner before any concurrent access.
238
239 @tparam T0 First task's result type.
240 @tparam Ts Remaining tasks' result types.
241 */
242 template<typename T0, typename... Ts>
243 struct when_any_state
244 {
245 /** Total number of tasks being raced. */
246 static constexpr std::size_t task_count = 1 + sizeof...(Ts);
247
248 /** Deduplicated variant type for storing the winner's result. */
249 using variant_type = unique_variant_t<T0, Ts...>;
250
251 /** Counter for tasks still running.
252
253 Must wait for ALL tasks to finish before parent resumes; this ensures
254 runner coroutine frames are valid until their final_suspend completes.
255 */
256 std::atomic<std::size_t> remaining_count_;
257
258 /** Flag indicating whether a winner has been determined.
259
260 Winner selection: exactly one task wins via atomic CAS on has_winner_.
261 winner_index_ is written only by the winner, read after all complete.
262 */
263 std::atomic<bool> has_winner_{false};
264
265 /** Index of the winning task in the original argument list. */
266 std::size_t winner_index_{0};
267
268 /** Storage for the winner's result value.
269
270 Result storage: deduplicated variant. Stored by type, not task index,
271 because multiple tasks may share the same return type.
272 */
273 std::optional< variant_type > result_;
274
275 /** Exception thrown by the winner, if any.
276
277 Non-null if winner threw (rethrown to caller after all tasks complete).
278 */
279 std::exception_ptr winner_exception_;
280
281 /** Handles to runner coroutines for cleanup.
282
283 Runner coroutine handles; destroyed in destructor after all complete.
284 */
285 std::array<coro, task_count> runner_handles_{};
286
287 /** Stop source for cancelling sibling tasks.
288
289 Owned stop_source: request_stop() called when winner determined.
290 */
291 std::stop_source stop_source_;
292
293 /** Callback functor that forwards stop requests.
294
295 Forwards parent's stop requests to our stop_source, enabling
296 cancellation to propagate from caller through when_any to children.
297 */
298 struct stop_callback_fn
299 {
300 /** Pointer to the stop source to signal. */
301 std::stop_source* source_;
302
303 /** Invoke the stop request on the source. */
304 4 void operator()() const noexcept { source_->request_stop(); }
305 };
306
307 /** Type alias for the stop callback registration. */
308 using stop_callback_t = std::stop_callback<stop_callback_fn>;
309
310 /** Optional callback linking parent's stop token to our stop source. */
311 std::optional<stop_callback_t> parent_stop_callback_;
312
313 /** Parent coroutine handle to resume when all children complete. */
314 coro continuation_;
315
316 /** Executor reference for dispatching the parent resumption. */
317 executor_ref caller_ex_;
318
319 /** Construct state for racing task_count tasks.
320
321 Initializes remaining_count_ to task_count so all tasks must complete
322 before the parent coroutine resumes.
323 */
324 37 when_any_state()
325
10/10
boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>::when_any_state():
✓ Branch 5 taken 1 time.
boost::capy::detail::when_any_state<int, int, int, int>::when_any_state():
✓ Branch 5 taken 1 time.
boost::capy::detail::when_any_state<int, int, int>::when_any_state():
✓ Branch 5 taken 9 times.
boost::capy::detail::when_any_state<int, int>::when_any_state():
✓ Branch 5 taken 17 times.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::when_any_state():
✓ Branch 5 taken 1 time.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::when_any_state():
✓ Branch 5 taken 1 time.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::when_any_state():
✓ Branch 5 taken 2 times.
boost::capy::detail::when_any_state<int>::when_any_state():
✓ Branch 5 taken 2 times.
boost::capy::detail::when_any_state<void, int>::when_any_state():
✓ Branch 5 taken 2 times.
boost::capy::detail::when_any_state<void, void, void>::when_any_state():
✓ Branch 5 taken 1 time.
37 : remaining_count_(task_count)
326 {
327 37 }
328
329 /** Destroy state and clean up runner coroutine handles.
330
331 All runners must have completed before destruction (guaranteed by
332 waiting for remaining_count_ to reach zero).
333 */
334 37 ~when_any_state()
335 {
336
20/20
boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>::~when_any_state():
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_state<int, int, int, int>::~when_any_state():
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_state<int, int, int>::~when_any_state():
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 9 times.
boost::capy::detail::when_any_state<int, int>::~when_any_state():
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 17 times.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::~when_any_state():
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::~when_any_state():
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::~when_any_state():
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_state<int>::~when_any_state():
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_state<void, int>::~when_any_state():
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_state<void, void, void>::~when_any_state():
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
130 for(auto h : runner_handles_)
337
10/20
boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>::~when_any_state():
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<int, int, int, int>::~when_any_state():
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<int, int, int>::~when_any_state():
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<int, int>::~when_any_state():
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::~when_any_state():
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::~when_any_state():
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::~when_any_state():
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<int>::~when_any_state():
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<void, int>::~when_any_state():
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<void, void, void>::~when_any_state():
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
93 if(h)
338 93 h.destroy();
339 37 }
340
341 /** Attempt to become the winner.
342
343 Atomically claims winner status. Exactly one task succeeds; all others
344 see false. The winner must store its result before returning.
345
346 @param index The task's index in the original argument list.
347 @return true if this task is now the winner, false if another won first.
348 */
349 93 bool try_win(std::size_t index) noexcept
350 {
351 93 bool expected = false;
352
19/20
boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>::try_win(unsigned long):
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 7 times.
boost::capy::detail::when_any_state<int, int, int, int>::try_win(unsigned long):
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
boost::capy::detail::when_any_state<int, int, int>::try_win(unsigned long):
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 18 times.
boost::capy::detail::when_any_state<int, int>::try_win(unsigned long):
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 17 times.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::try_win(unsigned long):
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::try_win(unsigned long):
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::try_win(unsigned long):
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
boost::capy::detail::when_any_state<int>::try_win(unsigned long):
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_state<void, int>::try_win(unsigned long):
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
boost::capy::detail::when_any_state<void, void, void>::try_win(unsigned long):
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
93 if(has_winner_.compare_exchange_strong(
353 expected, true, std::memory_order_acq_rel))
354 {
355 37 winner_index_ = index;
356 // Signal siblings to exit early if they support cancellation
357 37 stop_source_.request_stop();
358 37 return true;
359 }
360 56 return false;
361 }
362
363 /** Store the winner's result.
364
365 @pre Only called by the winner (try_win returned true).
366 @note Uses type-based emplacement because the variant is deduplicated;
367 task index may differ from variant alternative index.
368 */
369 template<typename T>
370 30 void set_winner_result(T value)
371 noexcept(std::is_nothrow_move_constructible_v<T>)
372 {
373 30 result_.emplace(std::in_place_type<T>, std::move(value));
374 30 }
375
376 /** Store the winner's void completion as monostate.
377
378 @pre Only called by the winner of a void-returning task.
379 */
380 2 void set_winner_void() noexcept
381 {
382 2 result_.emplace(std::in_place_type<std::monostate>, std::monostate{});
383 2 }
384
385 /** Store the winner's exception.
386
387 @pre Only called by the winner (try_win returned true).
388 */
389 5 void set_winner_exception(std::exception_ptr ep) noexcept
390 {
391 5 winner_exception_ = ep;
392 5 }
393
394 /** Signal that a task has completed (success, failure, or cancelled).
395
396 Called by every runner at final_suspend. The last one to complete
397 resumes the parent coroutine. This ensures all child coroutine
398 frames are destroyed before the parent continues.
399
400 @return Coroutine to resume (parent if last, noop otherwise).
401 */
402 93 coro signal_completion() noexcept
403 {
404 93 auto remaining = remaining_count_.fetch_sub(1, std::memory_order_acq_rel);
405
19/20
boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>::signal_completion():
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 7 times.
boost::capy::detail::when_any_state<int, int, int, int>::signal_completion():
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
boost::capy::detail::when_any_state<int, int, int>::signal_completion():
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 18 times.
boost::capy::detail::when_any_state<int, int>::signal_completion():
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 17 times.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::signal_completion():
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::signal_completion():
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::signal_completion():
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
boost::capy::detail::when_any_state<int>::signal_completion():
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
boost::capy::detail::when_any_state<void, int>::signal_completion():
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_state<void, void, void>::signal_completion():
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
93 if(remaining == 1)
406 37 return caller_ex_.dispatch(continuation_);
407 56 return std::noop_coroutine();
408 }
409 };
410
411 /** Wrapper coroutine that runs a single child task for when_any.
412
413 Each child task is wrapped in a runner that:
414 1. Propagates executor and stop_token to the child
415 2. Attempts to claim winner status on completion
416 3. Stores result only if this runner won
417 4. Signals completion regardless of win/loss (for cleanup)
418
419 @tparam T The result type of the wrapped task.
420 @tparam Ts All task result types (for when_any_state compatibility).
421 */
422 template<typename T, typename... Ts>
423 struct when_any_runner
424 {
425 /** Promise type for the runner coroutine.
426
427 Manages executor propagation, stop token forwarding, and completion
428 signaling for the wrapped child task.
429 */
430 struct promise_type : frame_allocating_base
431 {
432 /** Pointer to shared state for winner coordination. */
433 when_any_state<Ts...>* state_ = nullptr;
434
435 /** Index of this task in the original argument list. */
436 std::size_t index_ = 0;
437
438 /** Executor reference inherited from the parent coroutine. */
439 executor_ref ex_;
440
441 /** Stop token for cooperative cancellation. */
442 std::stop_token stop_token_;
443
444 /** Create the runner coroutine object from this promise.
445
446 @return Runner coroutine wrapping this promise's coroutine handle.
447 */
448 93 when_any_runner get_return_object()
449 {
450 93 return when_any_runner(std::coroutine_handle<promise_type>::from_promise(*this));
451 }
452
453 /** Suspend immediately on creation.
454
455 Runner coroutines start suspended; the launcher resumes them
456 after setting up state_, index_, ex_, and stop_token_.
457
458 @return Always suspends.
459 */
460 93 std::suspend_always initial_suspend() noexcept
461 {
462 93 return {};
463 }
464
465 /** Final suspend awaiter that signals completion to shared state.
466
467 @return Custom awaiter that calls signal_completion().
468 */
469 93 auto final_suspend() noexcept
470 {
471 /** Awaiter that signals task completion and potentially resumes parent. */
472 struct awaiter
473 {
474 /** Pointer to the promise for accessing shared state. */
475 promise_type* p_;
476
477 /** Never ready; always suspend to signal completion.
478
479 @return Always false.
480 */
481 bool await_ready() const noexcept
482 {
483 return false;
484 }
485
486 /** Signal completion and return next coroutine to resume.
487
488 @return Parent coroutine if this was the last task, noop otherwise.
489 */
490 coro await_suspend(coro) noexcept
491 {
492 return p_->state_->signal_completion();
493 }
494
495 /** No-op resume; coroutine is destroyed after final suspend. */
496 void await_resume() const noexcept
497 {
498 }
499 };
500 93 return awaiter{this};
501 }
502
503 /** Called when runner coroutine body completes normally.
504
505 The actual result handling is done in make_when_any_runner;
506 this just satisfies the coroutine return requirement.
507 */
508 84 void return_void()
509 {
510 84 }
511
512 /** Handle exceptions thrown by the child task.
513
514 Exceptions are valid completions in when_any (unlike when_all).
515 If this exception wins, it will be rethrown to the caller.
516 If another task already won, this exception is discarded.
517 */
518 9 void unhandled_exception()
519 {
520
5/30
boost::capy::detail::when_any_runner<double, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int>::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<int, int, int, int, int>::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<int, int, int, int>::promise_type::unhandled_exception():
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
boost::capy::detail::when_any_runner<int, int, int>::promise_type::unhandled_exception():
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<int, int>::promise_type::unhandled_exception():
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<int, void, int>::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<void, void, int>::promise_type::unhandled_exception():
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
boost::capy::detail::when_any_runner<void, void, void, void>::promise_type::unhandled_exception():
✗ Branch 1 not taken.
✗ Branch 2 not taken.
9 if(state_->try_win(index_))
521 5 state_->set_winner_exception(std::current_exception());
522 9 }
523
524 /** Awaiter wrapper that injects executor and stop token into child awaitables.
525
526 @tparam Awaitable The underlying awaitable type being wrapped.
527 */
528 template<class Awaitable>
529 struct transform_awaiter
530 {
531 /** The wrapped awaitable instance. */
532 std::decay_t<Awaitable> a_;
533
534 /** Pointer to promise for accessing executor and stop token. */
535 promise_type* p_;
536
537 /** Check if the underlying awaitable is ready.
538
539 @return True if awaitable can complete synchronously.
540 */
541 93 bool await_ready()
542 {
543 93 return a_.await_ready();
544 }
545
546 /** Get the result from the underlying awaitable.
547
548 @return The awaitable's result value.
549 */
550 93 auto await_resume()
551 {
552 93 return a_.await_resume();
553 }
554
555 /** Suspend with executor and stop token injection.
556
557 @tparam Promise The suspending coroutine's promise type.
558 @param h Handle to the suspending coroutine.
559 @return Coroutine to resume or void.
560 */
561 template<class Promise>
562 93 auto await_suspend(std::coroutine_handle<Promise> h)
563 {
564
15/15
auto boost::capy::detail::when_any_runner<double, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type::transform_awaiter<boost::capy::task<double> >::await_suspend<boost::capy::detail::when_any_runner<double, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<double, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type>):
✓ Branch 3 taken 1 time.
auto boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int>::promise_type>):
✓ Branch 3 taken 8 times.
auto boost::capy::detail::when_any_runner<int, int, int, int, int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_runner<int, int, int, int, int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<int, int, int, int, int>::promise_type>):
✓ Branch 3 taken 4 times.
auto boost::capy::detail::when_any_runner<int, int, int, int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_runner<int, int, int, int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<int, int, int, int>::promise_type>):
✓ Branch 3 taken 27 times.
auto boost::capy::detail::when_any_runner<int, int, int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_runner<int, int, int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<int, int, int>::promise_type>):
✓ Branch 3 taken 34 times.
auto boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type>):
✓ Branch 3 taken 1 time.
auto boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type>):
✓ Branch 3 taken 1 time.
auto boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::promise_type>):
✓ Branch 3 taken 4 times.
auto boost::capy::detail::when_any_runner<int, int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_runner<int, int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<int, int>::promise_type>):
✓ Branch 3 taken 2 times.
auto boost::capy::detail::when_any_runner<int, void, int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_runner<int, void, int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<int, void, int>::promise_type>):
✓ Branch 3 taken 2 times.
auto boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type::transform_awaiter<boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::await_suspend<boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::promise_type>):
✓ Branch 3 taken 1 time.
auto boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type::transform_awaiter<boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::await_suspend<boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::promise_type>):
✓ Branch 3 taken 1 time.
auto boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::promise_type::transform_awaiter<boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::await_suspend<boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::promise_type>):
✓ Branch 3 taken 2 times.
auto boost::capy::detail::when_any_runner<void, void, int>::promise_type::transform_awaiter<boost::capy::task<void> >::await_suspend<boost::capy::detail::when_any_runner<void, void, int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<void, void, int>::promise_type>):
✓ Branch 3 taken 2 times.
auto boost::capy::detail::when_any_runner<void, void, void, void>::promise_type::transform_awaiter<boost::capy::task<void> >::await_suspend<boost::capy::detail::when_any_runner<void, void, void, void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_runner<void, void, void, void>::promise_type>):
✓ Branch 3 taken 3 times.
93 return a_.await_suspend(h, p_->ex_, p_->stop_token_);
565 }
566 };
567
568 /** Transform awaitables to inject executor and stop token.
569
570 @tparam Awaitable The awaitable type being co_awaited.
571 @param a The awaitable instance.
572 @return Transformed awaiter with executor/stop_token injection.
573 */
574 template<class Awaitable>
575 93 auto await_transform(Awaitable&& a)
576 {
577 using A = std::decay_t<Awaitable>;
578 if constexpr (IoAwaitable<A>)
579 {
580 return transform_awaiter<Awaitable>{
581 186 std::forward<Awaitable>(a), this};
582 }
583 else
584 {
585 return make_affine(std::forward<Awaitable>(a), ex_);
586 }
587 93 }
588 };
589
590 /** Handle to the underlying coroutine frame. */
591 std::coroutine_handle<promise_type> h_;
592
593 /** Construct runner from a coroutine handle.
594
595 @param h Handle to the runner coroutine frame.
596 */
597 93 explicit when_any_runner(std::coroutine_handle<promise_type> h) noexcept
598 93 : h_(h)
599 {
600 93 }
601
602 /** Move constructor (Clang 14 workaround).
603
604 Clang 14 (non-Apple) has a coroutine codegen bug requiring explicit
605 move constructor; other compilers work correctly with deleted move.
606 */
607 #if defined(__clang__) && __clang_major__ == 14 && !defined(__apple_build_version__)
608 when_any_runner(when_any_runner&& other) noexcept : h_(std::exchange(other.h_, nullptr)) {}
609 #endif
610
611 /** Copy construction is not allowed. */
612 when_any_runner(when_any_runner const&) = delete;
613
614 /** Copy assignment is not allowed. */
615 when_any_runner& operator=(when_any_runner const&) = delete;
616
617 /** Move construction is deleted (except on Clang 14). */
618 #if !defined(__clang__) || __clang_major__ != 14 || defined(__apple_build_version__)
619 when_any_runner(when_any_runner&&) = delete;
620 #endif
621
622 /** Move assignment is not allowed. */
623 when_any_runner& operator=(when_any_runner&&) = delete;
624
625 /** Release ownership of the coroutine handle.
626
627 @return The coroutine handle; this object becomes empty.
628 */
629 93 auto release() noexcept
630 {
631 93 return std::exchange(h_, nullptr);
632 }
633 };
634
635 /** Create a runner coroutine for a single task in when_any.
636
637 Factory function that creates a wrapper coroutine for a child task.
638 The runner handles executor/stop_token propagation and winner selection.
639
640 @tparam Index Compile-time index of this task in the argument list.
641 @tparam T The result type of the task being wrapped.
642 @tparam Ts All task result types (for when_any_state compatibility).
643 @param inner The task to run (will be moved from).
644 @param state Shared state for winner coordination.
645 @return Runner coroutine (must be started via resume()).
646 */
647 template<std::size_t Index, typename T, typename... Ts>
648 when_any_runner<T, Ts...>
649
31/31
boost::capy::detail::when_any_runner<double, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double> boost::capy::detail::make_when_any_runner<2ul, double, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>(boost::capy::task<double>, boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int> boost::capy::detail::make_when_any_runner<0ul, int, int, int, int, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int> boost::capy::detail::make_when_any_runner<1ul, int, int, int, int, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int> boost::capy::detail::make_when_any_runner<2ul, int, int, int, int, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int> boost::capy::detail::make_when_any_runner<3ul, int, int, int, int, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int> boost::capy::detail::make_when_any_runner<4ul, int, int, int, int, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int> boost::capy::detail::make_when_any_runner<5ul, int, int, int, int, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int> boost::capy::detail::make_when_any_runner<6ul, int, int, int, int, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int, int, int, int, int> boost::capy::detail::make_when_any_runner<7ul, int, int, int, int, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int, int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int> boost::capy::detail::make_when_any_runner<0ul, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int> boost::capy::detail::make_when_any_runner<1ul, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int> boost::capy::detail::make_when_any_runner<2ul, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int, int> boost::capy::detail::make_when_any_runner<3ul, int, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int, int>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, int, int> boost::capy::detail::make_when_any_runner<0ul, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int>*):
✓ Branch 1 taken 9 times.
boost::capy::detail::when_any_runner<int, int, int, int> boost::capy::detail::make_when_any_runner<1ul, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int>*):
✓ Branch 1 taken 9 times.
boost::capy::detail::when_any_runner<int, int, int, int> boost::capy::detail::make_when_any_runner<2ul, int, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int, int>*):
✓ Branch 1 taken 9 times.
boost::capy::detail::when_any_runner<int, int, int> boost::capy::detail::make_when_any_runner<0ul, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int>*):
✓ Branch 1 taken 17 times.
boost::capy::detail::when_any_runner<int, int, int> boost::capy::detail::make_when_any_runner<1ul, int, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, int>*):
✓ Branch 1 taken 17 times.
boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > boost::capy::detail::make_when_any_runner<0ul, int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(boost::capy::task<int>, boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double> boost::capy::detail::make_when_any_runner<0ul, int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> boost::capy::detail::make_when_any_runner<0ul, int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>*):
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_runner<int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> boost::capy::detail::make_when_any_runner<2ul, int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>*):
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_runner<int, int> boost::capy::detail::make_when_any_runner<0ul, int, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<int>*):
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_runner<int, void, int> boost::capy::detail::make_when_any_runner<1ul, int, void, int>(boost::capy::task<int>, boost::capy::detail::when_any_state<void, int>*):
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > boost::capy::detail::make_when_any_runner<1ul, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double> boost::capy::detail::make_when_any_runner<1ul, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>(boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> boost::capy::detail::make_when_any_runner<1ul, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>(boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::capy::detail::when_any_state<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>*):
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_runner<void, void, int> boost::capy::detail::make_when_any_runner<0ul, void, void, int>(boost::capy::task<void>, boost::capy::detail::when_any_state<void, int>*):
✓ Branch 1 taken 2 times.
boost::capy::detail::when_any_runner<void, void, void, void> boost::capy::detail::make_when_any_runner<0ul, void, void, void, void>(boost::capy::task<void>, boost::capy::detail::when_any_state<void, void, void>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<void, void, void, void> boost::capy::detail::make_when_any_runner<1ul, void, void, void, void>(boost::capy::task<void>, boost::capy::detail::when_any_state<void, void, void>*):
✓ Branch 1 taken 1 time.
boost::capy::detail::when_any_runner<void, void, void, void> boost::capy::detail::make_when_any_runner<2ul, void, void, void, void>(boost::capy::task<void>, boost::capy::detail::when_any_state<void, void, void>*):
✓ Branch 1 taken 1 time.
93 make_when_any_runner(task<T> inner, when_any_state<Ts...>* state)
650 {
651 if constexpr (std::is_void_v<T>)
652 {
653 co_await std::move(inner);
654 if(state->try_win(Index))
655 state->set_winner_void(); // noexcept
656 }
657 else
658 {
659 auto result = co_await std::move(inner);
660 if(state->try_win(Index))
661 {
662 try
663 {
664 state->set_winner_result(std::move(result));
665 }
666 catch(...)
667 {
668 state->set_winner_exception(std::current_exception());
669 }
670 }
671 }
672 186 }
673
674 /** Awaitable that launches all runner coroutines concurrently.
675
676 Handles the tricky lifetime issue where tasks may complete synchronously
677 during launch, potentially destroying this awaitable's frame before
678 all tasks are extracted from the tuple. See await_suspend for details.
679
680 @tparam Ts The result types of the tasks being launched.
681 */
682 template<typename... Ts>
683 class when_any_launcher
684 {
685 /** Pointer to tuple of tasks to launch. */
686 std::tuple<task<Ts>...>* tasks_;
687
688 /** Pointer to shared state for coordination. */
689 when_any_state<Ts...>* state_;
690
691 public:
692 /** Construct launcher with task tuple and shared state.
693
694 @param tasks Pointer to tuple of tasks (must outlive the await).
695 @param state Pointer to shared state for winner coordination.
696 */
697 37 when_any_launcher(
698 std::tuple<task<Ts>...>* tasks,
699 when_any_state<Ts...>* state)
700 37 : tasks_(tasks)
701 37 , state_(state)
702 {
703 37 }
704
705 /** Check if the launcher can complete synchronously.
706
707 @return True only if there are no tasks (degenerate case).
708 */
709 37 bool await_ready() const noexcept
710 {
711 37 return sizeof...(Ts) == 0;
712 }
713
714 /** Launch all runner coroutines and suspend the parent.
715
716 Sets up stop propagation from parent to children, then launches
717 each task in a runner coroutine. Returns noop_coroutine because
718 runners resume the parent via signal_completion().
719
720 CRITICAL: If the last task finishes synchronously then the parent
721 coroutine resumes, destroying its frame, and destroying this object
722 prior to the completion of await_suspend. Therefore, await_suspend
723 must ensure `this` cannot be referenced after calling `launch_one`
724 for the last time.
725
726 @tparam Ex The executor type.
727 @param continuation Handle to the parent coroutine to resume later.
728 @param caller_ex Executor for dispatching child coroutines.
729 @param parent_token Stop token from the parent for cancellation propagation.
730 @return noop_coroutine; parent is resumed by the last completing task.
731 */
732 template<typename Ex>
733 37 coro await_suspend(coro continuation, Ex const& caller_ex, std::stop_token parent_token = {})
734 {
735 37 state_->continuation_ = continuation;
736 37 state_->caller_ex_ = caller_ex;
737
738 // Forward parent's stop requests to children
739
12/20
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int, int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 8 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 10 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<void, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<void, void, void>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
37 if(parent_token.stop_possible())
740 {
741 16 state_->parent_stop_callback_.emplace(
742 parent_token,
743 8 typename when_any_state<Ts...>::stop_callback_fn{&state_->stop_source_});
744
745
3/20
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int, int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 6 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<void, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<void, void, void>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
8 if(parent_token.stop_requested())
746 2 state_->stop_source_.request_stop();
747 }
748
749 37 auto token = state_->stop_source_.get_token();
750 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
751 (..., launch_one<Is>(caller_ex, token));
752
10/10
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int, int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 9 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 17 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<void, int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_launcher<void, void, void>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
37 }(std::index_sequence_for<Ts...>{});
753
754 74 return std::noop_coroutine();
755 37 }
756
757 /** Resume after all tasks complete.
758
759 No return value; results are accessed via the shared state.
760 */
761 37 void await_resume() const noexcept
762 {
763 37 }
764
765 private:
766 /** Launch a single runner coroutine for task at index I.
767
768 Creates the runner, configures its promise with state and executor,
769 stores its handle for cleanup, and dispatches it for execution.
770
771 @tparam I Compile-time index of the task in the tuple.
772 @tparam Ex The executor type.
773 @param caller_ex Executor for dispatching the runner.
774 @param token Stop token for cooperative cancellation.
775
776 @pre Ex::dispatch() and coro::resume() must not throw. If they do,
777 the coroutine handle may leak.
778 */
779 template<std::size_t I, typename Ex>
780 93 void launch_one(Ex const& caller_ex, std::stop_token token)
781 {
782
31/31
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<3ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<4ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<5ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<6ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<7ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int>::launch_one<3ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 9 times.
void boost::capy::detail::when_any_launcher<int, int, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 9 times.
void boost::capy::detail::when_any_launcher<int, int, int>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 9 times.
void boost::capy::detail::when_any_launcher<int, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 17 times.
void boost::capy::detail::when_any_launcher<int, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 17 times.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 2 times.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 2 times.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 2 times.
void boost::capy::detail::when_any_launcher<int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 2 times.
void boost::capy::detail::when_any_launcher<void, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 2 times.
void boost::capy::detail::when_any_launcher<void, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 2 times.
void boost::capy::detail::when_any_launcher<void, void, void>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<void, void, void>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
void boost::capy::detail::when_any_launcher<void, void, void>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 1 time.
93 auto runner = make_when_any_runner<I>(
783 93 std::move(std::get<I>(*tasks_)), state_);
784
785 93 auto h = runner.release();
786 93 h.promise().state_ = state_;
787 93 h.promise().index_ = I;
788 93 h.promise().ex_ = caller_ex;
789 93 h.promise().stop_token_ = token;
790
791 93 coro ch{h};
792 93 state_->runner_handles_[I] = ch;
793
62/62
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<3ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<4ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<5ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<6ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int, int, int, int, int>::launch_one<7ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int, int>::launch_one<3ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, int, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 9 times.
✓ Branch 4 taken 9 times.
void boost::capy::detail::when_any_launcher<int, int, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 9 times.
✓ Branch 4 taken 9 times.
void boost::capy::detail::when_any_launcher<int, int, int>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 9 times.
✓ Branch 4 taken 9 times.
void boost::capy::detail::when_any_launcher<int, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 17 times.
✓ Branch 4 taken 17 times.
void boost::capy::detail::when_any_launcher<int, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 17 times.
✓ Branch 4 taken 17 times.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
void boost::capy::detail::when_any_launcher<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
void boost::capy::detail::when_any_launcher<int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
void boost::capy::detail::when_any_launcher<void, int>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
void boost::capy::detail::when_any_launcher<void, int>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
void boost::capy::detail::when_any_launcher<void, void, void>::launch_one<0ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<void, void, void>::launch_one<1ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
void boost::capy::detail::when_any_launcher<void, void, void>::launch_one<2ul, boost::capy::executor_ref>(boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
93 caller_ex.dispatch(ch).resume();
794 93 }
795 };
796
797 } // namespace detail
798
799 /** Wait for the first task to complete.
800
801 Races multiple heterogeneous tasks concurrently and returns when the
802 first one completes. The result includes the winner's index and a
803 deduplicated variant containing the result value.
804
805 @par Example
806 @code
807 task<void> example() {
808 auto [index, result] = co_await when_any(
809 fetch_from_primary(), // task<Response>
810 fetch_from_backup() // task<Response>
811 );
812 // index is 0 or 1, result holds the winner's Response
813 auto response = std::get<Response>(result);
814 }
815 @endcode
816
817 @tparam T0 First task's result type.
818 @tparam Ts Remaining tasks' result types.
819 @param task0 The first task to race.
820 @param tasks Additional tasks to race concurrently.
821 @return A task yielding a pair of (winner_index, result_variant).
822
823 @par Key Features
824 @li All tasks are launched concurrently
825 @li Returns when first task completes (success or failure)
826 @li Stop is requested for all siblings
827 @li Waits for all siblings to complete before returning
828 @li If winner threw, that exception is rethrown
829 @li Void tasks contribute std::monostate to the variant
830 */
831 template<typename T0, typename... Ts>
832 [[nodiscard]] task<detail::when_any_result_t<T0, Ts...>>
833
10/10
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<int>, std::monostate, int>::type>, std::conditional<is_void_v<int>, std::monostate, int>::type, std::conditional<is_void_v<int>, std::monostate, int>::type, std::conditional<is_void_v<int>, std::monostate, int>::type, std::conditional<is_void_v<int>, std::monostate, int>::type, std::conditional<is_void_v<int>, std::monostate, int>::type, std::conditional<is_void_v<int>, std::monostate, int>::type, std::conditional<is_void_v<int>, std::monostate, int>::type>::type> > boost::capy::when_any<int, int, int, int, int, int, int, int>(boost::capy::task<int>, boost::capy::task<int>, boost::capy::task<int>, boost::capy::task<int>, boost::capy::task<int>, boost::capy::task<int>, boost::capy::task<int>, boost::capy::task<int>):
✓ Branch 1 taken 1 time.
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<int>, std::monostate, int>::type>, std::conditional<is_void_v<int>, std::monostate, int>::type, std::conditional<is_void_v<int>, std::monostate, int>::type, std::conditional<is_void_v<int>, std::monostate, int>::type>::type> > boost::capy::when_any<int, int, int, int>(boost::capy::task<int>, boost::capy::task<int>, boost::capy::task<int>, boost::capy::task<int>):
✓ Branch 1 taken 1 time.
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<int>, std::monostate, int>::type>, std::conditional<is_void_v<int>, std::monostate, int>::type, std::conditional<is_void_v<int>, std::monostate, int>::type>::type> > boost::capy::when_any<int, int, int>(boost::capy::task<int>, boost::capy::task<int>, boost::capy::task<int>):
✓ Branch 1 taken 9 times.
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<int>, std::monostate, int>::type>, std::conditional<is_void_v<int>, std::monostate, int>::type>::type> > boost::capy::when_any<int, int>(boost::capy::task<int>, boost::capy::task<int>):
✓ Branch 1 taken 17 times.
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<int>, std::monostate, int>::type>, std::conditional<is_void_v<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::monostate, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::type, std::conditional<is_void_v<double>, std::monostate, double>::type>::type> > boost::capy::when_any<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double>(boost::capy::task<int>, boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::capy::task<double>):
✓ Branch 1 taken 1 time.
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<int>, std::monostate, int>::type>, std::conditional<is_void_v<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::monostate, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::type, std::conditional<is_void_v<int>, std::monostate, int>::type>::type> > boost::capy::when_any<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>(boost::capy::task<int>, boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::capy::task<int>):
✓ Branch 1 taken 2 times.
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<int>, std::monostate, int>::type>, std::conditional<is_void_v<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::monostate, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::type>::type> > boost::capy::when_any<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(boost::capy::task<int>, boost::capy::task<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >):
✓ Branch 1 taken 1 time.
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<int>, std::monostate, int>::type>>::type> > boost::capy::when_any<int>(boost::capy::task<int>):
✓ Branch 1 taken 2 times.
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<void>, std::monostate, void>::type>, std::conditional<is_void_v<int>, std::monostate, int>::type>::type> > boost::capy::when_any<void, int>(boost::capy::task<void>, boost::capy::task<int>):
✓ Branch 1 taken 2 times.
boost::capy::task<std::pair<unsigned long, boost::capy::detail::deduplicate_impl<std::variant<std::conditional<is_void_v<void>, std::monostate, void>::type>, std::conditional<is_void_v<void>, std::monostate, void>::type, std::conditional<is_void_v<void>, std::monostate, void>::type>::type> > boost::capy::when_any<void, void, void>(boost::capy::task<void>, boost::capy::task<void>, boost::capy::task<void>):
✓ Branch 1 taken 1 time.
37 when_any(task<T0> task0, task<Ts>... tasks)
834 {
835 using result_type = detail::when_any_result_t<T0, Ts...>;
836
837 detail::when_any_state<T0, Ts...> state;
838 std::tuple<task<T0>, task<Ts>...> task_tuple(std::move(task0), std::move(tasks)...);
839
840 co_await detail::when_any_launcher<T0, Ts...>(&task_tuple, &state);
841
842 if(state.winner_exception_)
843 std::rethrow_exception(state.winner_exception_);
844
845 co_return result_type{state.winner_index_, std::move(*state.result_)};
846 74 }
847
848 /** Alias for when_any result type, useful for declaring callback signatures.
849
850 Provides a convenient public alias for the internal result type.
851 The result is a pair containing the winner's index and a deduplicated
852 variant holding the result value.
853
854 @par Example
855 @code
856 void on_complete(when_any_result_type<int, std::string> result);
857 @endcode
858
859 @tparam T0 First task's result type.
860 @tparam Ts Remaining tasks' result types.
861 */
862 template<typename T0, typename... Ts>
863 using when_any_result_type = detail::when_any_result_t<T0, Ts...>;
864
865 namespace detail {
866
867 /** Shared state for homogeneous when_any (vector overload).
868
869 Simpler than the heterogeneous version: uses std::optional<T> instead
870 of variant, and std::vector instead of std::array for runner handles.
871
872 @tparam T The common result type of all tasks.
873 */
874 template<typename T>
875 struct when_any_homogeneous_state
876 {
877 /** Counter for tasks still running.
878
879 Completion tracking - must wait for ALL tasks for proper cleanup.
880 */
881 std::atomic<std::size_t> remaining_count_;
882
883 /** Total number of tasks being raced. */
884 std::size_t task_count_;
885
886 /** Flag indicating whether a winner has been determined.
887
888 Winner tracking - first task to complete claims this.
889 */
890 std::atomic<bool> has_winner_{false};
891
892 /** Index of the winning task in the vector. */
893 std::size_t winner_index_{0};
894
895 /** Storage for the winner's result value.
896
897 Result storage - simple value, no variant needed.
898 */
899 std::optional<T> result_;
900
901 /** Exception thrown by the winner, if any. */
902 std::exception_ptr winner_exception_;
903
904 /** Handles to runner coroutines for cleanup.
905
906 Runner handles - destroyed in destructor.
907 */
908 std::vector<coro> runner_handles_;
909
910 /** Stop source for cancelling sibling tasks.
911
912 Stop propagation - requested when winner is found.
913 */
914 std::stop_source stop_source_;
915
916 /** Callback functor that forwards stop requests.
917
918 Connects parent's stop_token to our stop_source.
919 */
920 struct stop_callback_fn
921 {
922 /** Pointer to the stop source to signal. */
923 std::stop_source* source_;
924
925 /** Invoke the stop request on the source. */
926 2 void operator()() const noexcept { source_->request_stop(); }
927 };
928
929 /** Type alias for the stop callback registration. */
930 using stop_callback_t = std::stop_callback<stop_callback_fn>;
931
932 /** Optional callback linking parent's stop token to our stop source. */
933 std::optional<stop_callback_t> parent_stop_callback_;
934
935 /** Parent coroutine handle to resume when all children complete. */
936 coro continuation_;
937
938 /** Executor reference for dispatching the parent resumption. */
939 executor_ref caller_ex_;
940
941 /** Construct state for racing the given number of tasks.
942
943 @param count Number of tasks to race.
944 */
945 13 explicit when_any_homogeneous_state(std::size_t count)
946 13 : remaining_count_(count)
947 13 , task_count_(count)
948
2/2
✓ Branch 4 taken 13 times.
✓ Branch 7 taken 13 times.
39 , runner_handles_(count)
949 {
950 13 }
951
952 /** Destroy state and clean up runner coroutine handles.
953
954 All runners must have completed before destruction.
955 */
956 13 ~when_any_homogeneous_state()
957 {
958
2/2
✓ Branch 5 taken 47 times.
✓ Branch 6 taken 13 times.
60 for(auto h : runner_handles_)
959
1/2
✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
47 if(h)
960 47 h.destroy();
961 13 }
962
963 /** Attempt to become the winner.
964
965 @param index The task's index in the vector.
966 @return true if this task is now the winner, false if another won first.
967 */
968 47 bool try_win(std::size_t index) noexcept
969 {
970 47 bool expected = false;
971
2/2
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 34 times.
47 if(has_winner_.compare_exchange_strong(
972 expected, true, std::memory_order_acq_rel))
973 {
974 13 winner_index_ = index;
975 13 stop_source_.request_stop();
976 13 return true;
977 }
978 34 return false;
979 }
980
981 /** Store the winner's result.
982
983 @pre Only called by the winner (try_win returned true).
984 */
985 11 void set_winner_result(T value)
986 noexcept(std::is_nothrow_move_constructible_v<T>)
987 {
988 11 result_.emplace(std::move(value));
989 11 }
990
991 /** Store the winner's exception.
992
993 @pre Only called by the winner (try_win returned true).
994 */
995 2 void set_winner_exception(std::exception_ptr ep) noexcept
996 {
997 2 winner_exception_ = ep;
998 2 }
999
1000 /** Signal task completion; last one resumes the parent.
1001
1002 @return Coroutine to resume (parent if last, noop otherwise).
1003 */
1004 47 coro signal_completion() noexcept
1005 {
1006 47 auto remaining = remaining_count_.fetch_sub(1, std::memory_order_acq_rel);
1007
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 34 times.
47 if(remaining == 1)
1008 13 return caller_ex_.dispatch(continuation_);
1009 34 return std::noop_coroutine();
1010 }
1011 };
1012
1013 /** Specialization for void tasks (no result storage needed).
1014
1015 When racing void-returning tasks, there is no result value to store.
1016 Only the winner's index and any exception are tracked.
1017 */
1018 template<>
1019 struct when_any_homogeneous_state<void>
1020 {
1021 /** Counter for tasks still running. */
1022 std::atomic<std::size_t> remaining_count_;
1023
1024 /** Total number of tasks being raced. */
1025 std::size_t task_count_;
1026
1027 /** Flag indicating whether a winner has been determined. */
1028 std::atomic<bool> has_winner_{false};
1029
1030 /** Index of the winning task in the vector. */
1031 std::size_t winner_index_{0};
1032
1033 /** Exception thrown by the winner, if any. */
1034 std::exception_ptr winner_exception_;
1035
1036 /** Handles to runner coroutines for cleanup. */
1037 std::vector<coro> runner_handles_;
1038
1039 /** Stop source for cancelling sibling tasks. */
1040 std::stop_source stop_source_;
1041
1042 /** Callback functor that forwards stop requests. */
1043 struct stop_callback_fn
1044 {
1045 /** Pointer to the stop source to signal. */
1046 std::stop_source* source_;
1047
1048 /** Invoke the stop request on the source. */
1049 void operator()() const noexcept { source_->request_stop(); }
1050 };
1051
1052 /** Type alias for the stop callback registration. */
1053 using stop_callback_t = std::stop_callback<stop_callback_fn>;
1054
1055 /** Optional callback linking parent's stop token to our stop source. */
1056 std::optional<stop_callback_t> parent_stop_callback_;
1057
1058 /** Parent coroutine handle to resume when all children complete. */
1059 coro continuation_;
1060
1061 /** Executor reference for dispatching the parent resumption. */
1062 executor_ref caller_ex_;
1063
1064 /** Construct state for racing the given number of void tasks.
1065
1066 @param count Number of tasks to race.
1067 */
1068 2 explicit when_any_homogeneous_state(std::size_t count)
1069 2 : remaining_count_(count)
1070 2 , task_count_(count)
1071
2/2
✓ Branch 3 taken 2 times.
✓ Branch 6 taken 2 times.
6 , runner_handles_(count)
1072 {
1073 2 }
1074
1075 /** Destroy state and clean up runner coroutine handles. */
1076 2 ~when_any_homogeneous_state()
1077 {
1078
2/2
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 2 times.
7 for(auto h : runner_handles_)
1079
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 if(h)
1080 5 h.destroy();
1081 2 }
1082
1083 /** Attempt to become the winner.
1084
1085 @param index The task's index in the vector.
1086 @return true if this task is now the winner, false if another won first.
1087 */
1088 5 bool try_win(std::size_t index) noexcept
1089 {
1090 5 bool expected = false;
1091
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
5 if(has_winner_.compare_exchange_strong(
1092 expected, true, std::memory_order_acq_rel))
1093 {
1094 2 winner_index_ = index;
1095 2 stop_source_.request_stop();
1096 2 return true;
1097 }
1098 3 return false;
1099 }
1100
1101 /** Store the winner's exception.
1102
1103 @pre Only called by the winner (try_win returned true).
1104 */
1105 1 void set_winner_exception(std::exception_ptr ep) noexcept
1106 {
1107 1 winner_exception_ = ep;
1108 1 }
1109
1110 /** Signal task completion; last one resumes the parent.
1111
1112 @return Coroutine to resume (parent if last, noop otherwise).
1113 */
1114 5 coro signal_completion() noexcept
1115 {
1116 5 auto remaining = remaining_count_.fetch_sub(1, std::memory_order_acq_rel);
1117
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 if(remaining == 1)
1118 2 return caller_ex_.dispatch(continuation_);
1119 3 return std::noop_coroutine();
1120 }
1121 };
1122
1123 /** Wrapper coroutine for homogeneous when_any tasks (vector overload).
1124
1125 Same role as when_any_runner but uses a runtime index instead of
1126 a compile-time index, allowing it to work with vectors of tasks.
1127
1128 @tparam T The common result type of all tasks.
1129 */
1130 template<typename T>
1131 struct when_any_homogeneous_runner
1132 {
1133 /** Promise type for the homogeneous runner coroutine.
1134
1135 Manages executor propagation, stop token forwarding, and completion
1136 signaling for the wrapped child task.
1137 */
1138 struct promise_type : frame_allocating_base
1139 {
1140 /** Pointer to shared state for winner coordination. */
1141 when_any_homogeneous_state<T>* state_ = nullptr;
1142
1143 /** Runtime index of this task in the vector. */
1144 std::size_t index_ = 0;
1145
1146 /** Executor reference inherited from the parent coroutine. */
1147 executor_ref ex_;
1148
1149 /** Stop token for cooperative cancellation. */
1150 std::stop_token stop_token_;
1151
1152 /** Create the runner coroutine object from this promise.
1153
1154 @return Runner coroutine wrapping this promise's coroutine handle.
1155 */
1156 52 when_any_homogeneous_runner get_return_object()
1157 {
1158 return when_any_homogeneous_runner(
1159 52 std::coroutine_handle<promise_type>::from_promise(*this));
1160 }
1161
1162 /** Suspend immediately on creation.
1163
1164 Runner coroutines start suspended; the launcher resumes them
1165 after setting up state_, index_, ex_, and stop_token_.
1166
1167 @return Always suspends.
1168 */
1169 52 std::suspend_always initial_suspend() noexcept
1170 {
1171 52 return {};
1172 }
1173
1174 /** Final suspend awaiter that signals completion to shared state.
1175
1176 @return Custom awaiter that calls signal_completion().
1177 */
1178 52 auto final_suspend() noexcept
1179 {
1180 /** Awaiter that signals task completion and potentially resumes parent. */
1181 struct awaiter
1182 {
1183 /** Pointer to the promise for accessing shared state. */
1184 promise_type* p_;
1185
1186 /** Never ready; always suspend to signal completion.
1187
1188 @return Always false.
1189 */
1190 bool await_ready() const noexcept
1191 {
1192 return false;
1193 }
1194
1195 /** Signal completion and return next coroutine to resume.
1196
1197 @return Parent coroutine if this was the last task, noop otherwise.
1198 */
1199 coro await_suspend(coro) noexcept
1200 {
1201 return p_->state_->signal_completion();
1202 }
1203
1204 /** No-op resume; coroutine is destroyed after final suspend. */
1205 void await_resume() const noexcept
1206 {
1207 }
1208 };
1209 52 return awaiter{this};
1210 }
1211
1212 /** Called when runner coroutine body completes normally.
1213
1214 The actual result handling is done in make_when_any_homogeneous_runner;
1215 this just satisfies the coroutine return requirement.
1216 */
1217 49 void return_void()
1218 {
1219 49 }
1220
1221 /** Handle exceptions thrown by the child task.
1222
1223 Exceptions are valid completions in when_any. If this exception wins,
1224 it will be rethrown to the caller. If another task already won,
1225 this exception is discarded.
1226 */
1227 3 void unhandled_exception()
1228 {
1229
2/4
boost::capy::detail::when_any_homogeneous_runner<int>::promise_type::unhandled_exception():
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
boost::capy::detail::when_any_homogeneous_runner<void>::promise_type::unhandled_exception():
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
3 if(state_->try_win(index_))
1230 3 state_->set_winner_exception(std::current_exception());
1231 3 }
1232
1233 /** Awaiter wrapper that injects executor and stop token into child awaitables.
1234
1235 @tparam Awaitable The underlying awaitable type being wrapped.
1236 */
1237 template<class Awaitable>
1238 struct transform_awaiter
1239 {
1240 /** The wrapped awaitable instance. */
1241 std::decay_t<Awaitable> a_;
1242
1243 /** Pointer to promise for accessing executor and stop token. */
1244 promise_type* p_;
1245
1246 /** Check if the underlying awaitable is ready.
1247
1248 @return True if awaitable can complete synchronously.
1249 */
1250 52 bool await_ready()
1251 {
1252 52 return a_.await_ready();
1253 }
1254
1255 /** Get the result from the underlying awaitable.
1256
1257 @return The awaitable's result value.
1258 */
1259 52 auto await_resume()
1260 {
1261 52 return a_.await_resume();
1262 }
1263
1264 /** Suspend with executor and stop token injection.
1265
1266 @tparam Promise The suspending coroutine's promise type.
1267 @param h Handle to the suspending coroutine.
1268 @return Coroutine to resume or void.
1269 */
1270 template<class Promise>
1271 52 auto await_suspend(std::coroutine_handle<Promise> h)
1272 {
1273
2/2
auto boost::capy::detail::when_any_homogeneous_runner<int>::promise_type::transform_awaiter<boost::capy::task<int> >::await_suspend<boost::capy::detail::when_any_homogeneous_runner<int>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_homogeneous_runner<int>::promise_type>):
✓ Branch 3 taken 47 times.
auto boost::capy::detail::when_any_homogeneous_runner<void>::promise_type::transform_awaiter<boost::capy::task<void> >::await_suspend<boost::capy::detail::when_any_homogeneous_runner<void>::promise_type>(std::__n4861::coroutine_handle<boost::capy::detail::when_any_homogeneous_runner<void>::promise_type>):
✓ Branch 3 taken 5 times.
52 return a_.await_suspend(h, p_->ex_, p_->stop_token_);
1274 }
1275 };
1276
1277 /** Transform awaitables to inject executor and stop token.
1278
1279 @tparam Awaitable The awaitable type being co_awaited.
1280 @param a The awaitable instance.
1281 @return Transformed awaiter with executor/stop_token injection.
1282 */
1283 template<class Awaitable>
1284 52 auto await_transform(Awaitable&& a)
1285 {
1286 using A = std::decay_t<Awaitable>;
1287 if constexpr (IoAwaitable<A>)
1288 {
1289 return transform_awaiter<Awaitable>{
1290 104 std::forward<Awaitable>(a), this};
1291 }
1292 else
1293 {
1294 return make_affine(std::forward<Awaitable>(a), ex_);
1295 }
1296 52 }
1297 };
1298
1299 /** Handle to the underlying coroutine frame. */
1300 std::coroutine_handle<promise_type> h_;
1301
1302 /** Construct runner from a coroutine handle.
1303
1304 @param h Handle to the runner coroutine frame.
1305 */
1306 52 explicit when_any_homogeneous_runner(std::coroutine_handle<promise_type> h) noexcept
1307 52 : h_(h)
1308 {
1309 52 }
1310
1311 /** Move constructor (Clang 14 workaround).
1312
1313 Clang 14 (non-Apple) has a coroutine codegen bug requiring explicit
1314 move constructor; other compilers work correctly with deleted move.
1315 */
1316 #if defined(__clang__) && __clang_major__ == 14 && !defined(__apple_build_version__)
1317 when_any_homogeneous_runner(when_any_homogeneous_runner&& other) noexcept
1318 : h_(std::exchange(other.h_, nullptr)) {}
1319 #endif
1320
1321 /** Copy construction is not allowed. */
1322 when_any_homogeneous_runner(when_any_homogeneous_runner const&) = delete;
1323
1324 /** Copy assignment is not allowed. */
1325 when_any_homogeneous_runner& operator=(when_any_homogeneous_runner const&) = delete;
1326
1327 /** Move construction is deleted (except on Clang 14). */
1328 #if !defined(__clang__) || __clang_major__ != 14 || defined(__apple_build_version__)
1329 when_any_homogeneous_runner(when_any_homogeneous_runner&&) = delete;
1330 #endif
1331
1332 /** Move assignment is not allowed. */
1333 when_any_homogeneous_runner& operator=(when_any_homogeneous_runner&&) = delete;
1334
1335 /** Release ownership of the coroutine handle.
1336
1337 @return The coroutine handle; this object becomes empty.
1338 */
1339 52 auto release() noexcept
1340 {
1341 52 return std::exchange(h_, nullptr);
1342 }
1343 };
1344
1345 /** Create a runner coroutine for a homogeneous when_any task.
1346
1347 Factory function that creates a wrapper coroutine for a child task
1348 in the vector overload. Uses a runtime index instead of compile-time.
1349
1350 @tparam T The result type of the task being wrapped.
1351 @param inner The task to run (will be moved from).
1352 @param state Shared state for winner coordination.
1353 @param index Runtime index of this task in the vector.
1354 @return Runner coroutine (must be started via resume()).
1355 */
1356 template<typename T>
1357 when_any_homogeneous_runner<T>
1358
2/2
boost::capy::detail::when_any_homogeneous_runner<int> boost::capy::detail::make_when_any_homogeneous_runner<int>(boost::capy::task<int>, boost::capy::detail::when_any_homogeneous_state<int>*, unsigned long):
✓ Branch 1 taken 47 times.
boost::capy::detail::when_any_homogeneous_runner<void> boost::capy::detail::make_when_any_homogeneous_runner<void>(boost::capy::task<void>, boost::capy::detail::when_any_homogeneous_state<void>*, unsigned long):
✓ Branch 1 taken 5 times.
52 make_when_any_homogeneous_runner(task<T> inner, when_any_homogeneous_state<T>* state, std::size_t index)
1359 {
1360 if constexpr (std::is_void_v<T>)
1361 {
1362 co_await std::move(inner);
1363 state->try_win(index); // void tasks have no result to store
1364 }
1365 else
1366 {
1367 auto result = co_await std::move(inner);
1368 if(state->try_win(index))
1369 {
1370 try
1371 {
1372 state->set_winner_result(std::move(result));
1373 }
1374 catch(...)
1375 {
1376 state->set_winner_exception(std::current_exception());
1377 }
1378 }
1379 }
1380 104 }
1381
1382 /** Awaitable that launches all runners for homogeneous when_any.
1383
1384 Same lifetime concerns as when_any_launcher; see its documentation.
1385 Uses runtime iteration over the task vector instead of compile-time
1386 expansion over a tuple.
1387
1388 @tparam T The common result type of all tasks in the vector.
1389 */
1390 template<typename T>
1391 class when_any_homogeneous_launcher
1392 {
1393 /** Pointer to vector of tasks to launch. */
1394 std::vector<task<T>>* tasks_;
1395
1396 /** Pointer to shared state for coordination. */
1397 when_any_homogeneous_state<T>* state_;
1398
1399 public:
1400 /** Construct launcher with task vector and shared state.
1401
1402 @param tasks Pointer to vector of tasks (must outlive the await).
1403 @param state Pointer to shared state for winner coordination.
1404 */
1405 15 when_any_homogeneous_launcher(
1406 std::vector<task<T>>* tasks,
1407 when_any_homogeneous_state<T>* state)
1408 15 : tasks_(tasks)
1409 15 , state_(state)
1410 {
1411 15 }
1412
1413 /** Check if the launcher can complete synchronously.
1414
1415 @return True only if there are no tasks (degenerate case).
1416 */
1417 15 bool await_ready() const noexcept
1418 {
1419 15 return tasks_->empty();
1420 }
1421
1422 /** Launch all runner coroutines and suspend the parent.
1423
1424 Sets up stop propagation from parent to children, then launches
1425 each task in a runner coroutine. Returns noop_coroutine because
1426 runners resume the parent via signal_completion().
1427
1428 CRITICAL: If the last task finishes synchronously then the parent
1429 coroutine resumes, destroying its frame, and destroying this object
1430 prior to the completion of await_suspend. Therefore, await_suspend
1431 must ensure `this` cannot be referenced after calling `launch_one`
1432 for the last time.
1433
1434 @tparam Ex The executor type.
1435 @param continuation Handle to the parent coroutine to resume later.
1436 @param caller_ex Executor for dispatching child coroutines.
1437 @param parent_token Stop token from the parent for cancellation propagation.
1438 @return noop_coroutine; parent is resumed by the last completing task.
1439 */
1440 template<typename Ex>
1441 15 coro await_suspend(coro continuation, Ex const& caller_ex, std::stop_token parent_token = {})
1442 {
1443 15 state_->continuation_ = continuation;
1444 15 state_->caller_ex_ = caller_ex;
1445
1446 // Forward parent's stop requests to children
1447
3/4
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_homogeneous_launcher<int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 8 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_homogeneous_launcher<void>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
15 if(parent_token.stop_possible())
1448 {
1449 10 state_->parent_stop_callback_.emplace(
1450 parent_token,
1451 5 typename when_any_homogeneous_state<T>::stop_callback_fn{&state_->stop_source_});
1452
1453
2/4
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_homogeneous_launcher<int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_homogeneous_launcher<void>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
5 if(parent_token.stop_requested())
1454 2 state_->stop_source_.request_stop();
1455 }
1456
1457 15 auto num_tasks = tasks_->size();
1458 15 auto token = state_->stop_source_.get_token();
1459
4/4
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_homogeneous_launcher<int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 13 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_homogeneous_launcher<void>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
67 for(std::size_t i = 0; i < num_tasks; ++i)
1460
2/2
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_homogeneous_launcher<int>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 47 times.
std::__n4861::coroutine_handle<void> boost::capy::detail::when_any_homogeneous_launcher<void>::await_suspend<boost::capy::executor_ref>(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 5 times.
52 launch_one( i, caller_ex, token);
1461
1462 30 return std::noop_coroutine();
1463 15 }
1464
1465 /** Resume after all tasks complete.
1466
1467 No return value; results are accessed via the shared state.
1468 */
1469 15 void await_resume() const noexcept
1470 {
1471 15 }
1472
1473 private:
1474 /** Launch a single runner coroutine for task at the given index.
1475
1476 Creates the runner, configures its promise with state and executor,
1477 stores its handle for cleanup, and dispatches it for execution.
1478
1479 @tparam Ex The executor type.
1480 @param index Runtime index of the task in the vector.
1481 @param caller_ex Executor for dispatching the runner.
1482 @param token Stop token for cooperative cancellation.
1483
1484 @pre Ex::dispatch() and coro::resume() must not throw. If they do,
1485 the coroutine handle may leak.
1486 */
1487 template<typename Ex>
1488 52 void launch_one(std::size_t index, Ex const& caller_ex, std::stop_token token)
1489 {
1490
2/3
void boost::capy::detail::when_any_homogeneous_launcher<int>::launch_one<boost::capy::executor_ref>(unsigned long, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 47 times.
void boost::capy::detail::when_any_homogeneous_launcher<void>::launch_one<boost::capy::executor_ref>(unsigned long, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 2 taken 5 times.
✗ Branch 2 not taken.
52 auto runner = make_when_any_homogeneous_runner(
1491 52 std::move((*tasks_)[index]), state_, index);
1492
1493 52 auto h = runner.release();
1494 52 h.promise().state_ = state_;
1495 52 h.promise().index_ = index;
1496 52 h.promise().ex_ = caller_ex;
1497 52 h.promise().stop_token_ = token;
1498
1499 52 coro ch{h};
1500 52 state_->runner_handles_[index] = ch;
1501
4/4
void boost::capy::detail::when_any_homogeneous_launcher<int>::launch_one<boost::capy::executor_ref>(unsigned long, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 47 times.
✓ Branch 4 taken 47 times.
void boost::capy::detail::when_any_homogeneous_launcher<void>::launch_one<boost::capy::executor_ref>(unsigned long, boost::capy::executor_ref const&, std::stop_token):
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
52 caller_ex.dispatch(ch).resume();
1502 52 }
1503 };
1504
1505 } // namespace detail
1506
1507 /** Wait for the first task to complete (homogeneous overload).
1508
1509 Races a vector of tasks with the same result type. Simpler than the
1510 heterogeneous overload: returns a direct pair instead of a variant
1511 since all tasks share the same type.
1512
1513 @par Example
1514 @code
1515 task<void> example() {
1516 std::vector<task<Response>> requests;
1517 requests.push_back(fetch_from_server(0));
1518 requests.push_back(fetch_from_server(1));
1519 requests.push_back(fetch_from_server(2));
1520
1521 auto [index, response] = co_await when_any(std::move(requests));
1522 // index is 0, 1, or 2; response is the winner's Response
1523 }
1524 @endcode
1525
1526 @tparam T The common result type of all tasks (must not be void).
1527 @param tasks Vector of tasks to race concurrently (must not be empty).
1528 @return A task yielding a pair of (winner_index, result).
1529 @throws std::invalid_argument if tasks is empty.
1530
1531 @par Key Features
1532 @li All tasks are launched concurrently
1533 @li Returns when first task completes (success or failure)
1534 @li Stop is requested for all siblings
1535 @li Waits for all siblings to complete before returning
1536 @li If winner threw, that exception is rethrown
1537 @li Returns simple pair (no variant needed for homogeneous types)
1538 */
1539 template<typename T>
1540 requires (!std::is_void_v<T>)
1541 [[nodiscard]] task<std::pair<std::size_t, T>>
1542
1/1
✓ Branch 1 taken 14 times.
14 when_any(std::vector<task<T>> tasks)
1543 {
1544 if(tasks.empty())
1545 throw std::invalid_argument("when_any requires at least one task");
1546
1547 using result_type = std::pair<std::size_t, T>;
1548
1549 detail::when_any_homogeneous_state<T> state(tasks.size());
1550
1551 co_await detail::when_any_homogeneous_launcher<T>(&tasks, &state);
1552
1553 if(state.winner_exception_)
1554 std::rethrow_exception(state.winner_exception_);
1555
1556 co_return result_type{state.winner_index_, std::move(*state.result_)};
1557 28 }
1558
1559 /** Wait for the first task to complete (homogeneous void overload).
1560
1561 Races a vector of void-returning tasks. Since void tasks have no
1562 result value, only the winner's index is returned.
1563
1564 @param tasks Vector of void tasks to race concurrently (must not be empty).
1565 @return A task yielding the winner's index (zero-based).
1566 @throws std::invalid_argument if tasks is empty.
1567
1568 @par Key Features
1569 @li All tasks are launched concurrently
1570 @li Returns when first task completes (success or failure)
1571 @li Stop is requested for all siblings
1572 @li Waits for all siblings to complete before returning
1573 @li If winner threw, that exception is rethrown
1574 */
1575 [[nodiscard]] inline task<std::size_t>
1576
1/1
✓ Branch 1 taken 2 times.
2 when_any(std::vector<task<void>> tasks)
1577 {
1578 if(tasks.empty())
1579 throw std::invalid_argument("when_any requires at least one task");
1580
1581 detail::when_any_homogeneous_state<void> state(tasks.size());
1582
1583 co_await detail::when_any_homogeneous_launcher<void>(&tasks, &state);
1584
1585 if(state.winner_exception_)
1586 std::rethrow_exception(state.winner_exception_);
1587
1588 co_return state.winner_index_;
1589 4 }
1590
1591 /** Alias for vector when_any result type.
1592
1593 For homogeneous when_any (vector overload), the result is simpler:
1594 a pair of the winner's index and the result value directly (no variant
1595 needed since all tasks share the same type).
1596
1597 @par Example
1598 @code
1599 void on_complete(when_any_vector_result_type<Response> result);
1600 @endcode
1601
1602 @tparam T The common result type of all tasks in the vector.
1603 */
1604 template<typename T>
1605 using when_any_vector_result_type = std::pair<std::size_t, T>;
1606
1607 } // namespace capy
1608 } // namespace boost
1609
1610 #endif
1611