同じソケットを複数プロセスでacceptしてみるテスト
例えばあるサーバープロセスを無停止で新しいコードでビルドしたものと置き換えたいとする。
その場合、たとえ一瞬でも複数プロセスが同じソケットをacceptできるようになっていなければならない。
それがどういう仕組なのかなと思って調べた。
サーバー側はUnixDomainSocketを作る。
UnixDomainSocketはファイルであり必ずファイルディスクリプタを持つ。
そのファイルディスクリプタだけをworkerに渡す(同じファイルに書いちゃったけど別のファイルでも別言語でもOK)。
プロセスはforkとexec共に、基本的に親のリソースを引き継ぐ。
親でfd=5のリソースをopen(2)したら、子プロセスでもfd=5は同じリソースとして開かれていてread(2)なりwrite(2)なりできる。
Rubyの場合はこの挙動を安全でないとしてデフォルトでfdをexec時に閉じるようになっているので、 この安全策を解除する工夫が必要になる。つまり若干危ういことをする。
close_on_exec=false
は「このリソースはexecしたときに勝手にcloseしないでね」というフラグ。
close_others: false
オプションは、「execしたときに勝手に0,1,2以外のリソースをcloseしないでね」というオプション。
fork+execを3回繰り返すと、3つのworkerができあがる。
workerはそれぞれでacceptしているので同じソケットで同時に待ち受けていることになる。
プログラムを起動してみる。
$ ruby multi_accept.rb server # Ctrl+Cで終了
$ ps aux | grep ruby *** 73191 0.0 0.0 2432772 660 s001 R+ 11:21AM 0:00.00 grep ruby *** 72956 0.0 0.0 2484812 7912 s004 S+ 11:21AM 0:00.03 ruby multi_accept.rb worker 7 *** 72955 0.0 0.0 2485836 7852 s004 S+ 11:21AM 0:00.04 ruby multi_accept.rb worker 7 *** 72954 0.0 0.0 2467404 7832 s004 S+ 11:21AM 0:00.04 ruby multi_accept.rb worker 7 *** 72926 0.0 0.0 2457164 7780 s004 S+ 11:21AM 0:00.04 ruby multi_accept.rb server
複数プロセスが動いたっぽい。
ソケットにアクセスしてみる。 クライアント側も並列に動かしてみると、workerが並列に動いているのが分かる。
$ irb -rsocket irb(main):001:0> Array.new(10){ Thread.new { puts UNIXSocket.open("/tmp/test.sock"){|c| c.recv(16)} } }.each(&:join) hello 72956 hello 72954 hello 72955 hello 72954 hello 72956 hello 72955 hello 72954 hello 72956 hello 72955 hello 72956 => 10
10回繰り返し同じソケットを訪れているが、レスポンスに含まれるpidはバラバラになっているので、 見事同じソケットを複数プロセスでacceptできていることが確認できた。
プロセスの分散具合はOSががんばっているものと思われる(今回はOS Xで実験)。
これを応用すれば、どんなサーバープログラムでも無停止でプロセスを置き換え可能になる。
supervisor, circus, Server::Starter 等々が類似プログラムなのかなあ(雑)。
参考:
http://docs.ruby-lang.org/ja/2.2.0/class/UNIXServer.html
Server::Starterから学ぶhot deployの仕組み - $shibayu36->blog;
Server::Starter を使って複数の Fluentd で1つのポートを待ち受ける - sonots:blog