TCPServer#accept_nonblockの実装を少し追った
こんにちは。お金がないので、まともに年が越せるのか心配なきたけーです。
ちょっと前まで、社内の読書会でWorking With TCP Socketsという本を読んでいました。Rubyを使ってTCPSocketを使ったプログラミングについて学ぶことができる楽しい本なのですが、ちまちまと読みなおしています。
その中に、「Non-blocking IO」という章があって、「read, write, accept, connect」をノンブロッキングに行うメソッドについて紹介されています。今回の記事では特にaccept_nonblock
メソッドに着目して、Rubyがそれをどのように実装しているのか追ってみます。
acceptを用いたプログラム
TCPServer#accept
を用いる。接続を確立するまでブロックする。
require 'socket' server = TCPServer.new(4481) loop do connection = server.accept # ... end
straceした結果
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 accept(3,
accpetシステムコールを呼び出して、ブロックしている。
accept_nonblockを用いたプログラム
TCPServer#accept_nonblock
を用いる。接続が確立できなかったら、例外が発生する。
require 'socket' server = TCPServer.new(4481) loop do begin connection = server.accept_nonblock rescue Errno::EAGAIN retry end end
straceした結果
fcntl
(ファイルディスクリプタを操作するシステムコール)で、O_NONBLOCKフラグを立てているのが分かります。
fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 fcntl(3, F_GETFL) = 0x802 (flags O_RDWR|O_NONBLOCK) accept(3, 0x7fff7a9c8720, [128]) = -1 EAGAIN (Resource temporarily unavailable) rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 fcntl(3, F_GETFL) = 0x802 (flags O_RDWR|O_NONBLOCK) fcntl(3, F_GETFL) = 0x802 (flags O_RDWR|O_NONBLOCK) accept(3, 0x7fff7a9c8720, [128]) = -1 EAGAIN (Resource temporarily unavailable)
accept_nonblockの実装を追う
Rubyのソースコードを読んでみましょう。対象のRubyは2.0.0-p353です。
TCPSocket#accept_nonblockの実装は ext/socket/tcpserver.c#L92 にあるのがわかります。
実際にファイルディスクリプタを操作する処理は、rsock_s_accept_nonblock
でおこなっていそうです。
...rsock_s_accept_nonblock
の実装は ext/socket/init.c#L518にありました。
ここでもファイルディスクリプタの操作はおこなっていなさそうです。
rb_io_set_nonblock(fptr);
に当たりをつけて、さらに潜り込みます。実際に操作をおこなっているのは rb_io_set_nonblock
のようですね。io.c#L2351
rb_io_set_nonblock
で検索をかけると、connect_nonblock
のようなaccept_nonblock
以外のノンブロッキング関連の複数メソッドがこれを利用していることが分かります。
例外について
rsock_s_accept_nonblock
に戻ると、cloexec_accept
で実際にaccept
システムコールを呼び出し、その返り値をチェックしていることが分かります。システムコールがCの世界で「エラーを数値」として扱っているのに対して、それをどのようにRubyの世界の「例外」にしているのでしょうか? おなかもすいてきたので、これは次の機会に( error.c#L1899 や eval.c#L527 の周辺を追っかければよさそう)。
まとめ
accept_nonblock
やconnect_nonblock
などのいわゆるノンブロッキング関連のメソッドは、ファイルディスクリプタを操作するためにrb_io_set_nonblock
を共通に利用しているのでした。
RubyはCのシステムコールやライブラリなどをオブジェクト指向の扱いやすいインターフェイスで提供してくれていますが、それがどのように実現しているのか知るのは面白いです。少し話がズレますが、ActiveRecordもRDBMSの違いを吸収して、汎用的なインターフェイスを提供しています。便利な素晴らしいインターフェイスをただ使うのではなく、興味を持ったところから裏側で何が行われているのか知ることで、そのライブラリやツールを自信を持って扱うことができるのではないでしょうか。