Skip to content

Commit

Permalink
Merge pull request #1044 from msimberg/require-started-any-sender
Browse files Browse the repository at this point in the history
Explicitly mark destructors of `any_sender` and related classes `noexcept`
  • Loading branch information
msimberg authored Mar 6, 2024
2 parents 66bddd3 + 98e02a9 commit 75206d2
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,24 @@
#include <type_traits>
#include <utility>

// 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,
Expand All @@ -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) \
Expand All @@ -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 <typename OpState>
Expand Down Expand Up @@ -137,14 +149,14 @@ namespace pika {

PIKA_NO_UNIQUE_ADDRESS std::decay_t<Receiver> receiver;
std::optional<operation_state_type> 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 <typename Receiver_>
require_started_op_state_type(std::decay_t<Sender> sender, Receiver_&& receiver
#if !defined(PIKA_HAVE_STDEXEC)
#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE)
,
require_started_mode mode
#endif
Expand All @@ -154,27 +166,24 @@ namespace pika {
return pika::execution::experimental::connect(PIKA_MOVE(sender),
require_started_receiver<require_started_op_state_type>{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
Expand Down Expand Up @@ -213,7 +222,7 @@ namespace pika {
using is_sender = void;

std::optional<std::decay_t<Sender>> 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};
Expand All @@ -238,46 +247,42 @@ namespace pika {
typename Enable = std::enable_if_t<
!std::is_same_v<std::decay_t<Sender_>, 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
{
}

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 "
Expand All @@ -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;
Expand All @@ -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)
Expand All @@ -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 "
Expand All @@ -325,29 +330,26 @@ namespace pika {
}

sender = other.sender;
#if !defined(PIKA_HAVE_STDEXEC)
#if defined(PIKA_DETAIL_HAVE_REQUIRE_STARTED_MODE)
mode = other.mode;
#endif
connected = false;

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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -406,29 +408,29 @@ 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
};
}

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
};

#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 {
Expand Down
14 changes: 12 additions & 2 deletions libs/pika/execution/tests/unit/algorithm_require_started.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, [=] {
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand All @@ -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;
}
Loading

0 comments on commit 75206d2

Please sign in to comment.