Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion include/stdexec/__detail/__stop_when.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,14 @@ namespace STDEXEC
using __cb1_t = _Token1::template callback_type<__cb_t>;
using __cb2_t = _Token2::template callback_type<__cb_t>;

// __called_ must be initialized before the callbacks: a callback
// registered with an already-stopped token runs its function as part
// of the callback's own construction, and that function reads and
// writes __called_.
_Fn __fn_;
__std::atomic<bool> __called_{false};
__cb1_t __cb1_;
__cb2_t __cb2_;
__std::atomic<bool> __called_{false};
};

_Token1 __tkn1_;
Expand Down
37 changes: 37 additions & 0 deletions test/stdexec/algos/adaptors/test_stop_when.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,43 @@ namespace
wait_for_value(ex::__stop_when(isTokenStoppable(), source.get_token()), true);
}

#if !STDEXEC_USE_MODULES()
// TODO: figure out why this fails in a modules build
TEST_CASE("callbacks registered with an already-stopped fused token run exactly once",
"[adaptors][stop-when]")
{
// Regression test for #2326: the first token is already stopped when the
// callback is registered, so the function must run once, at registration,
// and never again when the second token is stopped later.
ex::inplace_stop_source first, second;
first.request_stop();

int calls = 0;
int atReg = -1;
auto makeCallback = [](auto token, auto fn) noexcept
{
using cb_t = decltype(token)::template callback_type<decltype(fn)>;
return std::optional<cb_t>(std::in_place, std::move(token), std::move(fn));
};

ex::sync_wait(ex::__stop_when(ex::__stop_when(ex::read_env(ex::get_stop_token)
| ex::then(
[&](auto token) noexcept
{
auto cb = makeCallback(token,
[&]() noexcept
{ ++calls; });
atReg = calls;
second.request_stop();
}),
first.get_token()),
second.get_token()));

REQUIRE(atReg == 1);
REQUIRE(calls == 1);
}
#endif

#if !STDEXEC_USE_MODULES()
// TODO: figure out why this fails in a modules build
TEST_CASE("stop-when fuses its token with the receiver's when both are stoppable",
Expand Down
Loading