Skip to content

stream: fix pipeTo assert when writer is released during microtask#63733

Open
colinaaa wants to merge 2 commits into
nodejs:mainfrom
colinaaa:fix-stream-shutdown
Open

stream: fix pipeTo assert when writer is released during microtask#63733
colinaaa wants to merge 2 commits into
nodejs:mainfrom
colinaaa:fix-stream-shutdown

Conversation

@colinaaa
Copy link
Copy Markdown

@colinaaa colinaaa commented Jun 3, 2026

Summary

The deferred write introduced by #61800 wraps the write operation in queueMicrotask(). A race condition exists where the pipe can shutdown and release the writer between when [kChunk] schedules the microtask and when it executes, causing an ERR_INTERNAL_ASSERTION because writer[[stream]] is undefined.

Fix by checking writer[kState].stream === undefined in the deferred microtask before attempting the write. This condition is only true after finalize() has released the writer, precisely targeting the crash without interfering with the spec requirement that already-read chunks are written to a still-writable destination during shutdown.

Fixes: #63732

The deferred write introduced by commit 65aa8f6 ("stream: fix
pipeTo to defer writes per WHATWG spec") wraps the write operation in
queueMicrotask(). A race condition exists where the pipe can shutdown
and release the writer between when [kChunk] schedules the microtask
and when it executes, causing an ERR_INTERNAL_ASSERTION because
writer[[stream]] is undefined.

Fix by moving the shuttingDown flag into the shared pipe state object
so that PipeToReadableStreamReadRequest can check it. The microtask
now skips the write if the pipe has already begun shutting down.

This aligns with the WHATWG Streams spec step 15 which states:
"if shuttingDown becomes true, the user agent must not initiate
further reads from reader, and must only perform writes of
already-read chunks".

Fixes: nodejs#63732
Signed-off-by: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. web streams labels Jun 3, 2026
The state.shuttingDown guard introduced in the previous commit is too
broad: it skips writes even when the destination is still writable.
The WHATWG Streams spec requires that already-read chunks are written
to a still-writable destination during shutdown (step 15, shutdown
substeps 3.1-3.2).

Replace the guard with a check for writer[kState].stream === undefined
which is only true after finalize() has released the writer. This
precisely targets the crash condition without violating the spec
requirement.

Restore shuttingDown as a closure-local variable since it no longer
needs to be shared with PipeToReadableStreamReadRequest.

Add a test case verifying that an already-read chunk is written when
an AbortSignal fires after enqueue while the destination is still
writable.

Signed-off-by: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. web streams

Projects

None yet

Development

Successfully merging this pull request may close these issues.

stream: pipeTo with preventCancel can hit ERR_INTERNAL_ASSERTION if destination errors before deferred write

2 participants