Skip to content

Commit daf9a01

Browse files
committed
just: Connect Can Throw
A just sender that isn't nullary contains one or more objects. These objects are transmitted thereinto by the sender factory therefor. Since copying and moving objects can throw the just sender factory can also throw (this is demonstrated by the unit test added by this commit). In order to be useful a sender must be connected with a receiver to form an operation state. Since the just operation does not yield the objects passed to the sender factory therefor until the operation has been started it is clear those objects must in turn be transmitted from the sender to the operation state at connect time. If initially providing those objects can throw then so too can propagating them from the sender to the operation state. That is to say that not only can the just sender factory throw but so too can connecting a just sender with a receiver. Before this commit if a just sender was connected, and one of the move operations involved therein threw an exception, the program would terminate due to the fact the exception would attempt to pass through a constructor marked noexcept. The code involved (changed by this commit) attempted to accurately reflect the noexcept-ness of the operations involved by checking: __noexcept_of<__sexpr_impl<__tag_t>::get_state, _Sexpr, _Receiver&> However this merely checks if the evaluation of some function throws an exception. In the case wherein a just sender is being connected the evaluation does not throw and therefore the above expression correctly yields true. The function is not merely invoked however, the value returned thereby is used to populate a data member in the constructor initializer list. In the instance of just the invocation yields an rvalue reference and therefore a move operation must occur, one which may throw an exception. To account for this the noexcept check must be: noexcept(__state_t(__sexpr_impl<__tag_t>::get_state(static_cast<_Sexpr&&>(__sndr), __rcvr_))) Which is what it is changed to by this commit.
1 parent 9bce1ba commit daf9a01

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

include/stdexec/__detail/__basic_sender.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ namespace stdexec {
228228

229229
__op_base(_Sexpr&& __sndr, _Receiver&& __rcvr) noexcept(
230230
__nothrow_decay_copyable<_Receiver>
231-
&& __noexcept_of<__sexpr_impl<__tag_t>::get_state, _Sexpr, _Receiver&>)
231+
&& noexcept(__state_t(__sexpr_impl<__tag_t>::get_state(static_cast<_Sexpr&&>(__sndr), __rcvr_))))
232232
: __rcvr_(static_cast<_Receiver&&>(__rcvr))
233233
, __state_(__sexpr_impl<__tag_t>::get_state(static_cast<_Sexpr&&>(__sndr), __rcvr_)) {
234234
}

test/stdexec/algos/factories/test_just.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
#include <test_common/receivers.hpp>
2020
#include <test_common/type_helpers.hpp>
2121

22+
#include <cstddef>
23+
#include <stdexcept>
24+
#include <utility>
25+
2226
namespace ex = stdexec;
2327

2428
namespace {
@@ -124,4 +128,55 @@ namespace {
124128
CHECK(res == original);
125129
CHECK(cat == typecat::rvalref);
126130
}
131+
132+
TEST_CASE("just works with types with throwing move", "[factories][just]") {
133+
struct throwing_move {
134+
explicit throwing_move(std::size_t& throws_after) noexcept : throws_after_(throws_after) {}
135+
throwing_move(throwing_move&& other)
136+
: throws_after_(other.throws_after_)
137+
{
138+
if (throws_after_) {
139+
--throws_after_;
140+
} else {
141+
throw std::runtime_error("Throwing as requested");
142+
}
143+
}
144+
private:
145+
std::size_t& throws_after_;
146+
};
147+
std::size_t throws_after = 0;
148+
const auto repeat_until_succeeds = [&](auto f) noexcept -> decltype(auto) {
149+
struct guard {
150+
~guard() noexcept {
151+
CHECK(threw);
152+
}
153+
bool threw{false};
154+
};
155+
guard g;
156+
for (;;) {
157+
auto orig = throws_after;
158+
try {
159+
return f();
160+
} catch (...) {
161+
g.threw = true;
162+
++orig;
163+
throws_after = orig;
164+
}
165+
}
166+
};
167+
auto sender = repeat_until_succeeds([&]() {
168+
return ::stdexec::just(throwing_move(throws_after));
169+
});
170+
CHECK(throws_after == 0);
171+
std::size_t invoked = 0;
172+
auto op = repeat_until_succeeds([&]() {
173+
return ::stdexec::connect(
174+
std::move(sender),
175+
make_fun_receiver([&](throwing_move&&) noexcept { ++invoked; }));
176+
});
177+
CHECK(throws_after == 0);
178+
CHECK(invoked == 0);
179+
::stdexec::start(op);
180+
CHECK(invoked == 1);
181+
}
127182
} // namespace

0 commit comments

Comments
 (0)