Skip to content

Commit e8517c2

Browse files
mmomtchevronag
authored andcommitted
stdio: lazy read ReadStream
All stdio ReadStream's use manual start to avoid consuming data for example when a process execs/spawns. Using stream._construct would cause the Readable to incorrectly greedily start reading. Refs: #36251
1 parent db79783 commit e8517c2

File tree

3 files changed

+59
-1
lines changed

3 files changed

+59
-1
lines changed

lib/internal/streams/readable.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ function ReadableState(options, stream, isDuplex) {
166166
// If true, a maybeReadMore has been scheduled.
167167
this.readingMore = false;
168168

169+
this.didRead = false;
170+
169171
this.decoder = null;
170172
this.encoding = null;
171173
if (options && options.encoding) {
@@ -203,7 +205,9 @@ function Readable(options) {
203205
Stream.call(this, options);
204206

205207
destroyImpl.construct(this, () => {
206-
maybeReadMore(this, this._readableState);
208+
if (this._readableState.didRead) {
209+
maybeReadMore(this, this._readableState);
210+
}
207211
});
208212
}
209213

@@ -401,6 +405,8 @@ Readable.prototype.read = function(n) {
401405
const state = this._readableState;
402406
const nOrig = n;
403407

408+
this.didRead = true;
409+
404410
// If we're asking for more than the current hwm, then raise the hwm.
405411
if (n > state.highWaterMark)
406412
state.highWaterMark = computeNewHighWaterMark(n);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
const common = require('../common');
3+
const process = require('process');
4+
5+
let defaultShell;
6+
if (process.platform === 'linux' || process.platform === 'darwin') {
7+
defaultShell = '/bin/sh';
8+
} else if (process.platform === 'win32') {
9+
defaultShell = 'cmd.exe';
10+
} else {
11+
common.skip('This is test exists only on Linux/Win32/OSX');
12+
}
13+
14+
const { execSync } = require('child_process');
15+
const fs = require('fs');
16+
const path = require('path');
17+
const tmpdir = require('../common/tmpdir');
18+
19+
const tmpDir = tmpdir.path;
20+
tmpdir.refresh();
21+
const tmpCmdFile = path.join(tmpDir, 'test-stdin-from-file-spawn-cmd');
22+
const tmpJsFile = path.join(tmpDir, 'test-stdin-from-file-spawn.js');
23+
fs.writeFileSync(tmpCmdFile, 'echo hello');
24+
fs.writeFileSync(tmpJsFile, `
25+
'use strict';
26+
const { spawn } = require('child_process');
27+
// Reference the object to invoke the getter
28+
process.stdin;
29+
setTimeout(() => {
30+
let ok = false;
31+
const child = spawn(process.env.SHELL || '${defaultShell}',
32+
[], { stdio: ['inherit', 'pipe'] });
33+
child.stdout.on('data', () => {
34+
ok = true;
35+
});
36+
child.on('close', () => {
37+
process.exit(ok ? 0 : -1);
38+
});
39+
}, 100);
40+
`);
41+
42+
execSync(`${process.argv[0]} ${tmpJsFile} < ${tmpCmdFile}`);

test/parallel/test-stream-construct.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,13 @@ testDestroy((opts) => new Writable({
268268
assert.strictEqual(constructed, true);
269269
}));
270270
}
271+
272+
{
273+
// Construct should not cause stream to read.
274+
new Readable({
275+
construct: common.mustCall((callback) => {
276+
callback();
277+
}),
278+
read: common.mustNotCall()
279+
});
280+
}

0 commit comments

Comments
 (0)