From 46c9c105c828cf00650eb42061433e9832e6c1a7 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 20 Sep 2026 01:47:38 +0000 Subject: [PATCH] update the start scheduler before connecting scheduler sender in continues_on fixes #2268 --- include/exec/reschedule.hpp | 4 +- include/stdexec/__detail/__affine.hpp | 2 +- include/stdexec/__detail/__continues_on.hpp | 83 ++++++--- .../stdexec/__detail/__counting_scopes.hpp | 2 +- include/stdexec/__detail/__diagnostics.hpp | 5 +- include/stdexec/__detail/__env.hpp | 7 +- include/stdexec/__detail/__on.hpp | 168 ++++++++---------- include/stdexec/__detail/__schedulers.hpp | 1 + test/CMakeLists.txt | 1 + test/exec/test_completion_signatures.cpp | 2 +- test/stdexec/algos/adaptors/test_on3.cpp | 5 +- .../queries/test_get_start_scheduler.cpp | 121 +++++++++++++ 12 files changed, 262 insertions(+), 139 deletions(-) create mode 100644 test/stdexec/queries/test_get_start_scheduler.cpp diff --git a/include/exec/reschedule.hpp b/include/exec/reschedule.hpp index 736e90f01..1133e1123 100644 --- a/include/exec/reschedule.hpp +++ b/include/exec/reschedule.hpp @@ -21,7 +21,7 @@ namespace experimental::execution { struct _CANNOT_RESCHEDULE_ {}; - using STDEXEC::_THE_CURRENT_EXECUTION_ENVIRONMENT_DOESNT_HAVE_A_SCHEDULER_; + using STDEXEC::_THE_CURRENT_EXECUTION_ENVIRONMENT_DOES_NOT_HAVE_A_START_SCHEDULER_; namespace __resched { @@ -31,7 +31,7 @@ namespace experimental::execution template using __no_scheduler_error = __mexception<_WHAT_(_CANNOT_RESCHEDULE_), - _WHY_(_THE_CURRENT_EXECUTION_ENVIRONMENT_DOESNT_HAVE_A_SCHEDULER_), + _WHY_(_THE_CURRENT_EXECUTION_ENVIRONMENT_DOES_NOT_HAVE_A_START_SCHEDULER_), _WHERE_(_IN_ALGORITHM_, reschedule_t), _WITH_ENVIRONMENT_(_Env)>; diff --git a/include/stdexec/__detail/__affine.hpp b/include/stdexec/__detail/__affine.hpp index 0ee56019e..e5f10b3ce 100644 --- a/include/stdexec/__detail/__affine.hpp +++ b/include/stdexec/__detail/__affine.hpp @@ -109,7 +109,7 @@ namespace STDEXEC // sender to be affine. Instead, return a type describing the problem. return __not_a_sender< // _WHAT_(_CANNOT_MAKE_SENDER_AFFINE_TO_THE_STARTING_SCHEDULER_), - _WHY_(_THE_CURRENT_EXECUTION_ENVIRONMENT_DOESNT_HAVE_A_SCHEDULER_), + _WHY_(_THE_CURRENT_EXECUTION_ENVIRONMENT_DOES_NOT_HAVE_A_START_SCHEDULER_), _WHERE_(_IN_ALGORITHM_, affine_t), _WITH_PRETTY_SENDER_<__cv_child_t>, _WITH_ENVIRONMENT_(_Env)>{}; diff --git a/include/stdexec/__detail/__continues_on.hpp b/include/stdexec/__detail/__continues_on.hpp index f1f805c07..c3480e99e 100644 --- a/include/stdexec/__detail/__continues_on.hpp +++ b/include/stdexec/__detail/__continues_on.hpp @@ -48,13 +48,20 @@ namespace STDEXEC // [exec.continues.on] namespace __trnsfr { - template + template struct __state_base { - using __storage_t = __storage_for_t<__child_of<_Sexpr>, env_of_t<_Receiver>>; + using __env2_t = __secondary_env_t<_Sender, env_of_t<_Receiver>, set_value_t>; + using __storage_t = __storage_for_t<_Sender, env_of_t<_Receiver>>; - _Receiver __rcvr_; - __storage_t __data_; + constexpr __state_base(_Sender const & __child, _Receiver&& __rcvr) noexcept + : __rcvr_(static_cast<_Receiver&&>(__rcvr)) + , __env2_(__mk_secondary_env_t()(__child, get_env(__rcvr_))) + {} + + _Receiver __rcvr_; + __env2_t const __env2_; + __storage_t __data_; }; // This receiver is to be completed on the execution context associated with @@ -63,10 +70,12 @@ namespace STDEXEC // receiver completes, it can read the completion out of the operation state // and forward it to the output receiver after transitioning to the // scheduler's context. - template + template struct __receiver2 { using receiver_concept = receiver_tag; + using __env2_t = __state_base<_Sender, _Receiver>::__env2_t; + using __sch_env_t = __join_env_t<__env2_t const &, env_of_t<_Receiver>>; constexpr void set_value() noexcept { @@ -86,24 +95,24 @@ namespace STDEXEC } [[nodiscard]] - constexpr auto get_env() const noexcept -> env_of_t<_Receiver> + constexpr auto get_env() const noexcept -> __sch_env_t { - return STDEXEC::get_env(__state_->__rcvr_); + return __env::__join(__state_->__env2_, STDEXEC::get_env(__state_->__rcvr_)); } - __state_base<_Sexpr, _Receiver>* __state_; + __state_base<_Sender, _Receiver>* __state_; }; - template - struct __state : __state_base<_Sexpr, _Receiver> + template + struct __state : __state_base<_Sender, _Receiver> { - using __receiver2_t = __receiver2<_Sexpr, _Receiver>; + using __receiver2_t = __receiver2<_Sender, _Receiver>; using __schedule_sender_t = schedule_result_t<_Scheduler&>; - constexpr explicit __state(_Scheduler __sched, _Receiver&& __rcvr) + constexpr explicit __state(_Scheduler __sched, _Sender const & __child, _Receiver&& __rcvr) noexcept(__nothrow_callable && __nothrow_connectable<__schedule_sender_t, __receiver2_t>) - : __state::__state_base{static_cast<_Receiver&&>(__rcvr)} + : __state::__state_base{__child, static_cast<_Receiver&&>(__rcvr)} , __state2_(connect(schedule(__sched), __receiver2_t{this})) {} STDEXEC_IMMOVABLE(__state); @@ -116,6 +125,18 @@ namespace STDEXEC struct __attrs { private: + template + using __env2_t = __secondary_env_t<_Sender, _Env, set_value_t>; + template + using __sch_env_t = __join_env_t<__env2_t<_Env>, _Env>; + + template + constexpr auto __mk_sch_env(_Env&& __env) const noexcept -> __sch_env_t<_Env> + { + return __env::__join(__mk_secondary_env_t()(__sndr_, __env), + static_cast<_Env&&>(__env)); + } + //! @brief Returns `true` when: //! - _SetTag is set_error_t, and //! - _Sender has value completions, and @@ -138,13 +159,13 @@ namespace STDEXEC return false; } - _Scheduler __sch_; - env_of_t<_Sender> __attrs_; + _Scheduler __sch_; + _Sender const & __sndr_; public: - constexpr explicit __attrs(_Scheduler __sch, env_of_t<_Sender> __attrs) noexcept + constexpr explicit __attrs(_Scheduler __sch, _Sender const & __sndr) noexcept : __sch_(static_cast<_Scheduler&&>(__sch)) - , __attrs_(static_cast&&>(__attrs)) + , __sndr_(__sndr) {} //! @brief Queries the completion scheduler for a given @c _SetTag. @@ -171,14 +192,14 @@ namespace STDEXEC [[nodiscard]] constexpr auto query(get_completion_scheduler_t<_SetTag>, _Env const &... __env) const noexcept - -> __call_result_t, _Scheduler, __fwd_env_t<_Env>...> + -> __call_result_t, _Scheduler, __sch_env_t<_Env>...> { - return get_completion_scheduler<_SetTag>(__sch_, __fwd_env(__env)...); + return get_completion_scheduler<_SetTag>(__sch_, __mk_sch_env(__env)...); } //! @overload template - requires __never_sends<_SetTag, schedule_result_t<_Scheduler>, __fwd_env_t<_Env>...> + requires __never_sends<_SetTag, schedule_result_t<_Scheduler>, __sch_env_t<_Env>...> [[nodiscard]] constexpr auto query(get_completion_scheduler_t<_SetTag>, _Env const &... __env) const noexcept @@ -186,7 +207,7 @@ namespace STDEXEC env_of_t<_Sender>, __fwd_env_t<_Env>...> { - return get_completion_scheduler<_SetTag>(__attrs_, __fwd_env(__env)...); + return get_completion_scheduler<_SetTag>(get_env(__sndr_), __fwd_env(__env)...); } //! @brief Queries the completion domain for a given @c _SetTag. @@ -209,7 +230,7 @@ namespace STDEXEC [[nodiscard]] constexpr auto query(get_completion_domain_t<_SetTag>, _Env const &...) const noexcept -> __unless_one_of_t< - __completion_domain_of_t<_SetTag, schedule_result_t<_Scheduler>, __fwd_env_t<_Env>...>, + __completion_domain_of_t<_SetTag, schedule_result_t<_Scheduler>, __env2_t<_Env>...>, indeterminate_domain<>> { return {}; @@ -223,7 +244,7 @@ namespace STDEXEC query(get_completion_domain_t<_SetTag>, _Env const &...) const noexcept -> __unless_one_of_t< __common_domain_t< __completion_domain_of_t<_SetTag, _Sender, __fwd_env_t<_Env>...>, - __completion_domain_of_t<_SetTag, schedule_result_t<_Scheduler>, __fwd_env_t<_Env>...>>, + __completion_domain_of_t<_SetTag, schedule_result_t<_Scheduler>, __env2_t<_Env>...>>, indeterminate_domain<>> { return {}; @@ -237,7 +258,7 @@ namespace STDEXEC query(get_completion_domain_t<_SetTag>, _Env const &...) const noexcept -> __unless_one_of_t< __common_domain_t< __completion_domain_of_t<_SetTag, _Sender, __fwd_env_t<_Env>...>, - __completion_domain_of_t<_SetTag, schedule_result_t<_Scheduler>, __fwd_env_t<_Env>...>, + __completion_domain_of_t<_SetTag, schedule_result_t<_Scheduler>, __env2_t<_Env>...>, __completion_domain_of_t...>>, indeterminate_domain<>> { @@ -255,7 +276,7 @@ namespace STDEXEC { using _SchSender = schedule_result_t<_Scheduler>; constexpr auto cb_sched = - STDEXEC::__get_completion_behavior<_Tag, _SchSender, __fwd_env_t<_Env>...>(); + STDEXEC::__get_completion_behavior<_Tag, _SchSender, __env2_t<_Env>...>(); constexpr auto cb_sndr = STDEXEC::__get_completion_behavior<_Tag, _Sender, __fwd_env_t<_Env>...>(); return cb_sched | cb_sndr; @@ -271,7 +292,7 @@ namespace STDEXEC noexcept(__nothrow_queryable_with, _Query, _Args...>) -> __query_result_t, _Query, _Args...> { - return __attrs_.query(_Query(), static_cast<_Args&&>(__args)...); + return _Query()(STDEXEC::get_env(__sndr_), static_cast<_Args&&>(__args)...); } }; @@ -303,7 +324,8 @@ namespace STDEXEC } template - using __state_for_t = __state<__decay_t<__data_of<_Sender>>, _Sender, _Receiver>; + using __state_for_t = + __state<__decay_t<__data_of<_Sender>>, __decay_t<__child_of<_Sender>>, _Receiver>; public: static constexpr auto __get_attrs = @@ -311,7 +333,7 @@ namespace STDEXEC _Scheduler const & __data, _Child const & __child) noexcept { - return __attrs<_Scheduler, _Child>{__data, STDEXEC::get_env(__child)}; + return __attrs<_Scheduler, _Child>{__data, __child}; }; template @@ -332,12 +354,15 @@ namespace STDEXEC [](_Sender&& __sndr, _Receiver&& __rcvr) noexcept( __nothrow_constructible_from<__state_for_t<_Sender, _Receiver>, __data_of<_Sender>&, + __child_of<_Sender>&, _Receiver>) -> __state_for_t<_Sender, _Receiver> requires sender_in<__child_of<_Sender>, __fwd_env_t>> { static_assert(__sender_for<_Sender, continues_on_t>); auto& [__tag, __sched, __child] = __sndr; - return __state_for_t<_Sender, _Receiver>{__sched, static_cast<_Receiver&&>(__rcvr)}; + return __state_for_t<_Sender, _Receiver>{__sched, + __child, + static_cast<_Receiver&&>(__rcvr)}; }; static constexpr auto __complete = diff --git a/include/stdexec/__detail/__counting_scopes.hpp b/include/stdexec/__detail/__counting_scopes.hpp index c62ed8919..9386fe3c0 100644 --- a/include/stdexec/__detail/__counting_scopes.hpp +++ b/include/stdexec/__detail/__counting_scopes.hpp @@ -116,7 +116,7 @@ namespace STDEXEC { return STDEXEC::__throw_compile_time_error< _WHAT_(_JOINING_A_COUNTING_SCOPE_NEEDS_A_SCHEDULER_IN_THE_ENVIRONMENT_), - _WHY_(_THE_CURRENT_EXECUTION_ENVIRONMENT_DOESNT_HAVE_A_SCHEDULER_), + _WHY_(_THE_CURRENT_EXECUTION_ENVIRONMENT_DOES_NOT_HAVE_A_START_SCHEDULER_), _WHERE_(STDEXEC::_IN_ALGORITHM_, __scope_join_t), _WITH_PRETTY_SENDER_<_Sender>, _WITH_ENVIRONMENT_(_Env)>(); diff --git a/include/stdexec/__detail/__diagnostics.hpp b/include/stdexec/__detail/__diagnostics.hpp index 75b082ec9..5d42bc818 100644 --- a/include/stdexec/__detail/__diagnostics.hpp +++ b/include/stdexec/__detail/__diagnostics.hpp @@ -151,7 +151,10 @@ namespace STDEXEC struct _CONCEPT_CHECK_FAILURE_ {}; - struct _THE_CURRENT_EXECUTION_ENVIRONMENT_DOESNT_HAVE_A_SCHEDULER_ + struct _THE_CURRENT_EXECUTION_ENVIRONMENT_DOES_NOT_HAVE_A_START_SCHEDULER_ + {}; + + struct _THE_PREDECESSOR_SENDER_DOES_NOT_KNOW_THE_SCHEDULER_ON_WHICH_IT_WILL_COMPLETE_ {}; template diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index 45671c79e..0c0e0e886 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -238,8 +238,7 @@ namespace STDEXEC STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) constexpr auto operator()(env<_Envs...> const &__env) const noexcept -> decltype(auto) { - // count of elements that includes the first env that supports the query - // and all subsequent envs + // compute the index of the first env that supports the query: STDEXEC_CONSTEXPR_LOCAL auto __index = sizeof...(_Envs) - __mcall<__mfind_if<__q1<__has_query_t>, __msize>, _Envs...>::value; if constexpr (__index < sizeof...(_Envs)) @@ -265,8 +264,8 @@ namespace STDEXEC noexcept(__nothrow_queryable_with<__1st_env_t<_Query, _Args...>, _Query, _Args...>) -> __query_result_t<__1st_env_t<_Query, _Args...>, _Query, _Args...> { - auto const &__env = __detail::__get_1st_env<_Query, _Args...>()(*this); - return __env.query(_Query(), static_cast<_Args &&>(__args)...); + constexpr auto __get_env = __detail::__get_1st_env<_Query, _Args...>(); + return __get_env(*this).query(_Query(), static_cast<_Args &&>(__args)...); } }; diff --git a/include/stdexec/__detail/__on.hpp b/include/stdexec/__detail/__on.hpp index 4e48e093c..de0a5f40f 100644 --- a/include/stdexec/__detail/__on.hpp +++ b/include/stdexec/__detail/__on.hpp @@ -56,31 +56,36 @@ namespace STDEXEC // is no need to restore the execution context. We can use the inline // scheduler as the scheduler if __env does not have one. template - using __end_sched_t = - __if_c<__is_root_env<_Env>, - inline_scheduler, - __not_a_scheduler<_WHAT_(_CANNOT_RESTORE_EXECUTION_CONTEXT_AFTER_ON_), - _WHY_(_THE_CURRENT_EXECUTION_ENVIRONMENT_DOESNT_HAVE_A_SCHEDULER_), - _WHERE_(_IN_ALGORITHM_, on_t), - _WITH_PRETTY_SENDER_<_Child>, - _WITH_ENVIRONMENT_(_Env)>>; + using __end_sched_t = __if_c<__is_root_env<_Env>, inline_scheduler, __not_a_scheduler<>>; // This transform_sender overload handles the case where `on` was called // like `on(sch, sndr)`. In this case, we find the old scheduler by looking // in the receiver's environment. - template - requires scheduler<_Scheduler> - STDEXEC_ATTRIBUTE(always_inline) + template + STDEXEC_ATTRIBUTE(nodiscard, host, device) constexpr auto __transform_sender(_Scheduler&& __new_sched, _Child&& __child, _Env const & __env) { auto __default_sched = __end_sched_t<_Child, _Env>(); - auto __old_sched = __with_default(get_start_scheduler, __default_sched)(__env); + auto __get_sched = __with_default(get_start_scheduler, __default_sched); + auto __old_sched = __get_sched(__env); - return continues_on(starts_on(static_cast<_Scheduler&&>(__new_sched), - static_cast<_Child&&>(__child)), - std::move(__old_sched)); + if constexpr (__same_as>) + { + return __not_a_sender< + _WHAT_(_CANNOT_RESTORE_EXECUTION_CONTEXT_AFTER_ON_), + _WHY_(_THE_CURRENT_EXECUTION_ENVIRONMENT_DOES_NOT_HAVE_A_START_SCHEDULER_), + _WHERE_(_IN_ALGORITHM_, on_t), + _WITH_PRETTY_SENDER_<_Child>, + _WITH_ENVIRONMENT_(_Env)>(); + } + else + { + return continues_on(starts_on(static_cast<_Scheduler&&>(__new_sched), + static_cast<_Child&&>(__child)), + std::move(__old_sched)); + } } // This transform_sender overload handles the case where `on` was called @@ -88,99 +93,83 @@ namespace STDEXEC // __child is a predecessor sender, so the scheduler we want to restore is // the completion scheduler of __child. template - requires(!scheduler<_Data>) - STDEXEC_ATTRIBUTE(always_inline) + STDEXEC_ATTRIBUTE(nodiscard, host, device) constexpr auto __transform_sender(_Data&& __data, _Child&& __child, _Env const & __env) { auto& [__new_sched, __clsur] = __data; auto __default_sched = __end_sched_t<_Child, _Env>(); auto __get_sched = __with_default(get_completion_scheduler, __default_sched); - auto __old_sched = __get_sched(get_env(__child), __env); + auto __old_sched = __get_sched(STDEXEC::get_env(__child), __env); - return continues_on(STDEXEC::__forward_like<_Data>(__clsur)( - continues_on(static_cast<_Child&&>(__child), - STDEXEC::__forward_like<_Data>(__new_sched))), - std::move(__old_sched)); + if constexpr (__same_as>) + { + return __not_a_sender< + _WHAT_(_CANNOT_RESTORE_EXECUTION_CONTEXT_AFTER_ON_), + _WHY_(_THE_PREDECESSOR_SENDER_DOES_NOT_KNOW_THE_SCHEDULER_ON_WHICH_IT_WILL_COMPLETE_), + _WHERE_(_IN_ALGORITHM_, on_t), + _WITH_PRETTY_SENDER_<_Child>, + _WITH_ENVIRONMENT_(_Env)>(); + } + else + { + return continues_on(STDEXEC::__forward_like<_Data>(__clsur)( + continues_on(static_cast<_Child&&>(__child), + STDEXEC::__forward_like<_Data>(__new_sched))), + std::move(__old_sched)); + } } + template + using __transform_sender_t = decltype(__on::__transform_sender(__declval<_Data const &>(), + __declval<_Child const &>(), + __declval<_Env>())); + + template + concept __has_known_scheduler = + (scheduler<_Data> && __callable) + || (!scheduler<_Data> && __has_completion_scheduler_for); + template struct __attrs_base { - template <__forwarding_query _Query, class... _Args> + template , class _Query, class... _Args> requires(!__completion_query<_Query>) - && __queryable_with, _Query, _Args...> - STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) - constexpr auto query(_Query __query, _Args&&... __args) const - noexcept(__nothrow_queryable_with, _Query, _Args...>) - -> __query_result_t, _Query, _Args...> + && __queryable_with<_Attrs, _Query, _Args...> + STDEXEC_ATTRIBUTE(nodiscard, host, device) + constexpr auto query(_Query, _Args&&... __args) const + noexcept(__nothrow_queryable_with<_Attrs, _Query, _Args...>) + -> __query_result_t<_Attrs, _Query, _Args...> { - return __query(STDEXEC::get_env(__child_), static_cast<_Args&&>(__args)...); + return STDEXEC::get_env(__child_).query(_Query(), static_cast<_Args&&>(__args)...); } _Child const & __child_; }; - template - struct __attrs; - - template - struct __attrs<_Child, _Scheduler, _Closure> : __attrs_base<_Child> + template + struct __attrs : __attrs_base<_Child> { - using __trnsfr_sndr_t = __result_of, _Scheduler>; - using __clsur_result_t = __call_result_t<_Closure const &, __trnsfr_sndr_t>; - template - using __old_sched_t = __query_result_t<_Attrs, get_completion_scheduler_t, _Env>; - template - using __attrs_t = __trnsfr::__attrs<__old_sched_t<_Attrs, _Env>, __clsur_result_t>; - using __attrs_base<_Child>::query; - - explicit constexpr __attrs(_Child const & __child, - _Scheduler __sched, - _Closure const & __clsur) - : __attrs_base<_Child>{__child} - , __clsur_result_(__clsur(continues_on(__sender_proxy{__child}, std::move(__sched)))) - {} - - template , class _Query, class _Env> - requires __completion_query<_Query> // - && __queryable_with<_Attrs, get_completion_scheduler_t, _Env> - && __queryable_with<__attrs_t<_Attrs, _Env>, _Query, _Env> - STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) - constexpr auto query(_Query, _Env&& __env) const noexcept - -> __query_result_t<__attrs_t<_Attrs, _Env>, _Query, _Env> - { - auto&& __child_attrs = STDEXEC::get_env(this->__child_); - auto __old_sch = get_completion_scheduler(__child_attrs, __env); - auto __attrs = __attrs_t<_Attrs, _Env>(__old_sch, STDEXEC::get_env(__clsur_result_)); - return __attrs.query(_Query(), static_cast<_Env&&>(__env)); - } - - __clsur_result_t __clsur_result_; - }; - - template - struct __attrs<_Child, _Scheduler> : __attrs_base<_Child> - { - using __child_t = __result_of; - using __child_attrs_t = __starts_on::__attrs<_Scheduler, _Child>; template - using __attrs_t = __trnsfr::__attrs<__result_of, __child_t>; + using __attrs_t = env_of_t<__transform_sender_t<_Data, __sender_proxy<_Child const>, _Env>>; using __attrs_base<_Child>::query; - template <__completion_query _Query, __queryable_with _Env> - requires __queryable_with<__attrs_t<_Env>, _Query, _Env> + template <__completion_query _Query, class _Env> + requires __has_known_scheduler<_Data, _Child const, _Env> + && __queryable_with<__attrs_t<_Env>, _Query, _Env> STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) - constexpr auto query(_Query, _Env&& __env) const noexcept + constexpr auto query(_Query, _Env const & __env) const noexcept -> __query_result_t<__attrs_t<_Env>, _Query, _Env> { - auto&& __child_attrs = STDEXEC::get_env(this->__child_); - auto __old_sch = get_start_scheduler(__env); - auto __attrs = __attrs_t<_Env>(__old_sch, __child_attrs_t(__sched_, __child_attrs)); - return __attrs.query(_Query(), static_cast<_Env&&>(__env)); + auto __tfx_sndr = __on::__transform_sender(__data_, __sender_proxy(this->__child_), __env); + return STDEXEC::get_env(__tfx_sndr).query(_Query(), __env); } - _Scheduler __sched_; + _Data const & __data_; }; + + template + STDEXEC_HOST_DEVICE_DEDUCTION_GUIDE + __attrs(_Child const &, _Data const &) -> __attrs<_Child, _Data>; } // namespace __on ////////////////////////////////////////////////////////////////////////////// @@ -380,24 +369,9 @@ namespace STDEXEC struct __sexpr_impl : __sexpr_defaults { static constexpr auto __get_attrs = // - [](__ignore, _Data const & __data, _Child const & __child) noexcept + [](__ignore, auto const & __data, auto const & __child) noexcept { - if constexpr (scheduler<_Data>) - { - // This is the case where `on` was called like `on(sch, sndr)`, which is - // equivalent to `continues_on(starts_on(sndr, sch), old_sch)`. - using __attrs_t = __on::__attrs<_Child, _Data>; - return __attrs_t{__child, __data}; - } - else - { - // This is the case where `on` was called like `sndr | on(sch, clsur)` - // or `on(sndr, sch, clsur)`, which is equivalent to - // `continues_on(clsur(continues_on(sndr, sch)), old_sch)`. - auto const& [__sched, __clsur] = __data; - using __attrs_t = __on::__attrs<_Child, decltype(__sched), decltype(__clsur)>; - return __attrs_t{__child, __sched, __clsur}; - } + return __on::__attrs{__child, __data}; }; template diff --git a/include/stdexec/__detail/__schedulers.hpp b/include/stdexec/__detail/__schedulers.hpp index aad484fa9..9c9a12bfe 100644 --- a/include/stdexec/__detail/__schedulers.hpp +++ b/include/stdexec/__detail/__schedulers.hpp @@ -540,6 +540,7 @@ namespace STDEXEC get_completion_scheduler{}; # endif + STDEXEC_MODULE_EXPORT_AUTHORING template requires __sends<_Tag, _Sender, _Env...> using __completion_scheduler_of_t = diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e3cb13040..6cfcb85e1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -74,6 +74,7 @@ set(stdexec_test_sources stdexec/queries/test_forwarding_queries.cpp stdexec/queries/test_get_completion_behavior.cpp stdexec/queries/test_get_forward_progress_guarantee.cpp + stdexec/queries/test_get_start_scheduler.cpp stdexec/schedulers/test_task_scheduler.cpp stdexec/types/test_counting_scopes.cpp stdexec/types/test_task.cpp diff --git a/test/exec/test_completion_signatures.cpp b/test/exec/test_completion_signatures.cpp index 8a6d636ab..4ed5a2236 100644 --- a/test/exec/test_completion_signatures.cpp +++ b/test/exec/test_completion_signatures.cpp @@ -41,7 +41,7 @@ using STDEXEC::set_value_t; namespace { - TEST_CASE("", "[utilities][completion_signatures]") + TEST_CASE("completion_signatures equality", "[utilities][completion_signatures]") { STATIC_REQUIRE(completion_signatures{} == completion_signatures{}); STATIC_REQUIRE_FALSE(completion_signatures{} != completion_signatures{}); diff --git a/test/stdexec/algos/adaptors/test_on3.cpp b/test/stdexec/algos/adaptors/test_on3.cpp index 65234ac5b..fff56e76e 100644 --- a/test/stdexec/algos/adaptors/test_on3.cpp +++ b/test/stdexec/algos/adaptors/test_on3.cpp @@ -37,8 +37,7 @@ namespace template void set_value(Values&&...) noexcept { - auto env = ex::get_env(rcvr); - ex::set_value(std::move(rcvr), std::move(env)); + ex::set_value(std::move(rcvr), ex::get_env(rcvr)); } template @@ -53,7 +52,7 @@ namespace } [[nodiscard]] - auto get_env() const noexcept + auto get_env() const noexcept -> ex::env_of_t { return ex::get_env(rcvr); } diff --git a/test/stdexec/queries/test_get_start_scheduler.cpp b/test/stdexec/queries/test_get_start_scheduler.cpp new file mode 100644 index 000000000..c4745d5f7 --- /dev/null +++ b/test/stdexec/queries/test_get_start_scheduler.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2026 University of Liège + * Copyright (c) 2026 NVIDIA + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include + +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") + +namespace ex = STDEXEC; + +namespace +{ + auto then_store_thread_id(std::thread::id &id) noexcept + { + return ex::then([&id]() noexcept { id = std::this_thread::get_id(); }); + } + + //! @test Check that @c ex::get_start_scheduler is a forwarding query. + TEST_CASE("get_start_scheduler is a forwarding query", "[sched_queries][get_start_scheduler]") + { + STATIC_CHECK(ex::forwarding_query(ex::get_start_scheduler)); + } + + //! @test Check that the start scheduler that @c ex::sync_wait sets in the receiver + //! environment is a @c run_loop scheduler on the thread on which it starts the + //! operation state. + TEST_CASE("get_start_scheduler with sync_wait", "[sched_queries][get_start_scheduler]") + { + std::thread::id tid; + + auto sndr = ex::read_env(ex::get_start_scheduler) + | ex::let_value( + [&](auto schd) + { + STATIC_CHECK(std::same_as); + return ex::schedule(schd) | ::then_store_thread_id(tid); + }); + + ex::sync_wait(std::move(sndr)); + + CHECK(tid == std::this_thread::get_id()); + } + + //! @test Check that the start scheduler that @c ex::let_value sets in the receiver + //! environment of the sender returned by the closure is the completion scheduler of the + //! predecessor. + //! + //! Indeed, @c ex::let_value starts the successor from the completion of the + //! predecessor. + TEST_CASE("get_start_scheduler with let_value", "[sched_queries][get_start_scheduler]") + { + std::thread::id pool_tid, tid; + exec::static_thread_pool pool{1}; + + auto sndr = ex::schedule(pool.get_scheduler()) // + | ::then_store_thread_id(pool_tid) // + | ex::let_value( + [&]() noexcept + { + return ex::read_env(ex::get_start_scheduler) + | ex::let_value( + [&](auto schd) + { + STATIC_CHECK( + std::same_as); + return ex::schedule(schd) | ::then_store_thread_id(tid); + }); + }); + + ex::sync_wait(std::move(sndr)); + + CHECK(tid == pool_tid); + CHECK(tid != std::this_thread::get_id()); + } + + //! @test Check that the start scheduler that @c ex::continues_on sets in the receiver + //! environment of the schedule sender is the completion scheduler of the predecessor. + //! + //! Indeed, @c ex::continues_on starts an hop operation state from the completion of the + //! predecessor. + TEST_CASE("get_start_scheduler with continues_on and inline_scheduler", + "[sched_queries][get_start_scheduler]") + { + std::thread::id pool_tid, tid; + exec::static_thread_pool pool{1}; + + auto sndr = ex::schedule(pool.get_scheduler()) // + | ::then_store_thread_id(pool_tid) // + | ex::continues_on(ex::inline_scheduler{}) // + | ::then_store_thread_id(tid); + + STATIC_CHECK(std::same_as>, + decltype(pool.get_scheduler())>); + + ex::sync_wait(std::move(sndr)); + + CHECK(tid == pool_tid); + CHECK(tid != std::this_thread::get_id()); + } +} // namespace + +STDEXEC_PRAGMA_POP()