Skip to content

Commit 994cdc8

Browse files
authored
Merge pull request #2124 from RobertLeahy/when_all_20260623
when_all: Conditional Stop Source
2 parents ae92130 + 96a4e55 commit 994cdc8

2 files changed

Lines changed: 256 additions & 60 deletions

File tree

include/stdexec/__detail/__when_all.hpp

Lines changed: 120 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,15 @@ namespace STDEXEC
5151
//! their value datums.
5252
//!
5353
//! @c when_all is the canonical *parallel composition* primitive in the
54-
//! sender model. You give it one or more senders; it returns a single
54+
//! sender model. You give it zero or more senders; it returns a single
5555
//! sender that, when connected and started, starts *all* of the input
5656
//! senders concurrently. When every input has completed, @c when_all's
5757
//! sender completes with a value tuple that is the concatenation of every
5858
//! input's value datums.
5959
//!
60-
//! If any one input fails or is stopped, @c when_all requests stop on the
61-
//! others (via an internal @c inplace_stop_source) and completes with
62-
//! that error (or with @c set_stopped). This makes @c when_all naturally
63-
//! fail-fast: as soon as one branch has gone bad, the rest are asked to
64-
//! wind down.
60+
//! If any one input can fail or be stopped, @c when_all uses an internal
61+
//! @c inplace_stop_source. An unhappy completion requests stop on the
62+
//! other inputs before the combined operation completes.
6563
//!
6664
//! @code{.cpp}
6765
//! auto s = stdexec::when_all(
@@ -101,8 +99,7 @@ namespace STDEXEC
10199
//! set_value_t(V1..., V2..., ..., Vn...) // concatenation of every input
102100
//! set_error_t(Eij)... // union across all inputs
103101
//! set_error_t(std::exception_ptr) // added if any decay-copy may throw
104-
//! set_stopped_t() // added if any input has it,
105-
//! // or if cancellation may happen
102+
//! set_stopped_t() // added if any input has it
106103
//! @endcode
107104
//!
108105
//! The value datums of each input are decay-copied into the resulting
@@ -163,19 +160,34 @@ namespace STDEXEC
163160
//! input has completed.
164161
//!
165162
//! @tparam _Senders A pack of types each satisfying @c stdexec::sender.
166-
//! Must be non-empty. Each must have exactly one
167-
//! @c set_value_t completion signature in the
168-
//! ambient environment.
163+
//! Each must have exactly one @c set_value_t completion
164+
//! signature in the ambient environment.
169165
//!
170166
//! @param __sndrs The senders to compose. Forwarded into the result.
171167
//!
172-
//! @returns A sender that, when connected and started, concurrently
173-
//! starts every input and value-completes with the
174-
//! concatenation of the input's value datums.
175-
template <sender... _Senders>
176-
constexpr auto operator()(_Senders&&... __sndrs) const -> __well_formed_sender auto
168+
//! @returns @c just() for no inputs, the input sender for one input, or a
169+
//! sender that concurrently starts every input and concatenates
170+
//! their value datums for two or more inputs.
171+
constexpr auto operator()() const noexcept
172+
{
173+
return just();
174+
}
175+
176+
template <sender _Sender>
177+
constexpr auto operator()(_Sender&& __sndr) const noexcept(__nothrow_decay_copyable<_Sender>)
177178
{
178-
return __make_sexpr<when_all_t>(__(), static_cast<_Senders&&>(__sndrs)...);
179+
return static_cast<_Sender&&>(__sndr);
180+
}
181+
182+
template <sender _Sender0, sender _Sender1, sender... _Senders>
183+
constexpr auto operator()(_Sender0&& __sndr0, _Sender1&& __sndr1, _Senders&&... __sndrs) const
184+
noexcept(__nothrow_decay_copyable<_Sender0, _Sender1, _Senders...>) -> __well_formed_sender
185+
auto
186+
{
187+
return __make_sexpr<when_all_t>(__(),
188+
static_cast<_Sender0&&>(__sndr0),
189+
static_cast<_Sender1&&>(__sndr1),
190+
static_cast<_Senders&&>(__sndrs)...);
179191
}
180192
};
181193

