Skip to content

Stop identity stream aborts from waiting for a read - #7399

Merged
jasnell merged 1 commit into
mainfrom
jasnell/ts-streams-its-fls-abort
Sep 17, 2026
Merged

jasnell merged 1 commit into
mainfrom
jasnell/ts-streams-its-fls-abort

Conversation

@jasnell

@jasnell jasnell commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

A write to an IdentityTransformStream or FixedLengthStream waits in the
sink until a read consumes it. abort() runs the abort steps only once
the in-flight write has settled, and only a read could wake that write,
so with no reader the abort and the write never settled. Error-handling
code that aborts a streaming response's writer hung.

The writable machinery now has a module-private abort hook, called once
when an abort is requested, right after the controller's signal fires.
The identity streams use it to wake the waiting write, which finds the
stream erroring and rejects with the abort reason; the abort then
completes, as in the C++ implementation. An 'abort' listener on the
controller's signal would do the same, but adding one costs about 8
microseconds, more than doubling the cost of constructing these streams.

Reads after such an abort reject with the abort reason. The C++
implementation rejects them with "Network connection lost.", because
cancelling the waiting sink write puts the transform into its
disconnection error first; the identity suite pins both

@jasnell
jasnell requested review from guybedford and npaun September 16, 2026 20:38
@jasnell
jasnell added this pull request to stack #7392 September 16, 2026 20:38
@jasnell
jasnell requested review from a team as code owners September 16, 2026 20:38
@ask-bonk

ask-bonk Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

LGTM!

github run

@jasnell
jasnell force-pushed the jasnell/ts-streams-its-fls-abort branch from e49179e to d006054 Compare September 16, 2026 22:17
@jasnell
jasnell force-pushed the jasnell/ts-streams-its-fls-abort branch from d006054 to 8cb3518 Compare September 16, 2026 23:30
@jasnell
jasnell force-pushed the jasnell/ts-streams-its-fls-abort branch from 8cb3518 to 3f20666 Compare September 16, 2026 23:55

@guybedford guybedford 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.

Verified locally: with the fix reverted on the base, the three new tests fail in identity-ts@ with abort() must not wait for a read; with it, all identity and piping variants pass. Also checked that abort with nothing parked followed by a read rejects rather than hangs, that a second abort() after the one-shot hook is consumed still settles, and that abort racing an already-pending read is unchanged from before.

The ordering holds as described: the hook fires before writableStreamStartErroring, and the parked write's continuation runs in a microtask after that, so it always observes erroring with the abort reason as the stored error. The one-shot is safe since the identity controller isn't user-reachable, so nothing can re-enter between the hook and the erroring transition.

Nit: after the rebase #readyHook is declared as #readyHook: (() => void) | undefined while the new #abortHook?: (() => void) | undefined keeps the optional form; worth matching.

Base automatically changed from jasnell/ts-streams-native-fast-path-fix to main September 17, 2026 01:52
A write to an IdentityTransformStream or FixedLengthStream waits in the
sink until a read consumes it. abort() runs the abort steps only once
the in-flight write has settled, and only a read could wake that write,
so with no reader the abort and the write never settled. Error-handling
code that aborts a streaming response's writer hung.

The writable machinery now has a module-private abort hook, called once
when an abort is requested, right after the controller's signal fires.
The identity streams use it to wake the waiting write, which finds the
stream erroring and rejects with the abort reason; the abort then
completes, as in the C++ implementation. An 'abort' listener on the
controller's signal would do the same, but adding one costs about 8
microseconds, more than doubling the cost of constructing these streams.

Reads after such an abort reject with the abort reason. The C++
implementation rejects them with "Network connection lost.", because
cancelling the waiting sink write puts the transform into its
disconnection error first; the identity suite pins both.

The TypeScript streams implementation is experimental
(typescript_implemented_streams), so no compatibility flag is needed.
@jasnell
jasnell force-pushed the jasnell/ts-streams-its-fls-abort branch from 3f20666 to 735a2d7 Compare September 17, 2026 01:52
@jasnell
jasnell merged commit 5aef66d into main Sep 17, 2026
22 checks passed
@jasnell
jasnell deleted the jasnell/ts-streams-its-fls-abort branch September 17, 2026 02:35
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