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

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

Conversation

@colinaaa

@colinaaa colinaaa commented Jun 3, 2026 •

Copy link
Copy Markdown

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

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. web streams Issues and PRs related to the Web Streams API. labels Jun 3, 2026
pipeTo defers each slow-path write to a microtask so it does not run
synchronously during enqueue(). A race allows the pipe to shut down and
release its writer after the write is scheduled but before it runs. The
deferred write then hits an ERR_INTERNAL_ASSERTION because
writer[[stream]] is undefined.

Skip the deferred write when shutdown has already started.

Fixes: nodejs#63732
Signed-off-by: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
Assisted-by: Codex
The shuttingDown guard added by the previous commit is too broad: it
also skips an already-read chunk while the destination is still
writable. The WHATWG Streams spec requires pipeTo to write such chunks
during shutdown.

Check writer[kState].stream instead. It becomes undefined only after
finalize() releases the writer, which is precisely the assertion case.

Add a regression test that aborts after enqueue() and verifies that the
already-read chunk is written.

Signed-off-by: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
Assisted-by: Codex
@codecov

codecov Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.06%. Comparing base (82fc59d) to head (de4d49d).
⚠️ Report is 1004 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #63733   +/-   ##
=======================================
  Coverage   90.06%   90.06%           
=======================================
  Files         769      769           
  Lines      261316   261317    +1     
  Branches    49622    49628    +6     
=======================================
+ Hits       235351   235352    +1     
+ Misses      16998    16997    -1     
- Partials     8967     8968    +1     
Files with missing lines Coverage Δ
lib/internal/webstreams/readablestream.js 87.31% <100.00%> (+0.10%) ⬆️

... and 25 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell
jasnell requested review from mcollina and ronag September 8, 2026 00:16

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Sep 8, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Sep 8, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@colinaaa
colinaaa requested a review from mcollina September 16, 2026 12:20
@colinaaa

Copy link
Copy Markdown
Author

@mcollina Thanks for the review! The Jenkins CI run failed on ARM and Windows, but I don't have access to the logs: https://ci.nodejs.org/job/node-test-pull-request/77173/

Could you help check the failures and rerun CI if they are unrelated to this change? I'm happy to rebase if needed.

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Oct 25, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Oct 25, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@colinaaa

Copy link
Copy Markdown
Author

@mcollina Thanks for rerunning CI! The latest run is now green: https://ci.nodejs.org/job/node-test-pull-request/78643/

Could you please add this PR to the commit queue when you have a chance? Thanks!

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm

@mcollina mcollina added the commit-queue PRs queued for automated landing through the Commit Queue. label Oct 26, 2026
@nodejs-github-bot nodejs-github-bot added commit-queue-failed PRs whose Commit Queue landing failed and need manual intervention before retrying. and removed commit-queue PRs queued for automated landing through the Commit Queue. labels Oct 26, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Commit Queue failed

This pull request has multiple commits, but no landing policy was selected.

Add commit-queue-squash PRs the Commit Queue should land as one squashed commit. to land it as one commit, or commit-queue-rebase PRs the Commit Queue should land as multiple self-contained commits. to land the commits separately.

The pull request was removed from the Commit Queue and labeled commit-queue-failed PRs whose Commit Queue landing failed and need manual intervention before retrying. . After resolving the failure, remove that label and add commit-queue PRs queued for automated landing through the Commit Queue. to retry.

