diff --git a/libs/core/execution/include/hpx/execution/algorithms/detail/sync_wait_domain.hpp b/libs/core/execution/include/hpx/execution/algorithms/detail/sync_wait_domain.hpp index 76b340d77cf..ed204f206dd 100644 --- a/libs/core/execution/include/hpx/execution/algorithms/detail/sync_wait_domain.hpp +++ b/libs/core/execution/include/hpx/execution/algorithms/detail/sync_wait_domain.hpp @@ -400,3 +400,18 @@ namespace hpx::execution::experimental::detail { return {}; } } // namespace hpx::execution::experimental::detail + +namespace hpx::execution::experimental { + + // Out-of-class definition for run_loop::env_t::query declared in + // run_loop.hpp. Defined here (not there) because the body needs the + // complete sync_wait_domain type. Templating on CPO defers instantiation + // so the trailing-return forward decl in run_loop.hpp is sufficient + // at declaration site. + template + auto run_loop::env_t::query(get_completion_domain_t) noexcept + -> detail::sync_wait_domain + { + return {}; + } +} // namespace hpx::execution::experimental diff --git a/libs/core/execution/include/hpx/execution/algorithms/run_loop.hpp b/libs/core/execution/include/hpx/execution/algorithms/run_loop.hpp index 4a314a095c0..7a05306ff06 100644 --- a/libs/core/execution/include/hpx/execution/algorithms/run_loop.hpp +++ b/libs/core/execution/include/hpx/execution/algorithms/run_loop.hpp @@ -179,11 +179,17 @@ namespace hpx::execution::experimental { auto query( get_completion_scheduler_t) const noexcept; - //[[nodiscard]] - //static auto query(get_completion_domain_t) noexcept; - - //[[nodiscard]] - //static auto query(get_completion_domain_t) noexcept; + // P3826R5: advertise the HPX-aware sync_wait domain on this + // env so that stdexec::sync_wait routes through + // detail::sync_wait_domain instead of default_domain. The body + // is defined in detail/sync_wait_domain.hpp after sync_wait_domain + // is complete; this header only forward-declares it (see top of + // file). Templating on CPO defers instantiation until the + // domain type is fully visible to the caller. + template + [[nodiscard]] + static auto query(get_completion_domain_t) noexcept + -> detail::sync_wait_domain; run_loop* loop; }; @@ -378,16 +384,8 @@ namespace hpx::execution::experimental { return query(get_completion_scheduler); } - //auto run_loop::env_t::query(get_completion_domain_t) noexcept - //{ - // return hpx::execution::experimental::detail::sync_wait_domain{}; - //} - - //inline auto run_loop::env_t::query( - // get_completion_domain_t) noexcept - //{ - // return query(get_completion_domain); - //} + // run_loop::env_t::query(get_completion_domain_t) is defined in + // detail/sync_wait_domain.hpp once sync_wait_domain is complete. /////////////////////////////////////////////////////////////////////////// HPX_CXX_CORE_EXPORT using run_loop_scheduler = run_loop::run_loop_scheduler; diff --git a/libs/core/execution/tests/unit/algorithm_run_loop.cpp b/libs/core/execution/tests/unit/algorithm_run_loop.cpp index 3cce33da6a2..8c7522af25b 100644 --- a/libs/core/execution/tests/unit/algorithm_run_loop.cpp +++ b/libs/core/execution/tests/unit/algorithm_run_loop.cpp @@ -1830,6 +1830,86 @@ void test_completion_scheduler() loop.run(); } +// Regression test for the run_loop -> sync_wait_domain integration: +// verify that the P3826R5 resolution chain +// +// sender env -> get_completion_scheduler +// -> run_loop_scheduler +// -> get_completion_domain +// -> detail::sync_wait_domain +// +// terminates at detail::sync_wait_domain. With this in place, +// stdexec::sync_wait on a run_loop-scheduled sender dispatches through +// HPX's cooperative apply_sender(sync_wait_t) instead of default_domain's +// OS-blocking wait. Checks are static_asserts so a regression fails the +// build rather than a runtime test. +void test_completion_domain() +{ + using sync_wait_domain = + hpx::execution::experimental::detail::sync_wait_domain; + + ex::run_loop loop; + [[maybe_unused]] auto sched = loop.get_scheduler(); + + // 1. Direct: the env returned by run_loop_sender.get_env() exposes + // get_completion_domain -> sync_wait_domain. + { + auto sender = ex::schedule(sched); + auto env = ex::get_env(sender); + using domain_t = + std::decay_t( + env))>; + static_assert(std::is_same_v, + "run_loop env's get_completion_domain should " + "resolve to detail::sync_wait_domain"); + } + + // 2. set_stopped_t resolves the same way (templated query covers all + // CPOs the env recognises). + { + auto sender = ex::schedule(sched); + auto env = ex::get_env(sender); + using domain_t = + std::decay_t( + env))>; + static_assert(std::is_same_v, + "run_loop env's get_completion_domain should " + "resolve to detail::sync_wait_domain"); + } + + // 3. Full P3826R5 chain through the scheduler: from a sender's env, + // get_completion_scheduler -> run_loop_scheduler, + // then on that scheduler get_completion_domain + // -> sync_wait_domain. This is the path stdexec::sync_wait walks. + { + auto sender = ex::schedule(sched); + auto completion_scheduler = + ex::get_completion_scheduler(ex::get_env(sender)); + using domain_t = + std::decay_t( + completion_scheduler))>; + static_assert(std::is_same_v, + "run_loop_scheduler's get_completion_domain should " + "resolve to detail::sync_wait_domain"); + } + + // 4. Same chain on a composed sender (just | continues_on(sched)). + { + auto sender = ex::just(42) | ex::continues_on(sched); + auto completion_scheduler = + ex::get_completion_scheduler(ex::get_env(sender)); + using domain_t = + std::decay_t( + completion_scheduler))>; + static_assert(std::is_same_v, + "composed-sender completion_scheduler should still resolve " + "get_completion_domain to detail::sync_wait_domain"); + } + + loop.finish(); + loop.run(); +} + void do_run_test(void (*func)(), char const* func_name) { std::cout << func_name << "\n"; @@ -1867,6 +1947,7 @@ int hpx_main() RUN_TEST(test_bulk); RUN_TEST(test_completion_scheduler); + RUN_TEST(test_completion_domain); return hpx::local::finalize(); }