Repository navigation
stream: adding new 'data' handler doesn't resume stream after removing 'readable' handler #24474
Description
Activity
@nodejs/streams
- addedstreamIssues and PRs related to Node.js streams.Issues and PRs related to Node.js streams.windowsIssues and PRs related to the Windows platform.Issues and PRs related to the Windows platform.
on Nov 25, 2018 For the example in the description you can use
stream.removeListener()instead ofstream.off()and it will work as expected. However if the'data'listener if added on next tick (or later) the issue persists. Here is a test case:'use strict'; const { Readable } = require('stream'); const readable = new Readable({ read() {} }); function read() {} readable.setEncoding('utf8'); readable.on('readable', read); readable.removeListener('readable', read); process.nextTick(function() { readable.on('data', function(chunk) { console.log(chunk); }); readable.push('hello'); });
The expected behavior is to have 'hello' printed on the console but nothing is actually printed because
flowingis set tofalseLine 830 in 7032e59
state.flowing = false; when the
'readable'listener is added, andupdateReadableListening()does not find any registeredLines 877 to 890 in 7032e59
function updateReadableListening(self) { const state = self._readableState; state.readableListening = self.listenerCount('readable') > 0; if (state.resumeScheduled && !state.paused) { // Flowing needs to be set to true now, otherwise // the upcoming resume will not flow. state.flowing = true; // Crude way to check if we should resume } else if (self.listenerCount('data') > 0) { self.resume(); } } 'data'listener.I'm not sure if this is actually intended behavior but it is the reason why https://github.com/TooTallNate/node-https-proxy-agent no longer work in Node.js >= 10.0.0 and that lib has ~6 millions weekly downloads (I think we should add it to CITGM).
cc: @nodejs/streams @mcollina
- removedwindowsIssues and PRs related to the Windows platform.Issues and PRs related to the Windows platform.
on Jul 17, 2019 - added a commit that references this issue
on Jul 26, 2019 - added a commit that references this issue
on Jul 30, 2019 - added 2 commits that reference this issue
on Oct 4, 2019 For the example in the description you can use stream.removeListener() instead of stream.off() and it will work as expected.
I believe this part of the problem was resolved in 1665a93
2 remaining items
- added a commit that references this issue
on Jan 3, 2020 - added 2 commits that reference this issue
on Jan 14, 2020 - added a commit that references this issue
on Feb 6, 2020 - added a commit that references this issue
on Apr 22, 2022 - added 2 commits that reference this issue
on Aug 25, 2024 - added 2 commits that reference this issue
on Sep 28, 2025 - added 2 commits that reference this issue
on Sep 29, 2025 - added a commit that references this issue
on Jul 27, 2026
Description
After a
readablehandler has been added and removed from a stream, the stream no longer begins / resumes flowing when adatahandler is added to the stream. This is a breaking change from10.xand appears inconsistent with the (convoluted) intended behavior of streams. (More on this below.)Related issues: #22209, #24366, #24281
Example
Expected Output:
The
readablehandler is called, and then thedatahandler is called.Actual behavior:
The
readablehandler is called, but not thedatahandler.Further Discussion
It is desirable for a stream to resume flowing when no
readablehandler is attached and adatahandler is added. For example, one might generate a stream and check that it successfully opens (via thereadablehandler) before returning the stream to be consumed elsewhere (via thedatahandler). This worked at least up through10.x.With a certain reading, the current gap in behavior could be held as consistent with the "Three States" / "under the hood" explanation of stream modes, although that in itself may contradict the "Two Reading Modes" abstraction.
From streams documentation:
Two Reading Modes
Three States
Without having looked at code, I interpret the "Three States" description to indicate that there is no way to return from the
readableFlowing == falsestate to thereadableFlowing == nullstate. Hence, adding areadablehandler destroys the auto-start behavior of thedatahandler.This is sufficiently counter-intuitive that a slew of issues have been filed in the past few months, such as #24366 and #24281. In fact, a patch was even accepted in #22209 that ensures the auto-start behavior of a
datahandler IFF it was added before thereadablehandler.