Skip to content

Cancel incoming data streams when a reader stops early - #2168

Open
1egoman wants to merge 1 commit into
fix-ensure-ordered-chunks-not-dropping-controllerfrom
stream-reader-cancel-on-early-exit
Open

1egoman wants to merge 1 commit into
fix-ensure-ordered-chunks-not-dropping-controllerfrom
stream-reader-cancel-on-early-exit

Conversation

@1egoman

@1egoman 1egoman commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Stacked on #2167. Follow-up to this review thread.

#2167 unregisters an incoming data stream when its source ReadableStream is cancelled. But when a consumer stops reading early, TextStreamReader and ByteStreamReader only call releaseLock(), which doesn't cancel the stream. A consumer stops early by aborting the signal passed to readAll/withAbortSignal, by leaving a for await with break/return, or when the reader throws one of its own errors (for example, more bytes than totalLength). The stream then stays registered, and later chunks pile up in a queue nobody reads until the trailer arrives or the sender disconnects. On uncompressed streams nothing caps that buffering.

This PR makes the iterator's early-exit cleanup call reader.cancel(reason) before releasing the lock. The cancel travels up the pipe chain to the source stream, and the cancel hook from #2167 removes the stream from textStreamControllers/byteStreamControllers. Later packets for that stream are dropped as if it had never been opened. Streams that finish normally never run this cleanup, so they aren't affected. This also matches how the built-in ReadableStream async iterator behaves on early exit.

Behavior change: before this change, each [Symbol.asyncIterator]() call took a fresh lock, so a consumer could break out of a for await and resume the same stream later. That is no longer possible: once a reader stops early, the stream is cancelled. This wasn't documented, and I don't expect anyone to rely on it, but the changeset calls it out.

Testing

🤖 Generated with Claude Code

Stream readers only released their lock on abort, early `break`/`return`,
or a read error, so the source stream stayed registered and kept buffering
chunks nobody would read until the trailer arrived. Cancel the stream
instead, which unregisters it via the source `cancel` hook so later packets
are dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1c2e4cf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
livekit-client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@1egoman
1egoman added this pull request to stack #2169 October 20, 2026 22:00
@github-actions

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
dist/livekit-client.esm.mjs 115.83 KB (+0.05% 🔺)
dist/livekit-client.umd.js 125.12 KB (-0.02% 🔽)
dist/core/core.mjs 78.55 KB (0%)

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.

2 participants