弄死 Node.js 進程有幾種方法

有幾個緣由會致使 Node.js 進程終止。其中一些是能夠避免的,例如拋出錯誤時,而另外一些是沒法防止的,例如內存不足。全局 process 是一個 Event Emitter 實例,當執行正常退出時,將發出一個 exit 事件。而後程序代碼能夠經過偵聽這個事件來執行最後的同步清理工做。前端

下面是能夠主動觸發進程終止的一些方法:node

操做 例子
手動流程退出 process.exit(1)
未捕獲的異常 throw new Error()
未兌現的 promise Promise.reject()
忽略的錯誤事件 EventEmitter#emit('error')
未處理的信號 $ kill <PROCESS_ID>

其中有許可能是屬於偶然被觸發的,例如未捕獲的錯誤或未處理的 promise,可是其中也有爲了直接使進程終止而建立的。程序員

進程退出

使用 process.exit(code) 來終止進程是最直接的方法。這在當你知道本身的過程已經到了生命週期的盡頭時很是有用。 code 值是可選的,默認值爲0,最大能夠設爲 255。0 表示進程運行成功,而任何非零的數字都表示發生了問題。這些值能夠被許多不一樣的外部工具使用。例如當測試套件運行時,非零值表示測試失敗。面試

直接調用 process.exit() 時,不會向控制檯寫入任何隱式文本。若是你編寫了以錯誤表示形式調用此方法的代碼,則你的代碼應該用戶輸出錯誤來幫助他們解決問題。例如運行如下代碼:shell

$ node -e "process.exit(42)"
$ echo $?

在這種狀況下,單行的 Node.js 程序不會輸出任何信息,儘管 shell 程序確實會打印退出狀態。遇到這樣的進程退出,用戶將沒法理解究竟發生了什麼事情。因此要參考下面這段程序配置錯誤時會執行的代碼:segmentfault

function checkConfig(config) {
  if (!config.host) {
    console.error("Configuration is missing 'host' parameter!");
    process.exit(1);
  }
}

在這種狀況下,用戶沒會很清楚發生了什麼。他們運行這個程序,將錯誤輸出到控制檯上,而且他們可以糾正這個問題。promise

process.exit() 方法很是強大。儘管它在程序代碼中有本身的用途,但實際上絕對不該該將其引入可重用的庫中。若是在庫中確實發生了錯誤,則應拋出這個錯誤,以便程序能夠決定應該如何處理它。bash

exceprion、rejection 和發出的 Error

雖然 process.exit() 頗有用,但對於運行時錯誤,你須要使用其餘工具。例如當程序正在處理 HTTP 請求時,通常來講錯誤不該該終止進程,而是僅返回錯誤響應。發生錯誤的位置信息也頗有用,這正是應該拋出 Error 對象的地方。服務器

Error 類的實例包含對致使錯誤的緣由有用的元數據,例如棧跟蹤信息和消息字符串。從 Error 擴展你本身的錯誤類是很常見的操做。單獨實例化 Error 不會有太多反作用,若是發生錯誤則必須拋出。微信

在使用 throw 關鍵字或發生某些邏輯錯誤時,將引起 Error。發生這種狀況時,當前棧將會「展開」,這意味着每一個函數都會退出,直到一個調用函數將調用包裝在 try/catch 語句中爲止。遇到此語句後,將調用 catch 分支。若是錯誤沒有被包含在 try/catch 中,則該錯誤被視爲未捕獲。

雖然你應該使用帶有 Errorthrow 關鍵字,例如 throw new Error('foo'),但從技術上講,你能夠拋出任何東西。一旦拋出了什麼東西,它就被認爲是一個例外。拋出 Error 實例很是重要,由於捕獲這些錯誤的代碼極可能會指望獲得錯誤屬性。

Node.js 內部庫中經常使用的另外一種模式是提供一個 .code 屬性,該屬性是一個字符串值,在發行版之間應保持一致。好比錯誤的 .code 值是 ERR_INVALID_URI,即便是供人類可讀的 .message 屬性可能會更改,但這個 code 值也不該被更改。

可悲的是,一種更經常使用的區分錯誤的模式是檢查 .message 屬性,這個屬性一般是動態的,由於可能回須要修改拼寫錯誤。這種方法是很冒險的,也是容易出錯的。 Node.js 生態中沒有完美的解決方案來區分全部庫中的錯誤。

當引起未捕獲的錯誤時,控制檯中將打印棧跟蹤信息,而且進程將回以退出狀態 1 終止。這是此類異常的例子:

/tmp/foo.js:1
throw new TypeError('invalid foo');
^
Error: invalid foo
    at Object.<anonymous> (/tmp/foo.js:2:11)
    ... TRUNCATED ...
    at internal/main/run_main_module.js:17:47

上面的棧跟蹤片斷代表錯誤發生在名爲 foo.js 的文件的第 2 行第 11 列。

全局的 process 是一個事件發射器,能夠經過偵聽 uncaughtException 事件來攔截未捕獲的錯誤。下面是一個使用它的例子,在退出前攔截錯誤以發送異步消息:

