Describe the bug
Cancelling a task that is collecting switchToLatest() can leave the collection suspended indefinitely when the outer sequence and the latest inner sequence are still open.
The latest-wins behavior works correctly: values are emitted from each newly selected inner sequence. The hang happens specifically when the collection task is cancelled and then awaited.
To reproduce
- Create an
AsyncStream as the outer sequence.
- Yield a few non-terminating
AsyncBufferedChannel instances and confirm that their values are received in order.
- Keep the outer stream and latest channel open.
- Cancel the task collecting
outer.switchToLatest().
- Await the collection task. It may never finish.
This differs from the existing cancellation test because cancellation happens while next() is suspended on open sequences rather than from inside the loop body after a finite outer sequence completes.
Expected behavior
Cancelling the collection task should promptly finish iteration, even when the outer and latest inner sequences have not completed.
Likely cause
Iterator.next() cancels its base and child tasks, but a cancelled child can still lead the state machine to continue while retaining its iterator. The next loop can then create a replacement unstructured child task, which does not inherit the consumer's existing cancellation and can wait forever.
There is a similar race while waiting for the next outer child: the stored continuation is not resumed directly by the cancellation handler, so termination depends on the outer sequence reacting to cancellation.
Proposed fix
My proposed approach is to:
- persist cancellation in the iterator's protected state;
- resume any continuation waiting for the outer sequence when cancellation occurs;
- prevent new child tasks from being created or delivered after cancellation; and
- finish the post-child transition when the iterator is cancelled.
Normal completion would remain unchanged, including allowing the latest inner sequence to drain after a finite outer sequence finishes.
I have a minimal regression test for the unfinished-latest case and a second test covering cancellation while waiting on a non-cooperative outer sequence.
Would this state-machine approach fit the intended cancellation semantics? If so, I would be happy to open a PR with the fix and regression coverage.
This may be related to #35, although that issue reports a lifetime leak through flatMapLatest, while this report focuses on a reproducible cancellation hang.
Environment
Describe the bug
Cancelling a task that is collecting
switchToLatest()can leave the collection suspended indefinitely when the outer sequence and the latest inner sequence are still open.The latest-wins behavior works correctly: values are emitted from each newly selected inner sequence. The hang happens specifically when the collection task is cancelled and then awaited.
To reproduce
AsyncStreamas the outer sequence.AsyncBufferedChannelinstances and confirm that their values are received in order.outer.switchToLatest().This differs from the existing cancellation test because cancellation happens while
next()is suspended on open sequences rather than from inside the loop body after a finite outer sequence completes.Expected behavior
Cancelling the collection task should promptly finish iteration, even when the outer and latest inner sequences have not completed.
Likely cause
Iterator.next()cancels its base and child tasks, but a cancelled child can still lead the state machine to continue while retaining its iterator. The next loop can then create a replacement unstructured child task, which does not inherit the consumer's existing cancellation and can wait forever.There is a similar race while waiting for the next outer child: the stored continuation is not resumed directly by the cancellation handler, so termination depends on the outer sequence reacting to cancellation.
Proposed fix
My proposed approach is to:
Normal completion would remain unchanged, including allowing the latest inner sequence to drain after a finite outer sequence finishes.
I have a minimal regression test for the unfinished-latest case and a second test covering cancellation while waiting on a non-cooperative outer sequence.
Would this state-machine approach fit the intended cancellation semantics? If so, I would be happy to open a PR with the fix and regression coverage.
This may be related to #35, although that issue reports a lifetime leak through
flatMapLatest, while this report focuses on a reproducible cancellation hang.Environment
main(a64160a)