Skip to content

fix(when_any): guard stop callback against inline completion destroying the opstate - #2115

Merged
ericniebler merged 2 commits into
NVIDIA:mainfrom
nebkat:fix-when-any-inline-completion
Jul 5, 2026
Merged

ericniebler merged 2 commits into
NVIDIA:mainfrom
nebkat:fix-when-any-inline-completion

Conversation

@nebkat

@nebkat nebkat commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

If every child completes set_stopped() synchronously from inside __stop_source_.request_stop() (senders that complete inline from their stop callbacks), __count_ reaches zero and the receiver is completed -destroying the operation state, including __stop_source_, while request_stop() is still iterating its callback list. The subsequent relock then spins on freed memory.

Adopt __when_all's rug-pull guard: temporarily increment __count_ around request_stop() in the stop callback and arrive afterwards, so completion is deferred until the callback has returned.

https://godbolt.org/z/sPWeEEesE

@copy-pr-bot

copy-pr-bot Bot commented Jun 12, 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.

@nebkat nebkat changed the title fix(when_any): guard stop callback against inline completion destroyi… fix(when_any): guard stop callback against inline completion destroying the opstate Jun 12, 2026
…ng the opstate

If every child completes set_stopped() synchronously from inside
__stop_source_.request_stop() (senders that complete inline from their
stop callbacks), __count_ reaches zero and the receiver is completed --
destroying the operation state, including __stop_source_, while
request_stop() is still iterating its callback list. The subsequent
relock then spins on freed memory.

Adopt __when_all's rug-pull guard: temporarily increment __count_
around request_stop() in the stop callback and arrive afterwards, so
completion is deferred until the callback has returned.
@nebkat
nebkat force-pushed the fix-when-any-inline-completion branch from fc40409 to 1649cf3 Compare June 12, 2026 19:12
@ericniebler

Copy link
Copy Markdown
Collaborator

/ok to test 1649cf3

@ericniebler

Copy link
Copy Markdown
Collaborator

/ok to test 7b88e50

@ericniebler
ericniebler merged commit ae92130 into NVIDIA:main Jul 5, 2026
35 checks passed
@nebkat
nebkat deleted the fix-when-any-inline-completion branch October 4, 2026 21:06
ericniebler pushed a commit to ericniebler/stdexec that referenced this pull request Oct 25, 2026
The stop callbacks that when_all (since NVIDIA#1718) and exec::when_any (since
NVIDIA#2115) register on the receiver's stop token increment the count of
outstanding children, request stop, and then arrive. If the last child
arrives on another thread while such a callback is running, the count is
already zero, so the callback's arrival completes the operation a second
time. With inplace_stop_source the thread of the last child usually
spins forever in ~inplace_stop_callback instead, waiting for a callback
that the other thread has already destroyed.

The extra count only worked around inplace_stop_source not tolerating
its destruction from inside its own request_stop() (NVIDIA#1889), which NVIDIA#2303
fixed. Make both callbacks only forward the request to the internal stop
source, like on-stop-request in [exec.when.all]. For when_all this also
undoes the rest of the workaround from NVIDIA#1851: it completes with
set_stopped only when a child does, and it no longer records values once
a child has failed or been stopped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.

2 participants