Skip to content
Permalink
Browse files

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 <vieuxtech@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
  • Loading branch information...
koh110 authored and sam-github committed Sep 22, 2018
1 parent 1656cd2 commit eb8a51a35c961fe36ec78ccb4a176e7091408ba1
@@ -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

{
@@ -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);

0 comments on commit eb8a51a

Please sign in to comment.
You can’t perform that action at this time.