Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

process.stdin example from docs no longer works in node 10 #20503

Closed
apieceofbart opened this issue May 3, 2018 · 24 comments
Closed

process.stdin example from docs no longer works in node 10 #20503

apieceofbart opened this issue May 3, 2018 · 24 comments
Labels
confirmed-bug Issues with confirmed bugs. process Issues and PRs related to the process subsystem. stream Issues and PRs related to the stream subsystem.

Comments

@apieceofbart
Copy link

apieceofbart commented May 3, 2018

I am running process.stdin example from docs:

process.stdin.setEncoding('utf8');

process.stdin.on('readable', () => {
  const chunk = process.stdin.read();
  if (chunk !== null) {
    process.stdout.write(`data: ${chunk}`);
  }
});

process.stdin.on('end', () => {
  process.stdout.write('end');
});

I expect the same results as in node v9 and below: command line should wait for my input and return it prefixed with data: . Instead, process is closed right away. I believe this is regression as example works fine in node v9 and v8.

(edited by @addaleax: syntax highlighting)

@addaleax addaleax added the confirmed-bug Issues with confirmed bugs. label May 3, 2018
@addaleax
Copy link
Member

addaleax commented May 3, 2018

Bisecting this turned into trisecting, because it seems like we introduced this issue gradually:

  • 1e0f331 made a first change in behaviour for the test case, because now only the first chunk of data is being read before the process exits on its own
  • 0778f79 made the change in behaviour to break this fully. I’m not quite sure (yet) why this is happening, though.

@nodejs/streams

@addaleax addaleax added stream Issues and PRs related to the stream subsystem. process Issues and PRs related to the process subsystem. v10.x labels May 3, 2018
@dmfay
Copy link

dmfay commented May 3, 2018

I finally narrowed down the problem I was having with Node 10 and node-pg-query-stream to this and lo and behold someone else had already reported it! What I noticed was that I'd get the first batch of results out of the stream, but then it would abort prematurely without emitting end.

The commit message for 0778f79 looks at odds with the conditional if (!state.destroyed && (state.length || state.ended)) though -- if it's not supposed to emit readable when the stream has ended, shouldn't it be negating state.ended at minimum? In the testcase accompanying the change it looks like the conditional might be satisfied by state.length.

@AyushG3112
Copy link
Contributor

AyushG3112 commented May 4, 2018

Can confirm, Changing the condition to if (!state.destroyed && (state.length || !state.ended)) causes the first chunk of data to be read, after which the process exits (which I suppose is fixed by 4383c0a as @addaleax mentioned)

@addaleax
Copy link
Member

addaleax commented May 4, 2018

I think the idea behind (state.length || state.ended) was that we do emit the readable event both for incoming data, and for the end-of-data marker. So readable is supposed to be emitted even if the stream has ended, just not after it has been destroyed.

/cc @nodejs/streams @mafintosh

@mafintosh
Copy link
Member

mafintosh commented May 4, 2018 via email

@addaleax
Copy link
Member

addaleax commented May 4, 2018

@mafintosh The PR description has a standalone test case, if you want

@mafintosh
Copy link
Member

@addaleax ah, didn't see that in the email thread but indeed here.

@mcollina
Copy link
Member

mcollina commented May 4, 2018

See also #20449.

1e0f331 and 4383c0a should stay in ideally (with a fix if needed).

@dmfay
Copy link

dmfay commented May 4, 2018

@mafintosh this might be more involved than you're looking for but the problem I was having is reducible to the "fast-reader" test case in https://github.com/brianc/node-pg-query-stream if you check out that repository and run the tests.

@klimashkin
Copy link

Any update on that? It breaks some of our tools in development

@lundibundi
Copy link
Member

I'd like to work on the issue, I seem to have found the underlying cause and hopefully a correct solution =). Will submit a PR soon.

lundibundi added a commit to lundibundi/node that referenced this issue Aug 10, 2018
Avoid trying to emit 'readable' due to the fact that
state.length is always >= state.highWaterMark if highWaterMark is 0.
Therefore upon .read(0) call (through .on('readable')) stream assumed
that it has enough data to emit 'readable' even though
state.length === 0 instead of issuing _read(). Which led to the TTY
not recognizing that someone is waiting for the input.

Fixes: nodejs#20503
Refs: nodejs#18372
@mcollina
Copy link
Member

Fixed in fe47b8b

targos pushed a commit that referenced this issue Aug 11, 2018
Avoid trying to emit 'readable' due to the fact that
state.length is always >= state.highWaterMark if highWaterMark is 0.
Therefore upon .read(0) call (through .on('readable')) stream assumed
that it has enough data to emit 'readable' even though
state.length === 0 instead of issuing _read(). Which led to the TTY
not recognizing that someone is waiting for the input.

