如何在项目中实现热更新中提到的一个坑child_process的exec使用问题,下面文章会详细介绍下,debug到node源码中的详细介绍,不容错过。html
Nodejs是单线程单进程的,可是有了child_process模块,能够在程序中直接建立子进程,并使用主进程和子进程之间实现通讯。 node
对于child_process的使用,你们能够找找其余文章,介绍仍是比较多的,本文主要讲一下踩过的坑。linux
在使用EHU(esl-hot-update)这个工具时(对于工具的介绍,参考前面的文章如何在项目中实现热更新),发现用子进程启动项目,常常性的挂掉。而后也不知道为何,甚至怀疑子进程的效率比较低。 git
最后为了进一步验证,在一样的环境下,一个直接启动服务,一个是使用require('child_process').exec('...')
方式启动。 github
最后发现使用子进程打开还真的就是使用到必定程度就挂掉。虽然此时也没有什么解决方案,可是至少能把问题定位在子进程上了,而不是其余工具代码致使程序挂掉。web
定位了问题后,网上查找child_process相关资料,发现exec与spawn方法的区别与陷阱 这篇文章提到几点:windows
exec与spawn是有区别的微信
exec是对spawn的一个封装app
最重要的exec比spawn多了一些默认的option函数
基于以上几点有些头绪了,可是仍是没有明确的解决方案。
最后一个办法,直接断点到nodejs的child_process.js模块中尝试看看问题出在哪里。
断点进去看后,豁然开朗,exec
是对execFile
的封装,execFile
又是对spawn
的封装。
每一层封装都是增强一些易用性以及功能。
直接看源码:
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
关键逻辑
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
中:
var options = { encoding: 'utf8', timeout: 0, maxBuffer: 200 * 1024, killSignal: 'SIGTERM', cwd: null, env: null };
有这么一段,设置了默认的参数。而后后面又是一些参数处理,最后调用spawn
方法启动子进程。
上面的简单流程就是启动一个子进程。到这里都没有什么问题。
继续看,重点又来了:
用过子进程应该知道这个child.stderr
下面的代码就解答了为何子进程会挂掉。
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大小,一旦超过maxBuffer
就kill
掉子进程。
原来真相在这里。咱们在使用exec
时,不知道设置maxBuffer
,默认的maxBuffer
是200K,当咱们子进程日志达到200K时,自动kill()
掉了。
不过exec确实比spawn在使用上面要好不少
例如咱们执行一个命令
使用exec
require('child_process').exec('edp webserver start');
使用spawn
linux下这么搞
var child = require('child_process').spawn( '/bin/sh', ['-c','edp webserver start'], { cwd: null, env: null, windowsVerbatimArguments: false } );
win下
var child = require('child_process').spawn( 'cmd.exe', ['/s', '/c', 'edp webserver start'], { cwd: null, env: null, windowsVerbatimArguments: true } );
可见spawn仍是比较麻烦的。
知道上面缘由了,解决方案就有几个了:
子进程的系统,再也不输出日志
maxBuffer这个传一个足够大的参数
直接使用spawn,放弃使用exec
我以为最优的方案是直接使用spawn
,解除maxBuffer
的限制。可是实际处理中,发现直接考出normalizeExecArgs
这个方法去处理平台问题,在win下仍是有些很差用,mac下没有问题。因此暂时将maxBuffer
设置了一个极大值,保证你们的正常使用。而后后续在优化成spawn
方法。
其实没有怎么理解,execFile对于spawn封装加maxBuffer的这个逻辑,并且感受就算加了,是否也能够给一个方式,去掉maxBuffer的限制。
难道是子进程的log量会影响性能?
其实在解决这个问题时,发现这个差别/坑还比较意外,由于自身对于node其实还不是很熟,这个子进程的使用其实也是在ehu中第一次遇到。
感觉比较多的就是有时候正对问题去学习/研究,其实效率特别高。