Skip to content

Commit 9cd75c7

Browse files
committed
child_process: only stop readable side of stream passed to proc
Closing the underlying resource completely has the unwanted side effect that the stream can no longer be used at all, including passing it to other child processes. What we want to avoid is accidentally reading from the stream; accordingly, it should be sufficient to stop its readable side manually, and otherwise leave the underlying resource intact. Fixes: nodejs#27097 Refs: nodejs#21209
1 parent b773f77 commit 9cd75c7

File tree

3 files changed

+38
-8
lines changed

3 files changed

+38
-8
lines changed

lib/internal/child_process.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -384,12 +384,12 @@ ChildProcess.prototype.spawn = function(options) {
384384
continue;
385385
}
386386

387-
// The stream is already cloned and piped, thus close it.
387+
// The stream is already cloned and piped, thus stop its readable side,
388+
// otherwise we might attempt to read from the stream when at the same time
389+
// the child process does.
388390
if (stream.type === 'wrap') {
389-
stream.handle.close();
390-
if (stream._stdio && stream._stdio instanceof EventEmitter) {
391-
stream._stdio.emit('close');
392-
}
391+
stream.handle.readStop();
392+
stream._stdio.pause();
393393
continue;
394394
}
395395

test/parallel/test-child-process-server-close.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ const tmpdir = require('../common/tmpdir');
88
tmpdir.refresh();
99

1010
const server = net.createServer((conn) => {
11-
conn.on('close', common.mustCall());
12-
1311
spawn(process.execPath, ['-v'], {
1412
stdio: ['ignore', conn, 'ignore']
15-
}).on('close', common.mustCall());
13+
}).on('close', common.mustCall(() => {
14+
conn.end();
15+
}));
1616
}).listen(common.PIPE, () => {
1717
const client = net.connect(common.PIPE, common.mustCall());
1818
client.on('data', () => {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
5+
// Regression test for https://github.com/nodejs/node/issues/27097.
6+
// Check that (cat [p1] ; cat [p2]) | cat [p3] works.
7+
8+
const { spawn } = require('child_process');
9+
const p3 = spawn('cat', { stdio: ['pipe', 'pipe', process.stderr] });
10+
const p1 = spawn('cat', { stdio: ['pipe', p3.stdin, process.stderr] });
11+
const p2 = spawn('cat', { stdio: ['pipe', p3.stdin, process.stderr] });
12+
p3.stdout.setEncoding('utf8');
13+
14+
// Write three different chunks:
15+
// - 'hello' from this process to p1 to p3 back to us
16+
// - 'world' from this process to p2 to p3 back to us
17+
// - 'foobar' from this process to p3 back to us
18+
// Do so sequentially in order to avoid race conditions.
19+
p1.stdin.end('hello\n');
20+
p3.stdout.once('data', common.mustCall((chunk) => {
21+
assert.strictEqual(chunk, 'hello\n');
22+
p2.stdin.end('world\n');
23+
p3.stdout.once('data', common.mustCall((chunk) => {
24+
assert.strictEqual(chunk, 'world\n');
25+
p3.stdin.end('foobar\n');
26+
p3.stdout.once('data', common.mustCall((chunk) => {
27+
assert.strictEqual(chunk, 'foobar\n');
28+
}));
29+
}));
30+
}));

0 commit comments

Comments
 (0)