使用node子进程spawn,exec踩过的坑

child_process

Nodejs是单线程单进程的,但是有了child_process模块,可以在程序中直接创建子进程,并使用主进程和子进程之间实现通信。

问题

child_process.exec启动的项目经常性的挂掉。

原因定位:

  1. exec与spawn是有区别的

  2. exec是对spawn的一个封装

  3. 最重要的exec比spawn多了一些默认的option

exec和spawn的源码区分

exec是对execFile的封装,execFile又是对spawn的封装。

每一层封装都是加强一些易用性以及功能。

源码

1
2
3
4
5
6
7
exports.exec = function(command /*, options, callback*/) {
var opts = normalizeExecArgs.apply(null, arguments);
return exports.execFile(opts.file,
opts.args,
opts.options,
opts.callback);
};

exec对于execFile的封装是进行参数处理, 处理的函数:normalizeExecArgs

关键处理逻辑

1
2
3
4
5
6
7
8
9
10
11
if (process.platform === 'win32') {
file = process.env.comspec || 'cmd.exe';
args = ['/s', '/c', '"' + command + '"'];
// Make a shallow copy before patching so we don't clobber the user's
// options object.
options = util._extend({}, options);
options.windowsVerbatimArguments = true;
} else {
file = '/bin/sh';
args = ['-c', command];
}

将简单的command命名做一个,win和linux的平台处理。

此时execFile接受到的就是一个区分平台的command参数。

然后重点来了,继续debug,execFile中:

1
2
3
4
5
6
7
8
var options = {
encoding: 'utf8',
timeout: 0,
maxBuffer: 200 * 1024,
killSignal: 'SIGTERM',
cwd: null,
env: null
};

有这么一段,设置了默认的参数。然后后面又是一些参数处理,最后调用spawn方法启动子进程。

上面的简单流程就是启动一个子进程。到这里都没有什么问题。

继续看,重点又来了:

用过子进程应该知道这个child.stderr

下面的代码就解答了为什么子进程会挂掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
child.stderr.addListener('data', function(chunk) {
stderrLen += chunk.length;

if (stderrLen > options.maxBuffer) {
ex = new Error('stderr maxBuffer exceeded.');
kill();
} else {
if (!encoding)
_stderr.push(chunk);
else
_stderr += chunk;
}
});

逻辑就是,记录子进程的log大小,一旦超过maxBufferkill掉子进程。

原来真相在这里。我们在使用exec时,不知道设置maxBuffer,默认的maxBuffer是200K,当我们子进程日志达到200K时,自动kill()掉了。

exec和spawn的使用区分

不过exec确实比spawn在使用上面要好很多

例如我们执行一个命令

使用exec

require(‘child_process’).exec(‘edp webserver start’);

使用spawn

linux下这么搞

1
2
3
4
5
6
7
8
9
var child = require('child_process').spawn(
'/bin/sh',
['-c','edp webserver start'],
{
cwd: null,
env: null,
windowsVerbatimArguments: false
}
);

win下

1
2
3
4
5
6
7
8
9
var child = require('child_process').spawn(
'cmd.exe',
['/s', '/c', 'edp webserver start'],
{
cwd: null,
env: null,
windowsVerbatimArguments: true
}
);

可见spawn还是比较麻烦的。

解决方案

知道上面原因了,解决方案就有几个了:

  1. 子进程的系统,不再输出日志

  2. maxBuffer这个传一个足够大的参数

  3. 直接使用spawn,放弃使用exec

我觉得最优的方案是直接使用spawn,解除maxBuffer的限制。但是实际处理中,发现直接考出normalizeExecArgs这个方法去处理平台问题,在win下还是有些不好用,mac下没有问题。所以暂时将maxBuffer设置了一个极大值,保证大家的正常使用。然后后续在优化成spawn方法。


转载自:使用node子进程spawn,exec踩过的坑