Skip to content

Make exec::iterate complete once stop is requested instead of repeating an item - #2459

Open
0z5a wants to merge 1 commit into
NVIDIA:mainfrom
0z5a:fix/iterate-stop-requested-livelock
Open

0z5a wants to merge 1 commit into
NVIDIA:mainfrom
0z5a:fix/iterate-stop-requested-livelock

Conversation

@0z5a

@0z5a 0z5a commented Nov 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #2446

Each item of exec::iterate is sequence(schedule(trampoline), item). Once stop has been requested, the trampoline's schedule sender completes with set_stopped before the item reads *it++, so a stopped item leaves the iterator where it was. iterate ended the sequence only when the next sender passed that set_stopped back. A next sender that completes with set_value after a stopped item made __start_next() emit the same element again, without end (the reproducer in #2446 gives up after one million stopped items).

This change checks the receiver's stop token in __start_next() before emitting the next item. If stop has been requested and elements remain, the sequence completes with set_stopped (through __set_value_unless_stopped, as __next_receiver::set_stopped does). A sequence whose last item requests stop still completes with set_value. For receivers with an unstoppable token the check is a constant false.

#2446 left open whether this is a bug in iterate or whether next senders are required to pass set_stopped back after a stopped item; there has been no reply on that yet. This PR takes the first reading, which matches the receivers in test_iterate.cpp and test_merge_each.cpp that answer set_value after a stopped item. If next senders are required to propagate set_stopped, this is a hardening change only, and it can be closed in favour of documenting that rule.

Tests

Added to test/exec/sequence/test_iterate.cpp: iterate - stops when stop is requested, even if the next senders ignore it, with a receiver whose next senders complete with set_value after a stopped item. It gives up after 100 items, so the test fails on main instead of hanging. Sections: stop requested by an item, before the start, and by the last item (the last one passes on main too).

Run on x86_64 Linux, Debug, C++20, at f71dc7d0:

Compiler test.exec "iterate*" on main + new test with the fix whole test.exec with the fix
g++ 13.3.0 1 of 11 cases fails (101 == 3, 101 == 0) 11 pass 511 cases pass
clang 20.1.8 (libstdc++) 1 of 11 cases fails 11 pass 511 cases pass

The branch is now rebased onto 8b2019b5; neither changed file was touched upstream in between, and the tests were not re-run after the rebase. clang-format 21.1.8 (--dry-run --Werror) reports no violations on the two changed files. libc++, gcc 12/14/15, MSVC, -fno-exceptions, the sanitizer rows and the modules build were not run locally.

This change was drafted with Claude Code (claude-opus-5-5); the test results above are from the runs described.

…ng an item

Each item of `exec::iterate` is `sequence(schedule(trampoline), item)`,
and the trampoline's schedule sender completes with `set_stopped` when
stop has been requested. That happens before the item reads `*it++`, so
a stopped item leaves the iterator where it was. `iterate` relied on the
next sender to pass the `set_stopped` back, and ended the sequence only
then. A next sender is free to complete with `set_value` after a stopped
item, as the ones in `test_iterate.cpp` and `test_merge_each.cpp` do,
and then `iterate` emitted the same element again, which was stopped
again, and so on without end. For example, this never completes once
`stop_source.request_stop()` has been called:

    iterate(std::views::iota(0, 100))
      | transform_each(upon_stopped([] {}))
      | ignore_all_values()
      | write_env(prop{get_stop_token, stop_source.get_token()})

This diff checks the receiver's stop token before emitting the next
item, and completes the sequence with `set_stopped` if stop has been
requested and elements remain. A sequence whose last item requests stop
still completes with `set_value`, as before.

The new test uses a receiver whose next senders ignore `set_stopped`,
and requests stop from an item, before the start, and from the last
item.
@copy-pr-bot

copy-pr-bot Bot commented Nov 14, 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.

This branch has not been deployed

No deployments
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.

exec::iterate emits the same element forever after a stop request if the next sender completes with set_value

1 participant