const logger = require('./lib/logger.js');
process.on('uncaughtException', (error) => {
  logger.send("An uncaught exception has occured", error, () => {
    console.error(error);
    process.exit(1);
  });
});

Promise 拒絕與拋出錯誤很是類似。若是 Promise 中的 reject() 方法被調用,或者在異步函數中引起了錯誤,則 Promise 能夠拒絕。在這方面,如下兩個例子大體相同:

Promise.reject(new Error('oh no'));

(async () => {
  throw new Error('oh no');
})();

這是輸出到控制檯的消息:

(node:52298) UnhandledPromiseRejectionWarning: Error: oh no
    at Object.<anonymous> (/tmp/reject.js:1:16)
    ... TRUNCATED ...
    at internal/main/run_main_module.js:17:47
(node:52298) UnhandledPromiseRejectionWarning: Unhandled promise
  rejection. This error originated either by throwing inside of an
  async function without a catch block, or by rejecting a promise
  which was not handled with .catch().

與未捕獲的異常不一樣,從 Node.js v14 開始,這些 rejection 不會使進程崩潰。在將來的 Node.js 版本中,會使當前進程崩潰。當這些未處理的 rejection 發生時,你還能夠攔截事件,偵聽 process 對象上的另外一個事件:

process.on('unhandledRejection', (reason, promise) => {});

事件發射器是 Node.js 中的常見模式,許多對象實例都從這個基類擴展而來,並在庫和程序中使用。它們很是歡迎,值得和 error 與 rejection 放在一塊兒討論。

當事件發射器發出沒有偵聽器的 error 事件時,將會拋出所發出的參數。而後將拋出出一個錯誤並致使進程退出:

events.js:306
    throw err; // Unhandled 'error' event
    ^
Error [ERR_UNHANDLED_ERROR]: Unhandled error. (undefined)
    at EventEmitter.emit (events.js:304:17)
    at Object.<anonymous> (/tmp/foo.js:1:40)
    ... TRUNCATED ...
    at internal/main/run_main_module.js:17:47 {
  code: 'ERR_UNHANDLED_ERROR',
  context: undefined
}

確保在你使用的事件發射器實例中偵聽 error 事件,以便你的程序能夠正常處理事件而不會崩潰。

信號

信號是操做系統提供的機制,用於把用數字表示的消息從一個程序發送到另外一個程序。這些數字一般用等價的常量字符串來表示。例如,信號 SIGKILL 表明數字信號 9。信號能夠有不一樣的用途,但一般用於終止程序。

不一樣的操做系統能夠定義不一樣的信號,可是下面列表中的信號通常是通用的:

名稱 編號 可處理 Node.js 默認 信號用途
SIGHUP 1 終止 父終端已關閉
SIGINT 2 終止 終端試圖中斷,按下 Ctrl + C
SIGQUIT 3 終止 終端試圖退出,按下 Ctrl + D
SIGKILL 9 終止 進程被強行殺死
SIGUSR1 10 啓動調試器 用戶定義的信號1
SIGUSR2 12 終止 用戶定義的信號2
SIGTERM 12 終止 表明優雅的終止
SIGSTOP 19 終止 進程被強行中止

若是程序能夠選擇實現信號處理程序,則 Handleable 一列則爲。爲的兩個信號沒法處理。 Node.js 默認 這一列告訴你在收到信號時,Node.js 程序的默認操做是什麼。最後一個信號用途指出了信號對應的做用。

在 Node.js 程序中處理這些信號能夠經過偵聽 process 對象上的更多事件來完成:

#!/usr/bin/env node
console.log(`Process ID: ${process.pid}`);
process.on('SIGHUP', () => console.log('Received: SIGHUP'));
process.on('SIGINT', () => console.log('Received: SIGINT'));
setTimeout(() => {}, 5 * 60 * 1000); // keep process alive

在終端窗口中運行這個程序,而後按 Ctrl + C,這個進程不會被終止。它將會聲明已接收到 SIGINT 信號。切換到另外一個終端窗口,並根據輸出的進程 ID 值執行如下命令:

$ kill -s SIGHUP <PROCESS_ID>

這演示了一個程序怎樣向另外一個程序發送信號,而且在第一個終端中運行的 Node.js 程序中輸出它所接收到的 SIGHUP 信號。

你可能已經猜到了,Node.js 也能把命令發送到其餘程序。能夠用下面的命令以把信號從臨時的 Node.js 進程發送到你現有的進程:

$ node -e "process.kill(<PROCESS_ID>, 'SIGHUP')"

這還會在你的第一個程序中顯示 SIGHUP 消息。如今,若是你想終止第一個進程,要運行下面的命令向其發送不能處理的 SIGKILL 信號:

$ kill -9 <PROCESS_ID>

這時程序應該結束。

這些信號在 Node.js 程序中常常用於處理正常的關閉事件。例如,當 Kubernetes Pod 終止時,它將向程序發送 SIGTERM 信號,以後啓動 30 秒計時器。而後程序能夠在這 30 秒內正常關閉本身,關閉鏈接並保存數據。若是該進程在此計時器後仍保持活動狀態,則 Kubernetes 將向其發送一個 SIGKILL

173382ede7319973.gif


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索