Commit 256d4f5
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
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
117 | 117 | | |
118 | 118 | | |
119 | 119 | | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
120 | 130 | | |
121 | 131 | | |
122 | 132 | | |
| |||
131 | 141 | | |
132 | 142 | | |
133 | 143 | | |
| 144 | + | |
134 | 145 | | |
135 | 146 | | |
136 | 147 | | |
| |||
0 commit comments