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: https://github.com/nodejs/node/pull/23027 Refs: https://github.com/nodejs/node/pull/22894 Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
parent
1656cd2edb
commit
eb8a51a35c
@ -721,7 +721,7 @@ changes:
|
||||
process will be killed. **Default:** `'SIGTERM'`.
|
||||
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
|
||||
stderr. If exceeded, the child process is terminated. See caveat at
|
||||
[`maxBuffer` and Unicode][]. **Default:** `Infinity`.
|
||||
[`maxBuffer` and Unicode][]. **Default:** `200 * 1024`.
|
||||
* `encoding` {string} The encoding used for all stdio inputs and outputs.
|
||||
**Default:** `'buffer'`.
|
||||
* `windowsHide` {boolean} Hide the subprocess console window that would
|
||||
@ -788,7 +788,7 @@ changes:
|
||||
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
|
||||
stderr. If exceeded, the child process is terminated and any output is
|
||||
truncated. See caveat at [`maxBuffer` and Unicode][].
|
||||
**Default:** `Infinity`.
|
||||
**Default:** `200 * 1024`.
|
||||
* `encoding` {string} The encoding used for all stdio inputs and outputs.
|
||||
**Default:** `'buffer'`.
|
||||
* `windowsHide` {boolean} Hide the subprocess console window that would
|
||||
@ -852,7 +852,7 @@ changes:
|
||||
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
|
||||
stderr. If exceeded, the child process is terminated and any output is
|
||||
truncated. See caveat at [`maxBuffer` and Unicode][].
|
||||
**Default:** `Infinity`.
|
||||
**Default:** `200 * 1024`.
|
||||
* `encoding` {string} The encoding used for all stdio inputs and outputs.
|
||||
**Default:** `'buffer'`.
|
||||
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses
|
||||
|
@ -46,6 +46,8 @@ const {
|
||||
ChildProcess
|
||||
} = child_process;
|
||||
|
||||
const MAX_BUFFER = 200 * 1024;
|
||||
|
||||
exports.ChildProcess = ChildProcess;
|
||||
|
||||
function stdioStringToArray(option) {
|
||||
@ -206,7 +208,7 @@ exports.execFile = function execFile(file /* , args, options, callback */) {
|
||||
options = {
|
||||
encoding: 'utf8',
|
||||
timeout: 0,
|
||||
maxBuffer: 200 * 1024,
|
||||
maxBuffer: MAX_BUFFER,
|
||||
killSignal: 'SIGTERM',
|
||||
cwd: null,
|
||||
env: null,
|
||||
@ -560,7 +562,11 @@ var spawn = exports.spawn = function spawn(file, args, options) {
|
||||
function spawnSync(file, args, options) {
|
||||
const opts = normalizeSpawnArguments(file, args, options);
|
||||
|
||||
options = opts.options;
|
||||
const defaults = {
|
||||
maxBuffer: MAX_BUFFER,
|
||||
...opts.options
|
||||
};
|
||||
options = opts.options = Object.assign(defaults, opts.options);
|
||||
|
||||
debug('spawnSync', opts.args, options);
|
||||
|
||||
|
@ -16,7 +16,8 @@ const { spawnSync } = require('child_process');
|
||||
|
||||
const ret = spawnSync(
|
||||
process.execPath,
|
||||
['--stack_size=150', __filename, 'async']
|
||||
['--stack_size=150', __filename, 'async'],
|
||||
{ maxBuffer: Infinity }
|
||||
);
|
||||
assert.strictEqual(ret.status, 0,
|
||||
`EXIT CODE: ${ret.status}, STDERR:\n${ret.stderr}`);
|
||||
|
@ -56,6 +56,30 @@ function runChecks(err, stdio, streamName, expected) {
|
||||
);
|
||||
}
|
||||
|
||||
// default value
|
||||
{
|
||||
const cmd = `"${process.execPath}" -e "console.log('a'.repeat(200 * 1024))"`;
|
||||
|
||||
cp.exec(
|
||||
cmd,
|
||||
common.mustCall((err, stdout, stderr) => {
|
||||
runChecks(err, { stdout, stderr }, 'stdout', 'a'.repeat(200 * 1024));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// default value
|
||||
{
|
||||
const cmd =
|
||||
`"${process.execPath}" -e "console.log('a'.repeat(200 * 1024 - 1))"`;
|
||||
|
||||
cp.exec(cmd, common.mustCall((err, stdout, stderr) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(stdout.trim(), 'a'.repeat(200 * 1024 - 1));
|
||||
assert.strictEqual(stderr, '');
|
||||
}));
|
||||
}
|
||||
|
||||
const unicode = '中文测试'; // length = 4, byte length = 12
|
||||
|
||||
{
|
||||
|
50
test/parallel/test-child-process-execfilesync-maxBuffer.js
Normal file
50
test/parallel/test-child-process-execfilesync-maxBuffer.js
Normal file
@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
|
||||
// This test checks that the maxBuffer option for child_process.spawnFileSync()
|
||||
// works as expected.
|
||||
|
||||
const assert = require('assert');
|
||||
const { execFileSync } = require('child_process');
|
||||
const msgOut = 'this is stdout';
|
||||
const msgOutBuf = Buffer.from(`${msgOut}\n`);
|
||||
|
||||
const args = [
|
||||
'-e',
|
||||
`console.log("${msgOut}");`
|
||||
];
|
||||
|
||||
// Verify that an error is returned if maxBuffer is surpassed.
|
||||
{
|
||||
assert.throws(() => {
|
||||
execFileSync(process.execPath, args, { maxBuffer: 1 });
|
||||
}, (e) => {
|
||||
assert.ok(e, 'maxBuffer should error');
|
||||
assert.strictEqual(e.errno, 'ENOBUFS');
|
||||
// We can have buffers larger than maxBuffer because underneath we alloc 64k
|
||||
// that matches our read sizes.
|
||||
assert.deepStrictEqual(e.stdout, msgOutBuf);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that a maxBuffer size of Infinity works.
|
||||
{
|
||||
const ret = execFileSync(process.execPath, args, { maxBuffer: Infinity });
|
||||
|
||||
assert.deepStrictEqual(ret, msgOutBuf);
|
||||
}
|
||||
|
||||
// Default maxBuffer size is 200 * 1024.
|
||||
{
|
||||
assert.throws(() => {
|
||||
execFileSync(
|
||||
process.execPath,
|
||||
['-e', "console.log('a'.repeat(200 * 1024))"]
|
||||
);
|
||||
}, (e) => {
|
||||
assert.ok(e, 'maxBuffer should error');
|
||||
assert.strictEqual(e.errno, 'ENOBUFS');
|
||||
return true;
|
||||
});
|
||||
}
|
@ -34,13 +34,19 @@ const args = [
|
||||
assert.deepStrictEqual(ret, msgOutBuf);
|
||||
}
|
||||
|
||||
// maxBuffer size is Infinity at default.
|
||||
// maxBuffer size is 200 * 1024 at default.
|
||||
{
|
||||
const ret = execFileSync(
|
||||
process.execPath,
|
||||
['-e', "console.log('a'.repeat(200 * 1024))"],
|
||||
{ encoding: 'utf-8' }
|
||||
assert.throws(
|
||||
() => {
|
||||
execFileSync(
|
||||
process.execPath,
|
||||
['-e', "console.log('a'.repeat(200 * 1024))"],
|
||||
{ encoding: 'utf-8' }
|
||||
);
|
||||
}, (e) => {
|
||||
assert.ok(e, 'maxBuffer should error');
|
||||
assert.strictEqual(e.errno, 'ENOBUFS');
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
assert.ifError(ret.error);
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ const args = [
|
||||
}, (e) => {
|
||||
assert.ok(e, 'maxBuffer should error');
|
||||
assert.strictEqual(e.errno, 'ENOBUFS');
|
||||
// We can have buffers larger than maxBuffer because underneath we alloc 64k
|
||||
// that matches our read sizes.
|
||||
assert.deepStrictEqual(e.stdout, msgOutBuf);
|
||||
return true;
|
||||
});
|
||||
@ -35,3 +37,23 @@ const args = [
|
||||
|
||||
assert.deepStrictEqual(ret, msgOutBuf);
|
||||
}
|
||||
|
||||
// Default maxBuffer size is 200 * 1024.
|
||||
{
|
||||
assert.throws(() => {
|
||||
execSync(`"${process.execPath}" -e "console.log('a'.repeat(200 * 1024))"`);
|
||||
}, (e) => {
|
||||
assert.ok(e, 'maxBuffer should error');
|
||||
assert.strictEqual(e.errno, 'ENOBUFS');
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Default maxBuffer size is 200 * 1024.
|
||||
{
|
||||
const ret = execSync(
|
||||
`"${process.execPath}" -e "console.log('a'.repeat(200 * 1024 - 1))"`
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(ret.toString().trim(), 'a'.repeat(200 * 1024 - 1));
|
||||
}
|
||||
|
@ -33,10 +33,23 @@ const args = [
|
||||
assert.deepStrictEqual(ret.stdout, msgOutBuf);
|
||||
}
|
||||
|
||||
// maxBuffer size is Infinity at default.
|
||||
// Default maxBuffer size is 200 * 1024.
|
||||
{
|
||||
const args = ['-e', "console.log('a'.repeat(200 * 1024))"];
|
||||
const ret = spawnSync(process.execPath, args, { encoding: 'utf-8' });
|
||||
const ret = spawnSync(process.execPath, args);
|
||||
|
||||
assert.ok(ret.error, 'maxBuffer should error');
|
||||
assert.strictEqual(ret.error.errno, 'ENOBUFS');
|
||||
}
|
||||
|
||||
// Default maxBuffer size is 200 * 1024.
|
||||
{
|
||||
const args = ['-e', "console.log('a'.repeat(200 * 1024 - 1))"];
|
||||
const ret = spawnSync(process.execPath, args);
|
||||
|
||||
assert.ifError(ret.error);
|
||||
assert.deepStrictEqual(
|
||||
ret.stdout.toString().trim(),
|
||||
'a'.repeat(200 * 1024 - 1)
|
||||
);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ assert(logfile);
|
||||
const { stdout } = spawnSync(
|
||||
process.execPath,
|
||||
[ '--prof-process', '--preprocess', logfile ],
|
||||
{ cwd: tmpdir.path, encoding: 'utf8' });
|
||||
{ cwd: tmpdir.path, encoding: 'utf8', maxBuffer: Infinity });
|
||||
|
||||
// Make sure that the result is valid JSON.
|
||||
JSON.parse(stdout);
|
||||
|
Loading…
x
Reference in New Issue
Block a user