Skip to content

stream: resume flow when an errored pipe destination is removed#64310

Open
MahinAnowar wants to merge 1 commit into
nodejs:mainfrom
MahinAnowar:fix/stream-pipe-errored-dest-stall
Open

stream: resume flow when an errored pipe destination is removed#64310
MahinAnowar wants to merge 1 commit into
nodejs:mainfrom
MahinAnowar:fix/stream-pipe-errored-dest-stall

Conversation

@MahinAnowar

Copy link
Copy Markdown

Fixes: #53185

Problem

When a source is piped to two destinations and one destination errors synchronously in _write(), the source stops flowing to the healthy destination as well. This is a regression from v20.10.0 (bisected to #50014 in the issue), where the healthy destination received all the data.

Cause

After #50014 ("avoid unnecessary drain for sync stream"), a synchronous write() to a destination that errors returns false without setting needDrain. On the readable side, pause() adds that destination to state.awaitDrainWriters and pauses the source. When the errored destination unpipes, cleanup() only re-invokes the source's drain handler when dest._writableState.needDrain is set — which is now false for the sync-errored destination. So the dead destination is never removed from awaitDrainWriters, pipeOnDrain never sees an empty set, and the source stays paused, starving the healthy destination.

Fix

Call the drain handler unconditionally in cleanup(). pipeOnDrain only removes the given destination from awaitDrainWriters and resumes the source once nothing else is awaiting a drain, so calling it is safe and no-ops when the destination wasn't awaiting a drain. This restores the pre-20.10 behavior (matching the direction @mcollina noted on the issue).

Test

test/parallel/test-stream-pipe-multiple-destinations-error.js pipes a source to a synchronously-erroring destination and a healthy counting destination, writes two over-highWaterMark chunks, and asserts the healthy destination receives everything. It fails on current main (the source stalls and the healthy destination only gets the first chunk) and passes with this change.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/streams

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to the stream subsystem. labels Jul 5, 2026

@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. label Jul 5, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jul 5, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

When a source is piped to multiple destinations and one destination
errors synchronously in `_write()`, `write()` returns false without
setting `needDrain` (since the "avoid unnecessary drain for sync
stream" change). The pipe cleanup only re-invoked the source's drain
handler when the destination still had `needDrain` set, so the errored
destination was never removed from the source's `awaitDrainWriters`
set. The source stayed paused forever and any remaining healthy
destination stopped receiving data.

Always invoke the drain handler during cleanup. `pipeOnDrain` only
removes this destination from the awaiting-drain set and resumes the
source once nothing else is awaiting a drain, so it is safe to call
unconditionally and no-ops when this destination was not awaiting a
drain.

Fixes: nodejs#53185
Signed-off-by: Mahin Anowar <86069420+MahinAnowar@users.noreply.github.com>
@MahinAnowar MahinAnowar force-pushed the fix/stream-pipe-errored-dest-stall branch from b723522 to 683eddc Compare July 5, 2026 22:58
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. stream Issues and PRs related to the stream subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REG 20.9->20.10] stream: Pipe is stopped after error on another pipe from the same source

3 participants