@@ -394,8 +406,8 @@ namespace STDEXEC
394406
}
395407

396408
template <class _Env>
397-
using __env_t = decltype(__when_all::__mk_env(__declval<_Env>(),
398-
__declval<inplace_stop_source&>()));
409+
using __stoppable_env_t = decltype(__when_all::__mk_env(__declval<_Env>(),
410+
__declval<inplace_stop_source&>()));
399411

400412
template <class _Sender, class _Env>
401413
concept __max1_sender =
@@ -423,6 +435,17 @@ namespace STDEXEC
423435
using __nothrow_decay_copyable_results_t =
424436
STDEXEC::__nothrow_decay_copyable_results_t<__completion_signatures_of_t<_Sender, _Env...>>;
425437

438+
template <class _Sender, class _Env>
439+
inline constexpr bool __can_fail = !__never_sends<set_error_t, _Sender, _Env>
440+
|| sends_stopped<_Sender, _Env>
441+
|| !__nothrow_decay_copyable_results_t<_Sender, _Env>::value;
442+
443+
template <class _Env, class... _Senders>
444+
inline constexpr bool __uses_stop_source = (__can_fail<_Senders, _Env> || ...);
445+
446+
template <class _Env, class... _Senders>
447+
using __env_t = __if_c<__uses_stop_source<_Env, _Senders...>, __stoppable_env_t<_Env>, _Env>;
448+
426449
template <class... _Env>
427450
struct __completions
428451
{
@@ -460,6 +483,23 @@ namespace STDEXEC
460483
__concat_completion_signatures_t>...>;
461484
};
462485

486+
template <class... _Env>
487+
struct __completions_for;
488+
489+
template <>
490+
struct __completions_for<>
491+
{
492+
template <class... _Senders>
493+
using __f = __completions<>::template __f<_Senders...>;
494+
};
495+
496+
template <class _Env>
497+
struct __completions_for<_Env>
498+
{
499+
template <class... _Senders>
500+
using __f = __completions<__env_t<_Env, _Senders...>>::template __f<_Senders...>;
501+
};
502+
463503
template <class _Receiver, class _ValuesTuple>
464504
constexpr void __set_values(_Receiver& __rcvr, _ValuesTuple& __values) noexcept
465505
{
@@ -472,29 +512,33 @@ namespace STDEXEC
472512
static_cast<_ValuesTuple&&>(__values));
473513
}
474514

475-
template <class _Env, class _Sender>
476-
using __values_opt_tuple_t =
477-
value_types_of_t<_Sender, __env_t<_Env>, __decayed_tuple, __optional>;
515+
template <class _ChildEnv, class _Sender>
516+
using __values_opt_tuple_t = value_types_of_t<_Sender, _ChildEnv, __decayed_tuple, __optional>;
478517

479-
template <class _Env, __max1_sender<__env_t<_Env>>... _Senders>
518+
template <class _Env, class... _Senders>
519+
requires(__max1_sender<_Senders, __env_t<_Env, _Senders...>> && ...)
480520
struct __traits
481521
{
522+
using __child_env = __env_t<_Env, _Senders...>;
523+
482524
// tuple<optional<tuple<Vs1...>>, optional<tuple<Vs2...>>, ...>
483-
using __values_tuple = __minvoke<
484-
__mwith_default<__mtransform<__mbind_front_q<__values_opt_tuple_t, _Env>, __q<__tuple>>,
485-
__ignore>,
486-
_Senders...>;
525+
using __values_tuple =
526+
__minvoke<__mwith_default<
527+
__mtransform<__mbind_front_q<__values_opt_tuple_t, __child_env>, __q<__tuple>>,
528+
__ignore>,
529+
_Senders...>;
487530

488531
using __collect_errors = __mbind_front_q<__mset_insert, __mset<>>;
489532

490533
using __errors_list =
491534
__minvoke<__mconcat<>,
492-
__if<__mand<__nothrow_decay_copyable_results_t<_Senders, _Env>...>,
535+
__if<__mand<__nothrow_decay_copyable_results_t<_Senders, __child_env>...>,
493536
__mlist<>,
494537
__mlist<std::exception_ptr>>,
495-
__error_types_of_t<_Senders, __env_t<_Env>, __q<__mlist>>...>;
538+
__error_types_of_t<_Senders, __child_env, __q<__mlist>>...>;
496539

497-
using __errors_variant = __mapply<__q<__uniqued_variant>, __errors_list>;
540+
using __errors_variant = __mapply<__q<__uniqued_variant>, __errors_list>;
541+
static constexpr bool __uses_stop_source = __when_all::__uses_stop_source<_Env, _Senders...>;
498542
};
499543

