Skip to content

Commit eb8a51a

Browse files
koh110sam-github
authored andcommitted
child_process: use non-infinite maxBuffer defaults
Set the default maxBuffer size to 204,800 bytes for execSync, execFileSync, and spawnSync. APIs that return the child output as a string should have non-infinite defaults for maxBuffer sizes to avoid out-of-memory error conditions. A non-infinite default used to be the documented behaviour for all relevant APIs, but the implemented behaviour for execSync, execFileSync and spawnSync was to have no maxBuffer limits. PR-URL: #23027 Refs: #22894 Reviewed-By: Sam Roberts <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 1656cd2 commit eb8a51a

9 files changed

+138
-16
lines changed

doc/api/child_process.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ changes:
721721
process will be killed. **Default:** `'SIGTERM'`.
722722
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
723723
stderr. If exceeded, the child process is terminated. See caveat at
724-
[`maxBuffer` and Unicode][]. **Default:** `Infinity`.
724+
[`maxBuffer` and Unicode][]. **Default:** `200 * 1024`.
725725
* `encoding` {string} The encoding used for all stdio inputs and outputs.
726726
**Default:** `'buffer'`.
727727
* `windowsHide` {boolean} Hide the subprocess console window that would
@@ -788,7 +788,7 @@ changes:
788788
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
789789
stderr. If exceeded, the child process is terminated and any output is
790790
truncated. See caveat at [`maxBuffer` and Unicode][].
791-
**Default:** `Infinity`.
791+
**Default:** `200 * 1024`.
792792
* `encoding` {string} The encoding used for all stdio inputs and outputs.
793793
**Default:** `'buffer'`.
794794
* `windowsHide` {boolean} Hide the subprocess console window that would
@@ -852,7 +852,7 @@ changes:
852852
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
853853
stderr. If exceeded, the child process is terminated and any output is
854854
truncated. See caveat at [`maxBuffer` and Unicode][].
855-
**Default:** `Infinity`.
855+
**Default:** `200 * 1024`.
856856
* `encoding` {string} The encoding used for all stdio inputs and outputs.
857857
**Default:** `'buffer'`.
858858
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses

lib/child_process.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ const {
4646
ChildProcess
4747
} = child_process;
4848

49+
const MAX_BUFFER = 200 * 1024;
50+
4951
exports.ChildProcess = ChildProcess;
5052

