diff --git a/libs/pika/execution/include/pika/execution/algorithms/require_started.hpp b/libs/pika/execution/include/pika/execution/algorithms/require_started.hpp index e9a39500d..0ff0eee83 100644 --- a/libs/pika/execution/include/pika/execution/algorithms/require_started.hpp +++ b/libs/pika/execution/include/pika/execution/algorithms/require_started.hpp @@ -31,12 +31,24 @@ #include #include +// We only make the choice of mode available when not using stdexec and if not +// using older versions of GCC. stdexec's sender concepts require nothrow +// destructibility, which is not satisfied by throw_on_unstarted. With stdexec +// enabled, an unstarted sender will always terminate. Older versions of GCC +// don't handle the noexcept(false) destructor correctly and fail at runtime. +#if !(defined(PIKA_HAVE_STDEXEC) || (defined(PIKA_GCC_VERSION) && PIKA_GCC_VERSION < 100000)) +# define PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE +#endif + +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) +# define PIKA_DETAIL_REQUIRE_STARTED_NOEXCEPT noexcept(false) +#else +# define PIKA_DETAIL_REQUIRE_STARTED_NOEXCEPT noexcept +#endif + namespace pika { -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) namespace execution::experimental { - // We only make the choice of mode available when not using stdexec. stdexec's sender - // concepts require nothrow destructibility, which is not satisfied by throw_on_unstarted. - // With stdexec enabled, an unstarted sender will always terminate. enum class require_started_mode { terminate_on_unstarted, @@ -46,11 +58,7 @@ namespace pika { #endif namespace require_started_detail { -#if defined(PIKA_HAVE_STDEXEC) -# define PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(f, message) \ - fmt::print(std::cerr, "{}: {}\n", f, message); \ - std::terminate(); -#else +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) using pika::execution::experimental::require_started_mode; # define PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(mode, f, message) \ @@ -67,6 +75,10 @@ namespace pika { break; \ } \ } +#else +# define PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(f, message) \ + fmt::print(std::cerr, "{}: {}\n", f, message); \ + std::terminate(); #endif template @@ -137,14 +149,14 @@ namespace pika { PIKA_NO_UNIQUE_ADDRESS std::decay_t receiver; std::optional op_state{std::nullopt}; -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) require_started_mode mode{require_started_mode::terminate_on_unstarted}; #endif bool started{false}; template require_started_op_state_type(std::decay_t sender, Receiver_&& receiver -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) , require_started_mode mode #endif @@ -154,27 +166,24 @@ namespace pika { return pika::execution::experimental::connect(PIKA_MOVE(sender), require_started_receiver{this}); })) -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) , mode(mode) #endif { } - ~require_started_op_state_type() -#if !defined(PIKA_HAVE_STDEXEC) - noexcept(false) -#endif + ~require_started_op_state_type() PIKA_DETAIL_REQUIRE_STARTED_NOEXCEPT { if (!started) { op_state.reset(); -#if defined(PIKA_HAVE_STDEXEC) - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(mode, "pika::execution::experimental::~require_started_operation_state", "The operation state of a require_started sender was never started"); #else - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(mode, + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( "pika::execution::experimental::~require_started_operation_state", "The operation state of a require_started sender was never started"); #endif @@ -213,7 +222,7 @@ namespace pika { using is_sender = void; std::optional> sender{std::nullopt}; -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) require_started_mode mode{require_started_mode::terminate_on_unstarted}; #endif mutable bool connected{false}; @@ -238,13 +247,13 @@ namespace pika { typename Enable = std::enable_if_t< !std::is_same_v, require_started_sender_type>>> explicit require_started_sender_type(Sender_&& sender -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) , require_started_mode mode = require_started_mode::terminate_on_unstarted #endif ) : sender(PIKA_FORWARD(Sender_, sender)) -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) , mode(mode) #endif { @@ -252,32 +261,28 @@ namespace pika { require_started_sender_type(require_started_sender_type&& other) noexcept : sender(std::exchange(other.sender, std::nullopt)) -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) , mode(other.mode) #endif , connected(other.connected) { } - require_started_sender_type& operator=(require_started_sender_type&& other) -#if defined(PIKA_HAVE_STDEXEC) - noexcept -#else - noexcept(false) -#endif + require_started_sender_type& operator=( + require_started_sender_type&& other) PIKA_DETAIL_REQUIRE_STARTED_NOEXCEPT { if (sender.has_value() && !connected) { sender.reset(); -#if defined(PIKA_HAVE_STDEXEC) - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(mode, "pika::execution::experimental::require_started_sender::operator=(require_" "started_sender&&)", "Assigning to a require_started sender that was never started, the target " "would be discarded"); #else - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(mode, + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( "pika::execution::experimental::require_started_sender::operator=(require_" "started_sender&&)", "Assigning to a require_started sender that was never started, the target " @@ -286,7 +291,7 @@ namespace pika { } sender = std::exchange(other.sender, std::nullopt); -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) mode = other.mode; #endif connected = other.connected; @@ -296,7 +301,7 @@ namespace pika { require_started_sender_type(require_started_sender_type const& other) : sender(other.sender) -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) , mode(other.mode) #endif , connected(false) @@ -309,14 +314,14 @@ namespace pika { { sender.reset(); -#if defined(PIKA_HAVE_STDEXEC) - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(mode, "pika::execution::experimental::require_started_sender::operator=(require_" "started_sender const&)", "Assigning to a require_started sender that was never started, the target " "would be discarded"); #else - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(mode, + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( "pika::execution::experimental::require_started_sender::operator=(require_" "started_sender const&)", "Assigning to a require_started sender that was never started, the target " @@ -325,7 +330,7 @@ namespace pika { } sender = other.sender; -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) mode = other.mode; #endif connected = false; @@ -333,21 +338,18 @@ namespace pika { return *this; } - ~require_started_sender_type() -#if !defined(PIKA_HAVE_STDEXEC) - noexcept(false) -#endif + ~require_started_sender_type() PIKA_DETAIL_REQUIRE_STARTED_NOEXCEPT { if (sender.has_value() && !connected) { sender.reset(); -#if defined(PIKA_HAVE_STDEXEC) - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(mode, "pika::execution::experimental::~require_started_sender", "A require_started sender was never started"); #else - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(mode, + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( "pika::execution::experimental::~require_started_sender", "A require_started sender was never started"); #endif @@ -361,12 +363,12 @@ namespace pika { { if (!s.sender.has_value()) { -#if defined(PIKA_HAVE_STDEXEC) - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(s.mode, "pika::execution::experimental::connect(require_started_sender&&)", "Trying to connect an empty require_started sender"); #else - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(s.mode, + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( "pika::execution::experimental::connect(require_started_sender&&)", "Trying to connect an empty require_started sender"); #endif @@ -377,7 +379,7 @@ namespace pika { { // NOLINTNEXTLINE(bugprone-unchecked-optional-access) *std::exchange(s.sender, std::nullopt), PIKA_FORWARD(Receiver, receiver) -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) , s.mode #endif @@ -391,12 +393,12 @@ namespace pika { { if (!s.sender.has_value()) { -#if defined(PIKA_HAVE_STDEXEC) - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(s.mode, "pika::execution::experimental::connect(require_started_sender const&)", "Trying to connect an empty require_started sender"); #else - PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER(s.mode, + PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER( "pika::execution::experimental::connect(require_started_sender const&)", "Trying to connect an empty require_started sender"); #endif @@ -406,7 +408,7 @@ namespace pika { return { *s.sender, PIKA_FORWARD(Receiver, receiver) -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) , s.mode #endif @@ -414,7 +416,7 @@ namespace pika { } void discard() noexcept { connected = true; } -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) void set_mode(require_started_mode mode) noexcept { this->mode = mode; } #endif }; @@ -422,13 +424,13 @@ namespace pika { #undef PIKA_DETAIL_HANDLE_UNSTARTED_REQUIRE_STARTED_SENDER } // namespace require_started_detail -#if defined(PIKA_HAVE_STDEXEC) -# define PIKA_DETAIL_REQUIRE_STARTED_MODE_PARAMETER -# define PIKA_DETAIL_REQUIRE_STARTED_MODE_ARGUMENT -#else +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) # define PIKA_DETAIL_REQUIRE_STARTED_MODE_PARAMETER \ , require_started_mode mode = require_started_mode::terminate_on_unstarted # define PIKA_DETAIL_REQUIRE_STARTED_MODE_ARGUMENT , mode +#else +# define PIKA_DETAIL_REQUIRE_STARTED_MODE_PARAMETER +# define PIKA_DETAIL_REQUIRE_STARTED_MODE_ARGUMENT #endif namespace execution::experimental { diff --git a/libs/pika/execution/tests/unit/algorithm_require_started.cpp b/libs/pika/execution/tests/unit/algorithm_require_started.cpp index 3c8ec5412..762c92e77 100644 --- a/libs/pika/execution/tests/unit/algorithm_require_started.cpp +++ b/libs/pika/execution/tests/unit/algorithm_require_started.cpp @@ -235,7 +235,7 @@ void discard_if_required(S& s, exception_test_mode mode) void test_exception(exception_test_mode mode) { -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) // When the mode is no_discard we expect exceptions, otherwise we don't expect exceptions check_exception( mode == exception_test_mode::discard ? expect_exception::no : expect_exception::yes, [=] { @@ -316,7 +316,7 @@ void test_exception(exception_test_mode mode) void test_unstarted() { -#if !defined(PIKA_HAVE_STDEXEC) +#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE) // Connected, but not started check_exception(expect_exception::yes, [] { auto rs = ex::require_started(void_sender{}, ex::require_started_mode::throw_on_unstarted); @@ -337,6 +337,15 @@ void test_unstarted() #endif } +void test_any_sender() +{ + check_exception(expect_exception::no, + [] { tt::sync_wait(ex::unique_any_sender(ex::require_started(void_sender{}))); }); + + check_exception(expect_exception::no, + [] { tt::sync_wait(ex::any_sender(ex::require_started(void_sender{}))); }); +} + int main() { test_basics(); @@ -347,6 +356,7 @@ int main() test_exception(exception_test_mode::no_discard); test_exception(exception_test_mode::discard); test_unstarted(); + test_any_sender(); return 0; } diff --git a/libs/pika/execution_base/include/pika/execution_base/any_sender.hpp b/libs/pika/execution_base/include/pika/execution_base/any_sender.hpp index 47948279f..d1b0e8f27 100644 --- a/libs/pika/execution_base/include/pika/execution_base/any_sender.hpp +++ b/libs/pika/execution_base/include/pika/execution_base/any_sender.hpp @@ -138,7 +138,7 @@ namespace pika::detail { void reset_vtable() { object = const_cast(get_empty_vtable()); } - void release() + void release() noexcept { PIKA_ASSERT(!empty()); @@ -213,7 +213,7 @@ namespace pika::detail { public: movable_sbo_storage() = default; - ~movable_sbo_storage() + ~movable_sbo_storage() noexcept { if (!empty()) { release(); } } @@ -339,6 +339,7 @@ namespace pika::detail { using storage_base_type::store; copyable_sbo_storage() = default; + ~copyable_sbo_storage() noexcept = default; copyable_sbo_storage(copyable_sbo_storage&&) = default; copyable_sbo_storage& operator=(copyable_sbo_storage&&) = default; @@ -363,7 +364,7 @@ namespace pika::detail { namespace pika::execution::experimental::detail { struct any_operation_state_base { - virtual ~any_operation_state_base() = default; + virtual ~any_operation_state_base() noexcept = default; virtual bool empty() const noexcept { return false; } virtual void start() & noexcept = 0; }; @@ -395,6 +396,7 @@ namespace pika::execution::experimental::detail { PIKA_FORWARD(Sender_, sender), PIKA_FORWARD(Receiver_, receiver))) { } + ~any_operation_state_impl() noexcept = default; void start() & noexcept override { pika::execution::experimental::start(operation_state); } }; @@ -416,7 +418,7 @@ namespace pika::execution::experimental::detail { PIKA_FORWARD(Sender, sender), PIKA_FORWARD(Receiver, receiver)); } - ~any_operation_state() = default; + ~any_operation_state() noexcept = default; any_operation_state(any_operation_state&&) = delete; any_operation_state(any_operation_state const&) = delete; any_operation_state& operator=(any_operation_state&&) = delete; @@ -587,7 +589,7 @@ namespace pika::execution::experimental::detail { template struct unique_any_sender_base { - virtual ~unique_any_sender_base() = default; + virtual ~unique_any_sender_base() noexcept = default; virtual void move_into(void* p) = 0; virtual any_operation_state connect(any_receiver&& receiver) && = 0; virtual bool empty() const noexcept { return false; } @@ -650,6 +652,8 @@ namespace pika::execution::experimental::detail { { } + ~unique_any_sender_impl() noexcept = default; + void move_into(void* p) override { new (p) unique_any_sender_impl(PIKA_MOVE(sender)); } any_operation_state connect(any_receiver&& receiver) && override @@ -670,6 +674,8 @@ namespace pika::execution::experimental::detail { { } + ~any_sender_impl() noexcept = default; + void move_into(void* p) override { new (p) any_sender_impl(PIKA_MOVE(sender)); } any_sender_base* clone() const override { return new any_sender_impl(sender); } @@ -751,7 +757,7 @@ namespace pika::execution::experimental { return *this; } - ~unique_any_sender() = default; + ~unique_any_sender() noexcept = default; unique_any_sender(unique_any_sender&&) = default; unique_any_sender(unique_any_sender const&) = delete; unique_any_sender& operator=(unique_any_sender&&) = default; @@ -869,7 +875,7 @@ namespace pika::execution::experimental { return *this; } - ~any_sender() = default; + ~any_sender() noexcept = default; any_sender(any_sender&&) = default; any_sender(any_sender const&) = default; any_sender& operator=(any_sender&&) = default;