Fixes: #20503
Refs: #18372

PR-URL: #21690
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
addaleax added a commit to addaleax/node that referenced this issue Jan 4, 2019
@addaleax
Copy link
Member

addaleax commented Jan 4, 2019

Opened a documentation PR at #25344.

@sergiogiogio
Copy link

Thanks for the prompt follow-up and action on this issue. I have few issues though:
1- The modified example with the "while loop" even works in node v10 (i.e. before this fix): the program accepts multiple lines of input. Hence the question: Which test case is this fix addressing?
2- I am not sure the "while loop" is actually expected because the example works as expected with a filesystem stream, and a stdin stream should work the same way.

After few more tests, here are 2 notables differences between node v9 and v10 (and later):
1- in v9 the 'readable' and 'end' events are always active, while in v10 these events are activated at startup thanks to the 'on' function but we then need to explicitly call process.stdin.read() from the callback so that subsequent event can be triggered. The while loop can be replaced a single call to process.stdin.read() and the program then works, it seems that the second call process.stdin.read is needed to activate the 'readable' events

process.stdin.setEncoding('utf8');

process.stdin.on('readable', () => {
  console.log("readable");
  const chunk = process.stdin.read();
  console.log("read");
  if (chunk !== null) {
    process.stdout.write(`data: ${chunk}`);
  }
  process.stdin.read(); // <== without this function call the callback is not called and the program exists abruptly
});

process.stdin.on('end', () => {
  process.stdout.write('end');
});

2- in v9 the 'readable' event is fired immediately at program startup even without any user input (and process.stdin.read() returns null in this initial run), in v10 'readable' is not fired immediately at program startup but only upon actual user input (and process.stdin.read() returns the actual input in this initial run)

In conclusion I think the example is correct since it works fine on filesystem streams. There may be an issue with how callbacks are activated which would require further attention.

@addaleax
Copy link
Member

addaleax commented Jan 5, 2019

The modified example with the "while loop" even works in node v10 (i.e. before this fix): the program accepts multiple lines of input.

That’s because it’s arguably more correct – in fact, this should work for all streams since the introduction of streams 3 (i.e. streams with a readable event), including object-mode streams.

Hence the question: Which test case is this fix addressing?

I don’t quite understand this question.

I am not sure the "while loop" is actually expected because the example works as expected with a filesystem stream, and a stdin stream should work the same way.

I’m not sure, but this could also mean that you’re relying on a peculiarity of filesystem streams?

@mcollina One thing I’m wondering, .read() always returns all data, right? So shouldn’t a .read() call also trigger further _read()s on its own?

@mcollina
Copy link
Member

mcollina commented Jan 6, 2019

I am not sure the "while loop" is actually expected because the example works as expected with a filesystem stream, and a stdin stream should work the same way.

The fact that they work is not assured, and it depends on low-level file system timing, the business of the operating system, the type of disk, caches, etc. The while loop is necessary in all cases.

@mcollina One thing I’m wondering, .read() always returns all data, right? So shouldn’t a .read() call also trigger further _read()s on its own?

It returns a chunk of data, the chunks are not concatenated. The while loop is necessary to consume all data, and it will trigger a new _read once the data in the buffer is below highWaterMark. When .read() returns null, a new 'readable' will be emitted when there is more data.

@sergiogiogio
Copy link

@mcollina When .read() returns null, a new 'readable' will be emitted when there is more data.

