使用node子進程spawn,exec踩過的坑

圖片描述

如何在項目中實現熱更新中提到的一個坑child_process的exec使用問題,下面文章會詳細介紹下,debug到node源碼中的詳細介紹,不容錯過。html

child_process介紹

Nodejs是單線程單進程的,可是有了child_process模塊,能夠在程序中直接建立子進程,並使用主進程和子進程之間實現通訊。 node

對於child_process的使用,你們能夠找找其餘文章,介紹仍是比較多的,本文主要講一下踩過的坑。linux

踩過的坑

在使用EHU(esl-hot-update)這個工具時(對於工具的介紹,參考前面的文章如何在項目中實現熱更新),發現用子進程啓動項目,常常性的掛掉。而後也不知道爲何,甚至懷疑子進程的效率比較低。 git

最後爲了進一步驗證,在一樣的環境下,一個直接啓動服務,一個是使用require('child_process').exec('...') 方式啓動。 github

最後發現使用子進程打開還真的就是使用到必定程度就掛掉。雖然此時也沒有什麼解決方案,可是至少能把問題定位在子進程上了,而不是其餘工具代碼致使程序掛掉。web

定位問題

定位了問題後,網上查找child_process相關資料,發現exec與spawn方法的區別與陷阱 這篇文章提到幾點:windows

  1. exec與spawn是有區別的微信

  2. exec是對spawn的一個封裝app

  3. 最重要的exec比spawn多了一些默認的option函數

基於以上幾點有些頭緒了,可是仍是沒有明確的解決方案。

最後一個辦法,直接斷點到nodejs的child_process.js模塊中嘗試看看問題出在哪裏。

exec和spawn的源碼區分

斷點進去看後,豁然開朗,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大小,一旦超過maxBufferkill掉子進程。

原來真相在這裏。咱們在使用exec時,不知道設置maxBuffer,默認的maxBuffer是200K,當咱們子進程日誌達到200K時,自動kill()掉了。

exec和spawn的使用區分

不過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仍是比較麻煩的。

解決方案

知道上面緣由了,解決方案就有幾個了:

  1. 子進程的系統,再也不輸出日誌

  2. maxBuffer這個傳一個足夠大的參數

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

我以爲最優的方案是直接使用spawn,解除maxBuffer的限制。可是實際處理中,發現直接考出normalizeExecArgs這個方法去處理平臺問題,在win下仍是有些很差用,mac下沒有問題。因此暫時將maxBuffer設置了一個極大值,保證你們的正常使用。而後後續在優化成spawn方法。

吐槽

其實沒有怎麼理解,execFile對於spawn封裝加maxBuffer的這個邏輯,並且感受就算加了,是否也能夠給一個方式,去掉maxBuffer的限制。

難道是子進程的log量會影響性能?

感想

其實在解決這個問題時,發現這個差別/坑還比較意外,由於自身對於node其實還不是很熟,這個子進程的使用其實也是在ehu中第一次遇到。

感覺比較多的就是有時候正對問題去學習/研究,其實效率特別高。

微信公衆號

圖片描述

博客地址

http://tangguangyao.github.io/

相關文章
相關標籤/搜索