Skip to content

stream: adding new 'data' handler doesn't resume stream after removing 'readable' handler #24474

Closed
@tvald

Description

@tvald
  • Version: 11.2.0
  • Platform: Windows 10 x64
  • Subsystem: Streams

Description
After a readable handler has been added and removed from a stream, the stream no longer begins / resumes flowing when a data handler is added to the stream. This is a breaking change from 10.x and appears inconsistent with the (convoluted) intended behavior of streams. (More on this below.)

Related issues: #22209, #24366, #24281

Example

const stream = fs.createReadStream('foo.txt'); // some file that exists

const onData = () => { console.log('DATA'); };
const onReadable = () => {
  console.log('READABLE');
  stream.off('readable', onReadable);
  stream.on('data', onData);
  //stream.resume(); // <-- this causes data to be emitted
};

stream.on('readable', onReadable);

Expected Output:
The readable handler is called, and then the data handler is called.

Actual behavior:
The readable handler is called, but not the data handler.

Further Discussion
It is desirable for a stream to resume flowing when no readable handler is attached and a data handler is added. For example, one might generate a stream and check that it successfully opens (via the readable handler) before returning the stream to be consumed elsewhere (via the data handler). This worked at least up through 10.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

All Readable streams begin in paused mode but can be switched to flowing mode in one of the following ways: (1) Adding a data event handler...

Adding a readable event handler automatically makes the stream to stop flowing, and the data to be consumed via readable.read(). If the readable event handler is removed, then the stream will start flowing again if there is a data event handler.

Three States

Calling readable.pause(), readable.unpipe(), or receiving backpressure will cause the readable.readableFlowing to be set as false, temporarily halting the flowing of events but not halting the generation of data. While in this state, attaching a listener for the 'data' event will not switch readable.readableFlowing to true.

Without having looked at code, I interpret the "Three States" description to indicate that there is no way to return from the readableFlowing == false state to the readableFlowing == null state. Hence, adding a readable handler destroys the auto-start behavior of the data handler.

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 data handler IFF it was added before the readable handler.

Metadata

Metadata

Assignees

No one assigned

    Labels

    streamIssues and PRs related to the stream subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions