Skip to content

fix(stdexec): initialize stop-when's once-flag before its callbacks - #2332

Merged
ericniebler merged 1 commit into
NVIDIA:mainfrom
alwaysprince05:fix/stop-when-fused-token-once-flag-2326
Oct 13, 2026
Merged

ericniebler merged 1 commit into
NVIDIA:mainfrom
alwaysprince05:fix/stop-when-fused-token-once-flag-2326

Conversation

@alwaysprince05

Copy link
Copy Markdown
Contributor

Summary

Closes #2326. This reorders the once-flag __called_ in __stop_when_::__fused_token<T1, T2>::callback_type so that its lifetime starts before the two inner stop callbacks that use it.

Note: PR #2327 fixes the same bug the same way and is approved but unmerged; this PR was prepared independently and introduces a regression test that #2327 does not include.

Problem

callback_type declared its once-flag after the two inner callbacks:

_Fn                 __fn_;
__cb1_t             __cb1_;
__cb2_t             __cb2_;
__std::atomic<bool> __called_{false};   // initialized last

If the first token is already stopped, __cb1_'s constructor invokes the function immediately. That function does __called_.exchange(true) on an object whose lifetime has not begun, and then __called_'s own initializer stores false over it. Depending on the indeterminate value observed:

  • 0: the function runs at registration, the flag is reset, and the function runs a second time when the second token is stopped;
  • non-zero: the function is skipped at registration, and runs only if the second token is stopped later — it is lost otherwise.

Both violate [exec.stop.when]: the function must run when either token is stopped, and at most once. Regression from #2234.

Fix

__called_ is declared before __cb1_/__cb2_, so any immediate invocation during construction operates on a live, initialized flag:

_Fn                 __fn_;
__std::atomic<bool> __called_{false};
__cb1_t             __cb1_;
__cb2_t             __cb2_;

Verification

  • The issue's reproducer prints calls at registration: 1 (expected 1), in total: 2 (expected 1) before this change, and the correct 1 and 1 after it.
  • A regression test in test/stdexec/algos/adaptors/test_stop_when.cpp ("callbacks registered with an already-stopped fused token run exactly once") fails on main and passes with this change.
  • test.stdexec (629 cases) green in Release and Debug, and with STDEXEC_ENABLE_EXTRA_TYPE_CHECKING on.
  • clang-format 21 clean on both changed files.

The fused token's callback_type declared __called_ after __cb1_ and
__cb2_. When the first token is already stopped, __cb1_'s constructor
invokes the function immediately, and the function reads and writes
__called_ -- whose lifetime has not started: the flag then observes an
indeterminate value and is subsequently overwritten with false. The
function can therefore run twice (flag read as 0, then reset), or be
skipped entirely (flag read as non-zero and the second token never
stopped), both of which violate [exec.stop.when]. Regression from
NVIDIA#2234.

Declare __called_ before the two callback members so that any
immediate invocation during construction sees a live, initialized
flag. Fixes NVIDIA#2326.

test.stdexec passes in Release and Debug with
STDEXEC_ENABLE_EXTRA_TYPE_CHECKING on; a regression test covering
the already-stopped-first-token case is included.
@copy-pr-bot

copy-pr-bot Bot commented Oct 13, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@alwaysprince05

Copy link
Copy Markdown
Contributor Author

/ok to test 9a04fc9

@ericniebler

Copy link
Copy Markdown
Collaborator

/ok to test 9a04fc9

@ericniebler

Copy link
Copy Markdown
Collaborator

this is a dupe of #2327, but i'm keeping it for the test.

@ericniebler
ericniebler merged commit cc2f8f2 into NVIDIA:main Oct 13, 2026
39 checks passed
@alwaysprince05
alwaysprince05 deleted the fix/stop-when-fused-token-once-flag-2326 branch October 13, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

stop-when: the fused token's callback can run twice, or be skipped, when the first token is already stopped

2 participants