Full Commit Queue output
�[36m⠋�[39m Loading data for nodejs/node/pull/63733
�[36m⠋�[39m Loading data for nodejs/node/pull/63733
�[36m⠋�[39m Getting collaborator contacts from README of nodejs/node
�[36m⠋�[39m Getting PR from nodejs/node/pull/63733
�[36m⠋�[39m Getting reviews from nodejs/node/pull/63733
�[36m⠋�[39m Getting comments from nodejs/node/pull/63733
�[36m⠋�[39m Getting commits from nodejs/node/pull/63733
✔  Done loading data for nodejs/node/pull/63733
----------------------------------- PR info ------------------------------------
Title      stream: fix pipeTo assert when writer is released during microtask (#63733)
   ⚠  Could not retrieve the email or name of the PR's author from user's GitHub profile!
Branch     colinaaa:fix-stream-shutdown -> nodejs:main
Labels     needs-ci, commit-queue, web streams
Commits    2
 - stream: fix pipeTo assert when writer is released during microtask
 - stream: narrow pipeTo guard to writer release check
Committers 1
 - Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
PR-URL: https://github.com/nodejs/node/pull/63733
Fixes: https://github.com/nodejs/node/issues/63732
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
------------------------------ Generated metadata ------------------------------
PR-URL: https://github.com/nodejs/node/pull/63733
Fixes: https://github.com/nodejs/node/issues/63732
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
--------------------------------------------------------------------------------
   ℹ  This PR was created on Wed, 03 Jun 2026 11:21:01 GMT
   ✔  Approvals: 1
   ✔  - Matteo Collina (@mcollina) (TSC): https://github.com/nodejs/node/pull/63733#pullrequestreview-5649633765
   ✔  Last GitHub CI successful
   ℹ  Last Full PR CI on 2026-10-26T04:05:49Z: https://ci.nodejs.org/job/node-test-pull-request/78643/
�[36m⠙�[39m Querying data for job/node-test-pull-request/78643/
�[36m⠙�[39m Querying data for job/node-test-pull-request/78643/
�[36m⠙�[39m Querying API for job/node-test-pull-request/78643/
✔  Build data downloaded
   ✔  Last Jenkins CI successful
--------------------------------------------------------------------------------
   ✔  No git cherry-pick in progress
   ✔  No git am in progress
   ✔  No git rebase in progress
--------------------------------------------------------------------------------
�[36m⠹�[39m Bringing origin/main up to date...
�[36m⠹�[39m Bringing origin/main up to date...
From https://github.com/nodejs/node
 * branch                  main       -> FETCH_HEAD
✔  origin/main is now up-to-date
�[36m⠸�[39m Downloading patch for 63733
�[36m⠸�[39m Downloading patch for 63733
From https://github.com/nodejs/node
 * branch                  refs/pull/63733/merge -> FETCH_HEAD
✔  Fetched commits as a2cd8aa883e3..de4d49d5a4e8
--------------------------------------------------------------------------------
Auto-merging lib/internal/webstreams/readablestream.js
[main 0310982662] stream: fix pipeTo assert when writer is released during microtask
 Author: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
 Date: Wed Jun 3 19:15:01 2026 +0800
 2 files changed, 34 insertions(+)
 create mode 100644 test/parallel/test-webstreams-pipeto-writer-released-race.js
Auto-merging lib/internal/webstreams/readablestream.js
[main 37a448d197] stream: narrow pipeTo guard to writer release check
 Author: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
 Date: Wed Jun 3 20:51:46 2026 +0800
 2 files changed, 31 insertions(+), 1 deletion(-)
   ✔  Patches applied
There are 2 commits in the PR. Attempting autorebase.
(node:562) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.
(Use `node --trace-deprecation ...` to show where the warning was created)
Rebasing (2/4)

�[KExecuting: git node land --amend --yes
   ⚠  Found Fixes: https://github.com/nodejs/node/issues/63732, skipping..
--------------------------------- New Message ----------------------------------
stream: fix pipeTo assert when writer is released during microtask

pipeTo defers each slow-path write to a microtask so it does not run
synchronously during enqueue(). A race allows the pipe to shut down and
release its writer after the write is scheduled but before it runs. The
deferred write then hits an ERR_INTERNAL_ASSERTION because
writer[[stream]] is undefined.

Skip the deferred write when shutdown has already started.

Fixes: https://github.com/nodejs/node/issues/63732
Signed-off-by: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/63733
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
--------------------------------------------------------------------------------
[detached HEAD 5d689ea0b2] stream: fix pipeTo assert when writer is released during microtask
 Author: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
 Date: Wed Jun 3 19:15:01 2026 +0800
 2 files changed, 34 insertions(+)
 create mode 100644 test/parallel/test-webstreams-pipeto-writer-released-race.js
Rebasing (3/4)
Rebasing (4/4)

�[KExecuting: git node land --amend --yes
--------------------------------- New Message ----------------------------------
stream: narrow pipeTo guard to writer release check

The shuttingDown guard added by the previous commit is too broad: it
also skips an already-read chunk while the destination is still
writable. The WHATWG Streams spec requires pipeTo to write such chunks
during shutdown.

Check writer[kState].stream instead. It becomes undefined only after
finalize() releases the writer, which is precisely the assertion case.

Add a regression test that aborts after enqueue() and verifies that the
already-read chunk is written.

Signed-off-by: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/63733
Fixes: https://github.com/nodejs/node/issues/63732
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
--------------------------------------------------------------------------------
[detached HEAD 4720ae426e] stream: narrow pipeTo guard to writer release check
 Author: Qingyu Wang <wangqingyu.c0l1n@bytedance.com>
 Date: Wed Jun 3 20:51:46 2026 +0800
 2 files changed, 31 insertions(+), 1 deletion(-)

�[KSuccessfully rebased and updated refs/heads/main.
--------------------------------------------------------------------------------
   ℹ  Add `commit-queue-squash` label to land the PR as one commit, or `commit-queue-rebase` to land as separate commits.

View workflow run

@colinaaa

Copy link
Copy Markdown
Author

@mcollina The queue stopped because this PR has two commits but no landing policy label. Could you please add commit-queue-squash, remove commit-queue-failed, and re-add commit-queue? The second commit refines the first, so squashing makes sense here. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commit-queue-failed PRs whose Commit Queue landing failed and need manual intervention before retrying. needs-ci PRs that need a full CI run. web streams Issues and PRs related to the Web Streams API.

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

3 participants