5153
function stdioStringToArray(option) {
@@ -206,7 +208,7 @@ exports.execFile = function execFile(file /* , args, options, callback */) {
206208
options = {
207209
encoding: 'utf8',
208210
timeout: 0,
209-
maxBuffer: 200 * 1024,
211+
maxBuffer: MAX_BUFFER,
210212
killSignal: 'SIGTERM',
211213
cwd: null,
212214
env: null,
@@ -560,7 +562,11 @@ var spawn = exports.spawn = function spawn(file, args, options) {
560562
function spawnSync(file, args, options) {
561563
const opts = normalizeSpawnArguments(file, args, options);
562564

563-
options = opts.options;
565+
const defaults = {
566+
maxBuffer: MAX_BUFFER,
567+
...opts.options
568+
};
569+
options = opts.options = Object.assign(defaults, opts.options);
564570

565571
debug('spawnSync', opts.args, options);
566572

test/parallel/test-async-wrap-pop-id-during-load.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ const { spawnSync } = require('child_process');
1616

1717
const ret = spawnSync(
1818
process.execPath,
19-
['--stack_size=150', __filename, 'async']
19+
['--stack_size=150', __filename, 'async'],
20+
{ maxBuffer: Infinity }
2021
);
2122
assert.strictEqual(ret.status, 0,
2223
`EXIT CODE: ${ret.status}, STDERR:\n${ret.stderr}`);

test/parallel/test-child-process-exec-maxbuf.js

+24
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,30 @@ function runChecks(err, stdio, streamName, expected) {
5656
);
5757
}
5858

59+
// default value
60+
{
61+
const cmd = `"${process.execPath}" -e "console.log('a'.repeat(200 * 1024))"`;
62+
63+
cp.exec(
64+
cmd,
65+
common.mustCall((err, stdout, stderr) => {
66+
runChecks(err, { stdout, stderr }, 'stdout', 'a'.repeat(200 * 1024));
67+
})
68+
);
69+
}
70+
71+
// default value
72+
{
73+
const cmd =
74+
`"${process.execPath}" -e "console.log('a'.repeat(200 * 1024 - 1))"`;
75+
76+
cp.exec(cmd, common.mustCall((err, stdout, stderr) => {
77+
assert.ifError(err);
78+
assert.strictEqual(stdout.trim(), 'a'.repeat(200 * 1024 - 1));
79+
assert.strictEqual(stderr, '');
80+
}));
81+
}
82+
5983
const unicode = '中文测试'; // length = 4, byte length = 12
6084

6185
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
require('../common');
3+
4+
// This test checks that the maxBuffer option for child_process.spawnFileSync()
5+
// works as expected.
6+
7+
const assert = require('assert');
8+
const { execFileSync } = require('child_process');
9+
const msgOut = 'this is stdout';
10+
const msgOutBuf = Buffer.from(`${msgOut}\n`);
11+
12+
const args = [
13+
'-e',
14+
`console.log("${msgOut}");`
15+
];
16+
17+
// Verify that an error is returned if maxBuffer is surpassed.
18+
{
19+
assert.throws(() => {
20+
execFileSync(process.execPath, args, { maxBuffer: 1 });
21+
}, (e) => {
22+
assert.ok(e, 'maxBuffer should error');
23+
assert.strictEqual(e.errno, 'ENOBUFS');
24+
// We can have buffers larger than maxBuffer because underneath we alloc 64k
25+
// that matches our read sizes.
26+
assert.deepStrictEqual(e.stdout, msgOutBuf);
27+
return true;
28+
});
29+
}
30+
31+
// Verify that a maxBuffer size of Infinity works.
32+
{
33+
const ret = execFileSync(process.execPath, args, { maxBuffer: Infinity });
34+
35+
assert.deepStrictEqual(ret, msgOutBuf);
36+
}
37+
38+
// Default maxBuffer size is 200 * 1024.
39+
{
40+
assert.throws(() => {
41+
execFileSync(
42+
process.execPath,
43+
['-e', "console.log('a'.repeat(200 * 1024))"]
44+
);
45+
}, (e) => {
46+
assert.ok(e, 'maxBuffer should error');
47+
assert.strictEqual(e.errno, 'ENOBUFS');
48+
return true;
49+
});
50+
}

test/parallel/test-child-process-execfilesync-maxbuf.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,19 @@ const args = [
3434
assert.deepStrictEqual(ret, msgOutBuf);
3535
}
3636

37-
// maxBuffer size is Infinity at default.
37+
// maxBuffer size is 200 * 1024 at default.
3838
{
39-
const ret = execFileSync(
40-
process.execPath,
41-
['-e', "console.log('a'.repeat(200 * 1024))"],
42-
{ encoding: 'utf-8' }
39+
assert.throws(
40+
() => {
41+
execFileSync(
42+
process.execPath,
43+
['-e', "console.log('a'.repeat(200 * 1024))"],
44+
{ encoding: 'utf-8' }
45+
);
46+
}, (e) => {
47+
assert.ok(e, 'maxBuffer should error');
48+
assert.strictEqual(e.errno, 'ENOBUFS');
49+
return true;
50+
}
4351
);
44-
45-
assert.ifError(ret.error);
4652
}

test/parallel/test-child-process-execsync-maxbuf.js

+22
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const args = [
2121
}, (e) => {
2222
assert.ok(e, 'maxBuffer should error');
2323
assert.strictEqual(e.errno, 'ENOBUFS');
24+
// We can have buffers larger than maxBuffer because underneath we alloc 64k
25+
// that matches our read sizes.
2426
assert.deepStrictEqual(e.stdout, msgOutBuf);
2527
return true;
2628
});
@@ -35,3 +37,23 @@ const args = [
3537

3638
assert.deepStrictEqual(ret, msgOutBuf);
3739
}
40+
41+
// Default maxBuffer size is 200 * 1024.
42+
{
43+
assert.throws(() => {
44+
execSync(`"${process.execPath}" -e "console.log('a'.repeat(200 * 1024))"`);
45+
}, (e) => {
46+
assert.ok(e, 'maxBuffer should error');
47+
assert.strictEqual(e.errno, 'ENOBUFS');
48+
return true;
49+
});
50+
}
51+
52+
// Default maxBuffer size is 200 * 1024.
53+
{
54+
const ret = execSync(
55+
`"${process.execPath}" -e "console.log('a'.repeat(200 * 1024 - 1))"`
56+
);
57+
58+
assert.deepStrictEqual(ret.toString().trim(), 'a'.repeat(200 * 1024 - 1));
59+
}

test/parallel/test-child-process-spawnsync-maxbuf.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,23 @@ const args = [
3333
assert.deepStrictEqual(ret.stdout, msgOutBuf);
3434
}
3535

36-
// maxBuffer size is Infinity at default.
36+
// Default maxBuffer size is 200 * 1024.
3737
{
3838
const args = ['-e', "console.log('a'.repeat(200 * 1024))"];
39-
const ret = spawnSync(process.execPath, args, { encoding: 'utf-8' });
39+
const ret = spawnSync(process.execPath, args);
40+
41+
assert.ok(ret.error, 'maxBuffer should error');
42+
assert.strictEqual(ret.error.errno, 'ENOBUFS');
43+
}
44+
45+
// Default maxBuffer size is 200 * 1024.
46+
{
47+
const args = ['-e', "console.log('a'.repeat(200 * 1024 - 1))"];
48+
const ret = spawnSync(process.execPath, args);
4049

4150
assert.ifError(ret.error);
51+
assert.deepStrictEqual(
52+
ret.stdout.toString().trim(),
53+
'a'.repeat(200 * 1024 - 1)
54+
);
4255
}

test/parallel/test-tick-processor-arguments.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ assert(logfile);
2626
const { stdout } = spawnSync(
2727
process.execPath,
2828
[ '--prof-process', '--preprocess', logfile ],
29-
{ cwd: tmpdir.path, encoding: 'utf8' });
29+
{ cwd: tmpdir.path, encoding: 'utf8', maxBuffer: Infinity });
3030

3131
// Make sure that the result is valid JSON.
3232
JSON.parse(stdout);

0 commit comments

Comments
 (0)