Skip to content

Transform a parked TransformStream write after one backpressure wait - #7412

Merged
jasnell merged 1 commit into
mainfrom
jasnell/ts-streams-transform-write-stall
Sep 18, 2026
Merged

jasnell merged 1 commit into
mainfrom
jasnell/ts-streams-transform-write-stall

Conversation

@jasnell

@jasnell jasnell commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

The transform sink's write algorithm waited for the backpressure-change
promise in a loop, re-checking the flag after each wake-up. The spec
waits once: a read's pull releases the write, and an enqueue on a stored
controller in that same turn re-asserts backpressure before the write's
reaction runs, yet the write still transforms. Under the loop it parked
again until the next read, so a producer that awaited each write before
reading again deadlocked, and the transformer never saw the chunk. The
C++ implementation waits once.

Both sink writes now wait once and then check the writable for
erroring, as before. The zero-algorithm path's loop could never iterate
twice, since that readable has no controller to enqueue through; it
changes for uniformity.

Pinned in the transform suite's backpressure.js. The release through a
pending read is parity. The release through a dequeue at readable hwm 1
is a divergence (ledger #14): the C++ readable does not pull after a
dequeue that leaves room (readable ledger #5), so its write stays parked
until a read finds the queue empty. An error() in the release's turn is
a divergence too (ledger #15): the woken TypeScript write rejects with
the writable's stored error, the C++ one has already lost its writable
reference and algorithms and rejects with the identity path's enqueue
TypeError.

@jasnell
jasnell added this pull request to stack #7413 September 17, 2026 14:53
@jasnell
jasnell requested review from a team as code owners September 17, 2026 14:53
@jasnell
jasnell requested review from guybedford and npaun September 17, 2026 14:53
@ask-bonk

ask-bonk Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

LGTM

github run

Base automatically changed from jasnell/ts-streams-primordial-gaps to main September 17, 2026 16:29
The transform sink's write algorithm waited for the backpressure-change
promise in a loop, re-checking the flag after each wake-up. The spec
waits once: a read's pull releases the write, and an enqueue on a stored
controller in that same turn re-asserts backpressure before the write's
reaction runs, yet the write still transforms. Under the loop it parked
again until the next read, so a producer that awaited each write before
reading again deadlocked, and the transformer never saw the chunk. The
C++ implementation waits once.

Both sink writes now wait once and then check the writable for
erroring, as before. The zero-algorithm path's loop could never iterate
twice, since that readable has no controller to enqueue through; it
changes for uniformity.

Pinned in the transform suite's backpressure.js. The release through a
pending read is parity. The release through a dequeue at readable hwm 1
is a divergence (ledger #14): the C++ readable does not pull after a
dequeue that leaves room (readable ledger #5), so its write stays parked
until a read finds the queue empty. An error() in the release's turn is
a divergence too (ledger #15): the woken TypeScript write rejects with
the writable's stored error, the C++ one has already lost its writable
reference and algorithms and rejects with the identity path's enqueue
TypeError.
@jasnell
jasnell force-pushed the jasnell/ts-streams-transform-write-stall branch from 1ec78d5 to ae8979d Compare September 17, 2026 16:29

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

LGTM. The while -> if is exactly the spec shape: TransformStreamDefaultSinkWriteAlgorithm step 3 reacts to [[backpressureChangePromise]] once, checks erroring, then performs the transform, and never re-reads [[backpressure]]. The deadlock analysis in the description checks out (a read's pull releases the write; a same-turn enqueue on a stored controller re-asserts before the reaction runs), and the elided-path claim holds since that readable's controller is not user-reachable.

Verified locally: with main's transform.ts swapped in, transform-ts@ fails on the two new same-turn tests, so the regression is caught; with the PR all 18 transform targets pass across the cpp / cpp-legacy / cpp-legacy-backpressure / cpp-pedantic / ts cells, and //src/wpt:streams-ts@ (which runs transform-streams/backpressure.any.js with no expected failures) and //src/tests/node/stream/... pass too.

Two minor, non-blocking notes inline.

Comment thread src/per_isolate/webstreams/transform.ts
Comment thread src/tests/streams/transform/backpressure.js
@jasnell
jasnell merged commit 8370bfb into main Sep 18, 2026
31 of 32 checks passed
@jasnell
jasnell deleted the jasnell/ts-streams-transform-write-stall branch September 18, 2026 01:33
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