我的 Node.js Process 怎麼不會結束?

JavaScript Event Loop in Brief

工作時碰到了某個以 Node.js 撰寫的 scheduled job(排程工作)一直保持在 running 的狀態,永遠不會進到 complete。這時一般首先想到的是,程式可能在某個地方卡住了,進入無窮等待(像是進入無限迴圈一樣)。

一開始我試著使用懶人 debug 方法:加入幾個 console.log 看看 code 是跑到哪裡停住了。
結果,到程式碼最後一個 console.log 也有被執行到。
但程式依然沒有結束。

如果你是個對 JavaScript 這個語言的運行機制很熟的人,馬上就知道這問題在哪了。

但因為我在這之前我寫的都是其他語言,JavaScript 頂多看看語法差異就上陣了,也因此忽略了 JavaScript 一個很重要的特性:

Event Loop

Event Loop 其實就是 JavaScript 執行 Asynchronous 程式碼片段的方式:

simplified event loop

當 Node.js 執行程式時,會把主程式先執行完。這之間碰到 async code 的話,會將 async code 交給另外的 worker 處理,並在 queue 註冊 callback,主程式則繼續往下進行。
之後就會進入 Event Loop 不斷的監聽 callback,若有 worker 完成,則會將 callback 交給主程式執行。

也因此,Node.js 的 async functions 不會 block 主程式進行,而會丟給背後的 worker 來跑。Worker 和主程式不一樣,可以是多個 Threads,可以達到平行化,也因此 Node.js 鼓勵使用 asynchronous 的方式撰寫。

註:這是我個人簡化後的版本,若有觀念上的錯誤歡迎指正。

Quick Example

這算是蠻經典的例子:

// index.js
setTimeout(() => console.log('callback'), 0)
console.log('end of code')
❯ node index.js
end of code
callback

因為 setTimeout 是 async,會註冊 callback,即便等待時間是 0,也會在主程式跑完後才執行。這也是我一開始 debug 的方法沒成功的原因。

回到正題:我的 Node.js Process 怎麼不會結束?

就是有 worker 還在工作,導致 event loop 一直在等 callback 可以被放進 call stack

找出兇手

既然知道原因了,下一步就是找出是哪個 async code 沒有結束一直在等待。

Node.js 有兩個一直沒有被官方列入文件的方法,可以快速查看現在還在 queue 等待的 work 有哪些:

console.log(process._getActiveHandles())
console.log(process._getActiveRequests())

這兩個底線開頭的方法直到最近還有 issue 在討論要不要加入文件,但看起來就是無限延長,畢竟這個方法印出的結果也是蠻冗長的,包含了很多給人類 debug 時不需要的資訊。

因此我找到了一個 package:

Why Is Node Running

why-is-node-running 是一個利用 Node.js 實驗中的 Async Hooks API 撰寫的 library,可以幫你調查你的 Node.js 程式為什麼不會結束。

原理是在 require 時,利用 side effects 註冊一些 Async Hooks,這樣當 async code 被執行時,這個 module 就會知道,也可以因此監聽有哪些 Code 沒結束。

用法也蠻簡單的:

const log = require('why-is-node-running')  // 一定要一開始就 require,讓 side effects 優先執行

setInterval(function () {}, 1000)
log()  // 印出目前還沒有結束的 work
❯ node index.js
There are 1 handle(s) keeping the process running

# Timeout
/Users/davidye/Projects/node_running/index.js:3 - setInterval(function () {}, 1000)

參考資料

實際上關於 Event Loop 還有很多細節,也有很多已經寫得不錯的文章,列在下方給大家做為參考。

官方關於 Event Loop 的說明文件

Event Loop 好文

其他參考資料