Skip to content

[BUG] switchToLatest collection can hang after cancellation #53

Description

@pablobaldez

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

  1. Create an AsyncStream as the outer sequence.
  2. Yield a few non-terminating AsyncBufferedChannel instances and confirm that their values are received in order.
  3. Keep the outer stream and latest channel open.
  4. Cancel the task collecting outer.switchToLatest().
  5. 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

  • Current main (a64160a)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions