😺

Node.jsのイベントループについて調べてみた

に公開

はじめに

私自身Node.js上でのアプリケーションの開発運用を2年間行ってきましたが、高レベルのライブラリのおかげで原理を詳しく理解せずにそれなりにやれていましたが、何かトラブルがあったときに解決できるようにするため改めてNode.jsのイベントループについて勉強しようと思いました。
勉強がてらに本記事を書いてみたので、間違いがあればご指摘していただけますと幸いです。

イベントループとは

イベントループとは、監視すべきイベントがある限りwhileループを回す仕組みです。

timeout.js
setTimeout(function() {
  console.log('Timed out');
}, 1000);
console.log('Waiting...');
$ node timeout.js
Waiting...
Timed out

上記の例ではtimeout.jsの最終行の「Waiting...」を出力した後に実行が終わらずに「Timed out」を出力してから実行が終了しています。これはsetTimeout()が実行した際に監視すべきイベントが登録されたからです。
Node.jsでは、イベントループにlibuvというライブラリを使用しており、監視すべきイベントはHandleRequestになります。アクティブなHandle、Requestが存在すればイベントループを開始(継続)します。

イベントループの中身

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

Node.js公式ドキュメントに記載のようにイベントループは上の図の四角のフェーズ(厳密には他にもフェーズはあります)を順に処理しています。各々のフェーズでFIFOキューを持っていて、そこにはlibuvのHandle(Request)が格納されています。各フェーズに到達した時にHandle(Request)に設定されたコールバックが実行可能であれば実行されます。

以下、各フェーズでどのようなコールバックが実行されるのか説明します。

timers

setTimeout()setInterval()に設定されたコールバックが閾値(setTimeOutの場合、第二引数のms)に設定した時間が経過していたら実行されます。

pending callbacks

前回のループイテレーションで延期したコールバックを実行します。

idle, prepare

libuvのAPIでIdle Handle(Request)とPrepare Handle(Request)に設定したコールバックを実行します。(どのようなコールバックが実行されるのかはまだ調べられてません)

poll

I/Oのイベントを待ち受け、検知します。ここでは一旦イベントループが停止してOSがI/Oイベントを待ち受け、検知したらコールバックを実行します。timersが設定されていて最短の閾値の時間に到達したらtimersフェーズに戻ります。また、setImmediate()によってスクリプトが設定されていたらcheckフェーズに移ります。そうでなければイベントを待ち続けます。

check

setImmediate()にコールバックが設定されていたら実行します。

close callbacks

socket.destroy()のようなHandle(Request)が終了するイベントが発生した場合にコールバックを実行します。

process.nextTick()

上記のフェーズには含まれていませんが、process.nextTickにコールバックを設定するとフェーズのどこにいようとも操作が完了したタイミングでコールバックが実行されます。

さいごに

今回はイベントループがlibuvのHandle(Request)によって実行されること、イベントループの各フェーズで実行されるコールバックについてまとめてみました。今後、Node.jsのAPIがどのようにHandle(Request)を登録しているか調べてみたいと思います。

Discussion