hogeとはワイルドカードのようなものです。日々起こった、さまざまなこと −すなわちワイルドカード− を取り上げて日記を書く、という意味で名付けたのかというとそうでもありません。適当に決めたらこんな理由が浮かんできました。
12/18/2012 むう [長年日記]
■ [JS][メモ] Node.js の cluster がどう動くのか
本家ドキュメント に書いてあるけれど,実際何をどうしてるのか気になったので. ドキュメントにも "This causes potentially surprising behavior" とある通りだいぶサプライジングだけど,確かに書いてある通りの挙動をする.
if (cluster.isMaster) {
cluster.fork();
}
else {
http.createServer(function(req, res) { ... }).listen(8080);
}
こんなコードを書いた時,一体何がどうなってマルチプロセシングになるのか.
まずは親側.cluster を見てみる.
cluster.fork = function(env) {
...
return (new cluster.Worker(env));
};
Worker インスタンスを返しているだけ. じゃあ Worker のコンストラクタはどうなっているか.
function Worker(customEnv) {
...
if (cluster.isMaster) {
...
this.process = fork(settings.exec, settings.args, {
'env': envCopy,
'silent': settings.silent,
'execArgv': settings.execArgv
});
} else {
...
}
Worker 内の fork は child_process.fork そのもの.
では子供の方はどうか.子供の方は cluster を呼ばず http を呼んでいるだけなので,ここが最大の謎. http のコードを見てみる.
util.inherits(Server, net.Server);
exports.Server = Server;
exports.createServer = function(requestListener) {
return new Server(requestListener);
};
どうやら net を見れということらしい.listen がどうなっているのかを見る.
Server.prototype.listen = function() {
...
} else if (typeof arguments[1] == 'undefined' ||
typeof arguments[1] == 'function' ||
typeof arguments[1] == 'number') {
// The first argument is the port, no IP given.
listen(self, '0.0.0.0', port, 4, backlog);
...
};
何か別の listen 関数を呼んでいる.これは何者か.
function listen(self, address, port, addressType, backlog, fd) {
if (!cluster) cluster = require('cluster');
if (cluster.isWorker) {
cluster._getServer(self, address, port, addressType, fd, function(handle) {
self._handle = handle;
self._listen2(address, port, addressType, backlog, fd);
});
} else {
self._listen2(address, port, addressType, backlog, fd);
}
}
なんと net 内で cluster.isWorker を評価して振る舞いを変えている! なんだこれは!
子供なら cluster._getServer というものを呼んで self._handle を初期化するらしい. cluster._getServer ってなんじゃい.
cluster._getServer = function(tcpSelf, address, port, addressType, fd, cb) {
...
// Request the fd handler from the master process
var message = {
cmd: 'queryServer',
address: address,
port: port,
addressType: addressType,
fd: fd
};
// The callback will be stored until the master has responded
sendInternalMessage(cluster.worker, message, function(msg, handle) {
cb(handle);
});
};
sendInternalMessage という関数を呼んで,その callback の引数で self._handle を初期化するらしい. ここで起こしているのは 'queryServer' というメッセージ. sendInternalMessage は child.send() の wrapper のようなので割愛. 要はプロセス間通信を発生させていて,ここで親側とネゴるらしい.
では親の 'queryServer' のハンドラはどうなっているのかと言うと,
if (cluster.isMaster) {
...
messageHandler.queryServer = function(message, worker, send) {
// This sequence of information is unique to the connection
// but not to the worker
var args = [message.address,
message.port,
message.addressType,
message.fd];
var key = args.join(':');
var handler;
if (serverHandlers.hasOwnProperty(key)) {
handler = serverHandlers[key];
} else {
handler = serverHandlers[key] = net._createServerHandle.apply(net, args);
}
// echo callback with the fd handler associated with it
send({}, handler);
};
...
要求に対応する handler がなければ作って,あればそれを子に返す. この send というのが何者かというと,先ほど出てきた sendInternalMessage の wrapper. つまり子に handler を送りつける.
子に戻り,net の listen をもう一度見てみると,_listen2 なるメソッドを呼んでいる.
Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
...
if (!self._handle) {
self._handle = createServerHandle(address, port, addressType, fd);
...
親から貰った handle で既に self._handle は初期化されているので,新しく handle を生成することなしに本来の処理を行うということのようだ.
さてこれを実際にシステムコールレベルで見てみると,こんな感じ.
$ strace -f -s 8192 -e sendmsg,recvmsg,write,read node hoge.js
...
[pid 17615] write(3, "{\"cmd\":\"NODE_CLUSTER_online\"}\n", 30) = 30
[pid 17613] recvmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"{\"cmd\":\"NODE_CLUSTER_online\"}\n", 65536}], msg_controllen=0, msg_flags=0}, 0) = 30
[pid 17615] write(3, "{\"addressType\":4,\"port\":8080,\"address\":\"0.0.0.0\",\"cmd\":\"NODE_CLUSTER_queryServer\",\"_requestEcho\":\"1:1\"}\n", 104) = 104
[pid 17613] recvmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"{\"addressType\":4,\"port\":8080,\"address\":\"0.0.0.0\",\"cmd\":\"NODE_CLUSTER_queryServer\",\"_requestEcho\":\"1:1\"}\n", 65536}], msg_controllen=0, msg_flags=0}, 0) = 104
[pid 17613] sendmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"{\"cmd\":\"NODE_HANDLE\",\"type\":\"net.Native\",\"msg\":{\"cmd\":\"NODE_CLUSTER_\",\"_queryEcho\":\"1:1\"}}\n", 91}], msg_controllen=20, {cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, {8}}, msg_flags=0}, 0) = 91
[pid 17615] recvmsg(3, {msg_name(0)=NULL, msg_iov(1)=[{"{\"cmd\":\"NODE_HANDLE\",\"type\":\"net.Native\",\"msg\":{\"cmd\":\"NODE_CLUSTER_\",\"_queryEcho\":\"1:1\"}}\n", 65536}], msg_controllen=24, {cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, {8}}, msg_flags=0}, 0) = 91
[pid 17615] write(3, "{\"addressType\":4,\"port\":8080,\"address\":\"0.0.0.0\",\"cmd\":\"NODE_CLUSTER_listening\"}\n", 81) = 81
[pid 17613] recvmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"{\"addressType\":4,\"port\":8080,\"address\":\"0.0.0.0\",\"cmd\":\"NODE_CLUSTER_listening\"}\n", 65536}], msg_controllen=0, msg_flags=0}, 0) = 81
ここでは pid 17615 が子,pid 17613 が親となっている.