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'`.
|
process will be killed. **Default:** `'SIGTERM'`.
|
||||||
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
|
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
|
||||||
stderr. If exceeded, the child process is terminated. See caveat at
|
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.
|
* `encoding` {string} The encoding used for all stdio inputs and outputs.
|
||||||
**Default:** `'buffer'`.
|
**Default:** `'buffer'`.
|
||||||
* `windowsHide` {boolean} Hide the subprocess console window that would
|
* `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
|
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
|
||||||
stderr. If exceeded, the child process is terminated and any output is
|
stderr. If exceeded, the child process is terminated and any output is
|
||||||
truncated. See caveat at [`maxBuffer` and Unicode][].
|
truncated. See caveat at [`maxBuffer` and Unicode][].
|
||||||
**Default:** `Infinity`.
|
**Default:** `200 * 1024`.
|
||||||
* `encoding` {string} The encoding used for all stdio inputs and outputs.
|
* `encoding` {string} The encoding used for all stdio inputs and outputs.
|
||||||
**Default:** `'buffer'`.
|
**Default:** `'buffer'`.
|
||||||
* `windowsHide` {boolean} Hide the subprocess console window that would
|
* `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
|
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
|
||||||
stderr. If exceeded, the child process is terminated and any output is
|
stderr. If exceeded, the child process is terminated and any output is
|
||||||
truncated. See caveat at [`maxBuffer` and Unicode][].
|
truncated. See caveat at [`maxBuffer` and Unicode][].
|
||||||
**Default:** `Infinity`.
|
**Default:** `200 * 1024`.
|
||||||
* `encoding` {string} The encoding used for all stdio inputs and outputs.
|
* `encoding` {string} The encoding used for all stdio inputs and outputs.
|
||||||
**Default:** `'buffer'`.
|
**Default:** `'buffer'`.
|
||||||
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses
|
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses
|
||||||
|
@ -46,6 +46,8 @@ const {
|
|||||||
ChildProcess
|
ChildProcess
|
||||||
} = child_process;
|
} = child_process;
|
||||||
|
|
||||||
|
const MAX_BUFFER = 200 * 1024;
|
||||||
|
|
||||||
exports.ChildProcess = ChildProcess;
|
exports.ChildProcess = ChildProcess;
|
||||||
|
|
||||||
function stdioStringToArray(option) {
|
function stdioStringToArray(option) {
|
||||||
@ -206,7 +208,7 @@ exports.execFile = function execFile(file /* , args, options, callback */) {
|
|||||||
options = {
|
options = {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
maxBuffer: 200 * 1024,
|
maxBuffer: MAX_BUFFER,
|
||||||
killSignal: 'SIGTERM',
|
killSignal: 'SIGTERM',
|
||||||
cwd: null,
|
cwd: null,
|
||||||
env: null,
|
env: null,
|
||||||
@ -560,7 +562,11 @@ var spawn = exports.spawn = function spawn(file, args, options) {
|
|||||||
function spawnSync(file, args, options) {
|
function spawnSync(file, args, options) {
|
||||||
const opts = normalizeSpawnArguments(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);
|
debug('spawnSync', opts.args, options);
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ const { spawnSync } = require('child_process');
|
|||||||
|
|
||||||
const ret = spawnSync(
|
const ret = spawnSync(
|
||||||
process.execPath,
|
process.execPath,
|
||||||
['--stack_size=150', __filename, 'async']
|
['--stack_size=150', __filename, 'async'],
|
||||||
|
{ maxBuffer: Infinity }
|
||||||
);
|
);
|
||||||
assert.strictEqual(ret.status, 0,
|
assert.strictEqual(ret.status, 0,
|
||||||
`EXIT CODE: ${ret.status}, STDERR:\n${ret.stderr}`);
|
`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
|
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);
|
assert.deepStrictEqual(ret, msgOutBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// maxBuffer size is Infinity at default.
|
// maxBuffer size is 200 * 1024 at default.
|
||||||
{
|
{
|
||||||
const ret = execFileSync(
|
assert.throws(
|
||||||
process.execPath,
|
() => {
|
||||||
['-e', "console.log('a'.repeat(200 * 1024))"],
|
execFileSync(
|
||||||
{ encoding: 'utf-8' }
|
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) => {
|
}, (e) => {
|
||||||
assert.ok(e, 'maxBuffer should error');
|
assert.ok(e, 'maxBuffer should error');
|
||||||
assert.strictEqual(e.errno, 'ENOBUFS');
|
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);
|
assert.deepStrictEqual(e.stdout, msgOutBuf);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -35,3 +37,23 @@ const args = [
|
|||||||
|
|
||||||
assert.deepStrictEqual(ret, msgOutBuf);
|
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);
|
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 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.ifError(ret.error);
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
ret.stdout.toString().trim(),
|
||||||
|
'a'.repeat(200 * 1024 - 1)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ assert(logfile);
|
|||||||
const { stdout } = spawnSync(
|
const { stdout } = spawnSync(
|
||||||
process.execPath,
|
process.execPath,
|
||||||
[ '--prof-process', '--preprocess', logfile ],
|
[ '--prof-process', '--preprocess', logfile ],
|
||||||
{ cwd: tmpdir.path, encoding: 'utf8' });
|
{ cwd: tmpdir.path, encoding: 'utf8', maxBuffer: Infinity });
|
||||||
|
|
||||||
// Make sure that the result is valid JSON.
|
// Make sure that the result is valid JSON.
|
||||||
JSON.parse(stdout);
|
JSON.parse(stdout);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user