Thanks a lot for this clarification - I believe it would be useful to make this explicit in the Stream documentation (https://nodejs.org/docs/latest-v11.x/api/stream.html#stream_event_readable), and maybe consider updating the example there too. Beyond Stream and Process, a few more documentation pages use "if" instead of "while" and may benefit from an update too: Crypto (https://nodejs.org/api/crypto.html) and Buffer (https://nodejs.org/api/buffer.html)

@addaleax Hence the question: Which test case is this fix addressing?

My understanding now is that the original issue opened by @apieceofbart is caused by a wrong example rather than a node bug. The correct example (with the while loop) works fine for me in v10. So the code injected in fe47b8b is probably fixing another issue.

In any case thanks a lot for the help, I am satisfied with the change to "while" in my code so I will not bother you further on this issue.

mcollina added a commit to mcollina/node that referenced this issue Jan 7, 2019
The 'readable' event assumes that calls to readable.read() happens
within that event handler until readable.read() returns null.

Fixes: nodejs#20503
addaleax added a commit that referenced this issue Jan 9, 2019
Fixes: #20503

PR-URL: #25344
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
mcollina added a commit that referenced this issue Jan 10, 2019
The 'readable' event assumes that calls to readable.read() happens
within that event handler until readable.read() returns null.

Fixes: #20503
PR-URL: #25375
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
refack pushed a commit to refack/node that referenced this issue Jan 14, 2019
Fixes: nodejs#20503

PR-URL: nodejs#25344
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
addaleax pushed a commit that referenced this issue Jan 14, 2019
The 'readable' event assumes that calls to readable.read() happens
within that event handler until readable.read() returns null.

Fixes: #20503
PR-URL: #25375
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
BridgeAR pushed a commit to BridgeAR/node that referenced this issue Jan 16, 2019
Fixes: nodejs#20503

PR-URL: nodejs#25344
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
BridgeAR pushed a commit to BridgeAR/node that referenced this issue Jan 16, 2019
The 'readable' event assumes that calls to readable.read() happens
within that event handler until readable.read() returns null.

Fixes: nodejs#20503
PR-URL: nodejs#25375
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
@anentropic
Copy link
Contributor

anentropic commented Apr 7, 2019

The docs for this feature still aren't very clear...

Has anyone tried piping in a file longer than 65536 bytes via stdin?

The example from the docs shows this code:

process.stdin.on('readable', () => {
  let chunk;
  // Use a loop to make sure we read all available data.
  while ((chunk = process.stdin.read()) !== null) {
    process.stdout.write(`data: ${chunk}`);
  }
});

Note the code comment:

Use a loop to make sure we read all available data.

Perhaps it's stupid of me, but I assumed this meant the while loop there would cause it to read all of stdin, so I made some code like:

process.stdin.on('readable', () => {
  let chunk, chunks, content;
  chunks = [];
  // Use a loop to make sure we read all available data.
  while ((chunk = process.stdin.read()) !== null) {
    chunks.push(chunk);
  }
  content = chunks.join('');
  console.log(content.length);
  render(content);
});

What I see from this naïve code though is that my content.length is always 65536, but I know the file is longer than that - what I've got in content is truncated.

Unfortunately because the code was trying to render(content), exceptions prevented me immediately noticing what actually happens: which is that the on('readable' ...) event handler gets called multiple times... then eventually the end event when there is no more to read.

So I have to structure the code more like this:

      var chunk, chunks, content;
      chunks = [];
      process.stdin.on('end', () => {
        content = chunks.join('');
        console.error(content.length); // 88106 - yay!
        return content;
      });
      process.stdin.on('readable', function() {
        console.error('READABLE')
        while ((chunk = process.stdin.read()) !== null) {
          chunks.push(chunk);
          console.error('CHUNK')
        }
      });

This makes perfect sense, but I think something of this behaviour ought to be mentioned in the docs.

https://nodejs.org/api/process.html#process_process_stdin
Just says:

The process.stdin property returns a stream connected to stdin (fd 0). It is a net.Socket (which is a Duplex stream) unless fd 0 refers to a file, in which case it is a Readable stream.

and the docs for Readable streams waffle on quite a bit but I don't see anything about getting multiple readable events:
https://nodejs.org/api/stream.html#stream_readable_streams

I understand from the long discussion in the thread above that what the while loop is really for is to ensure that the stream doesn't end early, if there is initially nothing to read?

Perhaps this could all be made a bit clearer somehow.

@mcollina
Copy link
Member

mcollina commented Apr 8, 2019

@anentropic I think a PR to improve the docs would be welcomed!

@anentropic
Copy link
Contributor

@mcollina ok no prob, give me a day or two

BethGriggs pushed a commit that referenced this issue Apr 28, 2019
Fixes: #20503

PR-URL: #25344
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
BethGriggs pushed a commit that referenced this issue Apr 28, 2019
The 'readable' event assumes that calls to readable.read() happens
within that event handler until readable.read() returns null.

Fixes: #20503
PR-URL: #25375
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
BethGriggs pushed a commit that referenced this issue May 10, 2019
Fixes: #20503

PR-URL: #25344
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
BethGriggs pushed a commit that referenced this issue May 10, 2019
The 'readable' event assumes that calls to readable.read() happens
within that event handler until readable.read() returns null.

Fixes: #20503
PR-URL: #25375
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
MylesBorins pushed a commit that referenced this issue May 16, 2019
Fixes: #20503

PR-URL: #25344
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
MylesBorins pushed a commit that referenced this issue May 16, 2019
The 'readable' event assumes that calls to readable.read() happens
within that event handler until readable.read() returns null.

Fixes: #20503
PR-URL: #25375
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug Issues with confirmed bugs. process Issues and PRs related to the process subsystem. stream Issues and PRs related to the stream subsystem.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants