child_process: measure buffer length in bytes
This change fixes a known issue where `maxBuffer` limits by characters rather than bytes. Benchmark added to confirm no performance regression occurs with this change. PR-URL: https://github.com/nodejs/node/pull/6764 Fixes: https://github.com/nodejs/node/issues/1901 Reviewed-By: Brian White <mscdex@mscdex.net>
This commit is contained in:
parent
33c7b45378
commit
c9a5990a76
30
benchmark/child_process/child-process-exec-stdout.js
Normal file
30
benchmark/child_process/child-process-exec-stdout.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common.js');
|
||||||
|
const bench = common.createBenchmark(main, {
|
||||||
|
len: [64, 256, 1024, 4096, 32768],
|
||||||
|
dur: [5]
|
||||||
|
});
|
||||||
|
|
||||||
|
const exec = require('child_process').exec;
|
||||||
|
function main(conf) {
|
||||||
|
bench.start();
|
||||||
|
|
||||||
|
const dur = +conf.dur;
|
||||||
|
const len = +conf.len;
|
||||||
|
|
||||||
|
const msg = `"${'.'.repeat(len)}"`;
|
||||||
|
msg.match(/./);
|
||||||
|
const options = {'stdio': ['ignore', 'pipe', 'ignore']};
|
||||||
|
// NOTE: Command below assumes bash shell.
|
||||||
|
const child = exec(`while\n echo ${msg}\ndo :; done\n`, options);
|
||||||
|
|
||||||
|
var bytes = 0;
|
||||||
|
child.stdout.on('data', function(msg) {
|
||||||
|
bytes += msg.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
child.kill();
|
||||||
|
bench.end(bytes);
|
||||||
|
}, dur * 1000);
|
||||||
|
}
|
@ -1,20 +1,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
var common = require('../common.js');
|
const common = require('../common.js');
|
||||||
var bench = common.createBenchmark(main, {
|
const bench = common.createBenchmark(main, {
|
||||||
len: [64, 256, 1024, 4096, 32768],
|
len: [64, 256, 1024, 4096, 32768],
|
||||||
dur: [5]
|
dur: [5]
|
||||||
});
|
});
|
||||||
|
|
||||||
var spawn = require('child_process').spawn;
|
const spawn = require('child_process').spawn;
|
||||||
function main(conf) {
|
function main(conf) {
|
||||||
bench.start();
|
bench.start();
|
||||||
|
|
||||||
var dur = +conf.dur;
|
const dur = +conf.dur;
|
||||||
var len = +conf.len;
|
const len = +conf.len;
|
||||||
|
|
||||||
var msg = '"' + Array(len).join('.') + '"';
|
const msg = '"' + Array(len).join('.') + '"';
|
||||||
var options = { 'stdio': ['ignore', 'ipc', 'ignore'] };
|
const options = {'stdio': ['ignore', 'ipc', 'ignore']};
|
||||||
var child = spawn('yes', [msg], options);
|
const child = spawn('yes', [msg], options);
|
||||||
|
|
||||||
var bytes = 0;
|
var bytes = 0;
|
||||||
child.on('message', function(msg) {
|
child.on('message', function(msg) {
|
||||||
@ -23,7 +23,7 @@ function main(conf) {
|
|||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
child.kill();
|
child.kill();
|
||||||
var gbits = (bytes * 8) / (1024 * 1024 * 1024);
|
const gbits = (bytes * 8) / (1024 * 1024 * 1024);
|
||||||
bench.end(gbits);
|
bench.end(gbits);
|
||||||
}, dur * 1000);
|
}, dur * 1000);
|
||||||
}
|
}
|
||||||
|
@ -146,15 +146,13 @@ exports.execFile = function(file /*, args, options, callback*/) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var encoding;
|
var encoding;
|
||||||
var _stdout;
|
var stdoutState;
|
||||||
var _stderr;
|
var stderrState;
|
||||||
|
var _stdout = [];
|
||||||
|
var _stderr = [];
|
||||||
if (options.encoding !== 'buffer' && Buffer.isEncoding(options.encoding)) {
|
if (options.encoding !== 'buffer' && Buffer.isEncoding(options.encoding)) {
|
||||||
encoding = options.encoding;
|
encoding = options.encoding;
|
||||||
_stdout = '';
|
|
||||||
_stderr = '';
|
|
||||||
} else {
|
} else {
|
||||||
_stdout = [];
|
|
||||||
_stderr = [];
|
|
||||||
encoding = null;
|
encoding = null;
|
||||||
}
|
}
|
||||||
var stdoutLen = 0;
|
var stdoutLen = 0;
|
||||||
@ -176,16 +174,23 @@ exports.execFile = function(file /*, args, options, callback*/) {
|
|||||||
|
|
||||||
if (!callback) return;
|
if (!callback) return;
|
||||||
|
|
||||||
// merge chunks
|
var stdout = Buffer.concat(_stdout, stdoutLen);
|
||||||
var stdout;
|
var stderr = Buffer.concat(_stderr, stderrLen);
|
||||||
var stderr;
|
|
||||||
if (!encoding) {
|
var stdoutEncoding = encoding;
|
||||||
stdout = Buffer.concat(_stdout);
|
var stderrEncoding = encoding;
|
||||||
stderr = Buffer.concat(_stderr);
|
|
||||||
} else {
|
if (stdoutState && stdoutState.decoder)
|
||||||
stdout = _stdout;
|
stdoutEncoding = stdoutState.decoder.encoding;
|
||||||
stderr = _stderr;
|
|
||||||
}
|
if (stderrState && stderrState.decoder)
|
||||||
|
stderrEncoding = stderrState.decoder.encoding;
|
||||||
|
|
||||||
|
if (stdoutEncoding)
|
||||||
|
stdout = stdout.toString(stdoutEncoding);
|
||||||
|
|
||||||
|
if (stderrEncoding)
|
||||||
|
stderr = stderr.toString(stderrEncoding);
|
||||||
|
|
||||||
if (ex) {
|
if (ex) {
|
||||||
// Will be handled later
|
// Will be handled later
|
||||||
@ -245,39 +250,45 @@ exports.execFile = function(file /*, args, options, callback*/) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (child.stdout) {
|
if (child.stdout) {
|
||||||
if (encoding)
|
stdoutState = child.stdout._readableState;
|
||||||
child.stdout.setEncoding(encoding);
|
|
||||||
|
|
||||||
child.stdout.addListener('data', function(chunk) {
|
child.stdout.addListener('data', function(chunk) {
|
||||||
stdoutLen += chunk.length;
|
// If `child.stdout.setEncoding()` happened in userland, convert string to
|
||||||
|
// Buffer.
|
||||||
|
if (stdoutState.decoder) {
|
||||||
|
chunk = Buffer.from(chunk, stdoutState.decoder.encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdoutLen += chunk.byteLength;
|
||||||
|
|
||||||
if (stdoutLen > options.maxBuffer) {
|
if (stdoutLen > options.maxBuffer) {
|
||||||
ex = new Error('stdout maxBuffer exceeded');
|
ex = new Error('stdout maxBuffer exceeded');
|
||||||
|
stdoutLen -= chunk.byteLength;
|
||||||
kill();
|
kill();
|
||||||
} else {
|
} else {
|
||||||
if (!encoding)
|
|
||||||
_stdout.push(chunk);
|
_stdout.push(chunk);
|
||||||
else
|
|
||||||
_stdout += chunk;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.stderr) {
|
if (child.stderr) {
|
||||||
if (encoding)
|
stderrState = child.stderr._readableState;
|
||||||
child.stderr.setEncoding(encoding);
|
|
||||||
|
|
||||||
child.stderr.addListener('data', function(chunk) {
|
child.stderr.addListener('data', function(chunk) {
|
||||||
stderrLen += chunk.length;
|
// If `child.stderr.setEncoding()` happened in userland, convert string to
|
||||||
|
// Buffer.
|
||||||
|
if (stderrState.decoder) {
|
||||||
|
chunk = Buffer.from(chunk, stderrState.decoder.encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
stderrLen += chunk.byteLength;
|
||||||
|
|
||||||
if (stderrLen > options.maxBuffer) {
|
if (stderrLen > options.maxBuffer) {
|
||||||
ex = new Error('stderr maxBuffer exceeded');
|
ex = new Error('stderr maxBuffer exceeded');
|
||||||
|
stderrLen -= chunk.byteLength;
|
||||||
kill();
|
kill();
|
||||||
} else {
|
} else {
|
||||||
if (!encoding)
|
|
||||||
_stderr.push(chunk);
|
_stderr.push(chunk);
|
||||||
else
|
|
||||||
_stderr += chunk;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
15
test/parallel/test-child-process-maxBuffer-stderr.js
Normal file
15
test/parallel/test-child-process-maxBuffer-stderr.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const cp = require('child_process');
|
||||||
|
const unicode = '中文测试'; // Length = 4, Byte length = 13
|
||||||
|
|
||||||
|
if (process.argv[2] === 'child') {
|
||||||
|
console.error(unicode);
|
||||||
|
} else {
|
||||||
|
const cmd = `${process.execPath} ${__filename} child`;
|
||||||
|
|
||||||
|
cp.exec(cmd, {maxBuffer: 10}, common.mustCall((err, stdout, stderr) => {
|
||||||
|
assert.strictEqual(err.message, 'stderr maxBuffer exceeded');
|
||||||
|
}));
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
// Refs: https://github.com/nodejs/node/issues/1901
|
|
||||||
const common = require('../common');
|
const common = require('../common');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const cp = require('child_process');
|
const cp = require('child_process');
|
Loading…
x
Reference in New Issue
Block a user