読者です 読者をやめる 読者になる 読者になる

同じソケットを複数プロセスでacceptしてみるテスト

例えばあるサーバープロセスを無停止で新しいコードでビルドしたものと置き換えたいとする。

その場合、たとえ一瞬でも複数プロセスが同じソケットをacceptできるようになっていなければならない。

それがどういう仕組なのかなと思って調べた。

gist7a898e3e3189966314e1

サーバー側は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

なるほどUnixプロセス ― Rubyで学ぶUnixの基礎 - 達人出版会

http://www.oreilly.co.jp/books/9784873115856/