500544
struct _INVALID_ARGUMENTS_TO_WHEN_ALL_
@@ -515,7 +559,10 @@ namespace STDEXEC
515559
// error state, which trumps cancellation.)
516560
if (__state_->__state_.compare_exchange_strong(__expected, __stopped))
517561
{
518-
__state_->__stop_source_.request_stop();
562+
if constexpr (_State::__uses_stop_source)
563+
{
564+
__state_->__stop_source_.request_stop();
565+
}
519566
}
520567

521568
// Arrive in order to decrement the count again and complete if needed.
@@ -525,12 +572,19 @@ namespace STDEXEC
525572
_State* __state_;
526573
};
527574

528-
template <class _ErrorsVariant, class _ValuesTuple, class _Receiver, bool _SendsStopped>
575+
template <class _ErrorsVariant,
576+
class _ValuesTuple,
577+
class _Receiver,
578+
bool _SendsStopped,
579+
bool _UsesStopSource>
529580
struct __state
530581
{
531-
using __receiver_t = _Receiver;
582+
using __receiver_t = _Receiver;
583+
static constexpr bool __uses_stop_source = _UsesStopSource;
532584
using __stop_callback_t =
533585
stop_callback_for_t<stop_token_of_t<env_of_t<_Receiver>>, __forward_stop_request<__state>>;
586+
using __stop_source_t = __if_c<_UsesStopSource, inplace_stop_source, __empty>;
587+
using __on_stop_t = __if_c<_UsesStopSource, __optional<__stop_callback_t>, __empty>;
534588

535589
constexpr void __arrive() noexcept
536590
{
@@ -543,7 +597,10 @@ namespace STDEXEC
543597
constexpr void __complete() noexcept
544598
{
545599
// Stop callback is no longer needed. Destroy it.
546-
__on_stop_.reset();
600+
if constexpr (_UsesStopSource)
601+
{
602+
__on_stop_.reset();
603+
}
547604
// All child operations have completed and arrived at the barrier.
548605
switch (__state_.load(__std::memory_order_relaxed))
549606
{
@@ -579,13 +636,15 @@ namespace STDEXEC
579636
STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
580637
_Receiver __rcvr_;
581638
__std::atomic<std::size_t> __count_;
582-
inplace_stop_source __stop_source_{};
639+
STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
640+
__stop_source_t __stop_source_{};
583641
// Could be non-atomic here and atomic_ref everywhere except __completion_fn
584-
__std::atomic<__state_t> __state_{__started};
585-
_ErrorsVariant __errors_{__no_init};
642+
__std::atomic<__state_t> __state_{__started};
643+
_ErrorsVariant __errors_{__no_init};
644+
STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
645+
_ValuesTuple __values_{};
586646
STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
587-
_ValuesTuple __values_{};
588-
__optional<__stop_callback_t> __on_stop_{};
647+
__on_stop_t __on_stop_{};
589648
};
590649

591650
template <class... _Senders>
@@ -622,18 +681,12 @@ namespace STDEXEC
622681
}
623682
};
624683

625-
// A when_all with no senders completes inline with no values.
626684
template <>
627685
struct __attrs<>
628686
{
687+
template <class _Tag>
629688
[[nodiscard]]
630-
constexpr auto query(__get_completion_behavior_t<set_value_t>) const noexcept
631-
{
632-
return __completion_behavior::__inline_completion;
633-
}
634-
635-
[[nodiscard]]
636-
constexpr auto query(__get_completion_behavior_t<set_stopped_t>) const noexcept
689+
constexpr auto query(__get_completion_behavior_t<_Tag>) const noexcept
637690
{
638691
return __completion_behavior::__inline_completion;
639692
}
@@ -642,17 +695,18 @@ namespace STDEXEC
642695
template <class _Receiver>
643696
static constexpr auto __mk_state_fn(_Receiver&& __rcvr) noexcept
644697
{
645-
return [&]<__max1_sender<__env_t<env_of_t<_Receiver>>>... _Child>(__ignore,
646-
__ignore,
647-
_Child&&...) noexcept
698+
return [&]<class... _Child>(__ignore, __ignore, _Child&&...) noexcept
699+
requires(__max1_sender<_Child, __env_t<env_of_t<_Receiver>, _Child...>> && ...)
648700
{
649701
using _Traits = __traits<env_of_t<_Receiver>, _Child...>;
650702
using _ErrorsVariant = _Traits::__errors_variant;
651703
using _ValuesTuple = _Traits::__values_tuple;
704+
using _ChildEnv = _Traits::__child_env;
652705
using _State = __state<_ErrorsVariant,
653706
_ValuesTuple,
654707
_Receiver,
655-
(sends_stopped<_Child, env_of_t<_Receiver>> || ...)>;
708+
(sends_stopped<_Child, _ChildEnv> || ...),
709+
_Traits::__uses_stop_source>;
656710
return _State{static_cast<_Receiver&&>(__rcvr), sizeof...(_Child)};
657711
};
658712
}
@@ -663,7 +717,7 @@ namespace STDEXEC
663717
struct __when_all_impl : __sexpr_defaults
664718
{
665719
template <class _Self, class... _Env>
666-
using __completions_t = __children_of<_Self, __when_all::__completions<__env_t<_Env>...>>;
720+
using __completions_t = __children_of<_Self, __when_all::__completions_for<_Env...>>;
667721

668722
static constexpr auto __get_attrs =
669723
[]<class... _Child>(__ignore, __ignore, _Child const &...) noexcept
@@ -694,9 +748,15 @@ namespace STDEXEC
694748
}
695749

696750
static constexpr auto __get_env = []<class _State>(__ignore, _State const & __state) noexcept
697-
-> __env_t<env_of_t<typename _State::__receiver_t const &>>
698751
{
699-
return __when_all::__mk_env(STDEXEC::get_env(__state.__rcvr_), __state.__stop_source_);
752+
if constexpr (_State::__uses_stop_source)
753+
{
754+
return __when_all::__mk_env(STDEXEC::get_env(__state.__rcvr_), __state.__stop_source_);
755+
}
756+
else
757+
{
758+
return STDEXEC::get_env(__state.__rcvr_);
759+
}
700760
};
701761

702762
static constexpr auto __get_state =
@@ -711,19 +771,18 @@ namespace STDEXEC
711771
[]<class _State, class... _Operations>(_State& __state,
712772
_Operations&... __child_ops) noexcept -> void
713773
{
714-
// register stop callback:
715-
__state.__on_stop_.emplace(get_stop_token(STDEXEC::get_env(__state.__rcvr_)),
716-
__forward_stop_request<_State>{&__state});
717-
(STDEXEC::start(__child_ops), ...);
718-
if constexpr (sizeof...(__child_ops) == 0)
774+
if constexpr (_State::__uses_stop_source)
719775
{
720-
__state.__complete();
776+
__state.__on_stop_.emplace(get_stop_token(STDEXEC::get_env(__state.__rcvr_)),
777+
__forward_stop_request<_State>{&__state});
721778
}
779+
(STDEXEC::start(__child_ops), ...);
722780
};
723781

724782
template <class _State, class _Error>
725783
static constexpr void __set_error(_State& __state, _Error&& __err) noexcept
726784
{
785+
static_assert(_State::__uses_stop_source);
727786
// Transition to the "error" state and switch on the prior state.
728787
// TODO: What memory orderings are actually needed here?
729788
switch (__state.__state_.exchange(__error))
@@ -769,6 +828,7 @@ namespace STDEXEC
769828
}
770829
else if constexpr (__same_as<_Set, set_stopped_t>)
771830
{
831+
static_assert(_State::__uses_stop_source);
772832
__state_t __expected = __started;
773833
// Transition to the "stopped" state if and only if we're in the
774834
// "started" state. (If this fails, it's because we're in an

0 commit comments

Comments
 (0)