Skip to content

Commit 256d4f5

Browse files
committed
fix(sync_wait): do not report errors as stopped when exceptions are off
sync_wait's only way to deliver an error completion to its caller is to rethrow it. When exception support is unavailable (STDEXEC_NO_STDCPP_EXCEPTIONS()), its receiver funnels every non-exception_ptr error completion through std::make_exception_ptr, and at least libstdc++'s implementation then returns a NULL exception_ptr (the non-throwing fast path requires RTTI, the throwing path requires exceptions; with both disabled the fallback returns exception_ptr()). The rethrow site guards on the pointer, silently skips, and sync_wait returns a disengaged optional. That is not merely a missing report. [exec.sync.wait] reserves the disengaged optional for STOPPED completions ("For a stopped completion, a disengaged optional object is returned"), so every error completion is affirmatively misreported as cancellation -- corrupting exactly the caller logic (retry/commit/abort) that depends on telling the two apart. With this change, when STDEXEC_NO_STDCPP_EXCEPTIONS() is true the sync_wait receiver's error channel calls STDEXEC_TERMINATE(): a no-exceptions build has no channel that can carry the error to the caller, and terminating loudly is strictly better than converting errors into cancellations. This matches the library's own precedent for exactly this situation: STDEXEC_THROW already lowers to ::STDEXEC::__terminate() when exceptions are unavailable (__config.hpp). Runtime rejection was chosen over a static_assert deliberately: an error completion signature is routinely PRESENT but dynamically never taken (any generic chain advertises one), and rejecting those programs at compile time would make sync_wait unusable under -fno-exceptions rather than safe. Behavior with exceptions enabled is unchanged: the entire original body is the #else branch, and an A/B comparison against the unpatched tree shows identical behavior for all three AS-EXCEPT-PTR cases -- a custom error type is thrown as itself and caught, std::error_code arrives as std::system_error with the original code, and an exception_ptr is rethrown. Verified on aarch64-apple-darwin with GCC 16.1: under -fno-exceptions -fno-rtti an error completion now terminates (previously: silent disengaged optional); value and stopped channels unchanged in both modes. The full default test suite passes (963/968; the 5 relacy failures are environmental on this host and unaffected by this change), and the -fno-exceptions configuration from the CI matrix passes 879/879. On test coverage: the existing sync_wait error-path tests are all compiled out under no-exceptions builds (test_sync_wait.cpp, `#if !STDEXEC_NO_STDCPP_EXCEPTIONS()`), so the no-exceptions CI legs currently have no error-path coverage at all -- which is how this behavior shipped, and also why this change cannot alter their results. The observable fix behavior is termination, which Catch2 cannot assert in-process; a subprocess death test (exit-code assertion gated on STDEXEC_NO_STDCPP_EXCEPTIONS()) is a possible follow-up if there is appetite. Note: this commit is independent of, but in practice ordered after, the __spin_loop_pause duplicate-'inline' fix -- without that fix, GCC on arm/aarch64 cannot compile anything that reaches the spin-loop header, including the test suite runs cited above.
1 parent fd60b20 commit 256d4f5

1 file changed

Lines changed: 11 additions & 0 deletions

File tree

include/stdexec/__detail/__sync_wait.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ namespace STDEXEC::__sync_wait
117117
template <class _Error>
118118
constexpr void set_error(_Error __err) noexcept
119119
{
120+
# if STDEXEC_NO_STDCPP_EXCEPTIONS()
121+
// sync_wait's only way to deliver an error to its caller is to rethrow
122+
// it. Without exception support, std::make_exception_ptr returns a null
123+
// exception_ptr, which the rethrow path would silently skip -- turning
124+
// an error completion into an empty optional, indistinguishable from a
125+
// stopped completion. Errors must not masquerade as cancellation:
126+
// terminate loudly instead.
127+
(void) __err;
128+
STDEXEC_TERMINATE();
129+
# else
120130
if constexpr (__same_as<_Error, std::exception_ptr>)
121131
{
122132
STDEXEC_ASSERT(__err != nullptr); // std::exception_ptr must not be null.
@@ -131,6 +141,7 @@ namespace STDEXEC::__sync_wait
131141
__state_->__eptr_ = std::make_exception_ptr(static_cast<_Error&&>(__err));
132142
}
133143
__state_->__loop_.finish();
144+
# endif
134145
}
135146

136147
constexpr void set_stopped() noexcept

0 commit comments

Comments
 (0)