Ruby と FD_SETSIZE 問題
FD_SETSIZE 問題とは、1 つのプロセスで大量のファイルを同時に open するなどしてファイルデスクリプタの値が FD_SETSIZE *1の値を超える(fd >= FD_SETSIZE になる)と標準の fd_set 型で取り扱えなくなり、select(2) が正しく実行できなくなる、という問題。有名でわりと古典的な問題だが、今でもいろんなところに埋まっている。
たとえば、同時に大量のネットワークコネクションを取り扱いたい大規模サーバではこれが問題になる。実際にこれを踏むと、範囲外アクセスを起こして Segmentation fault で落ちることが多い。
なお、select(2) そのものの実装は(現代的な OS のカーネルであれば) FD_SETSIZE には依存しておらず、呼び出し側(ユーザランド)の問題である。
また、一般的な解決方法として、子プロセスを作ってそちらにコネクションを渡してしまい、1プロセスあたりのデスクリプタ数が FD_SETSIZE を超えないように調整するという方法がある。
Ruby 1.8 系の場合
- Ruby 本体
- 問題あり。大量のファイルやソケットを open すると Segmentation fault で死ぬ。
- スレッド構造体 rb_thread に fd_set が埋まっており、これを改造すると ABI が変わるため手が出せない。
- なお、select(2) は Kernel#select 以外に色々なところで呼び出されているので(Thread の待合わせなど)、「たくさんファイルを open しても Kernel#select を使わなければ大丈夫」という訳ではない。
- 拡張ライブラリ
- Ruby/EventMachine を使えば、FD_SETSIZE を超える数のネットワーク接続を同時に取り扱える。これは、Ruby 本体を介さずにデスクリプタを操作しているため。
- が、多数のソケットを開いている状態でうっかり Kernel#open などで普通にファイルを開くと、IO#fileno >= FD_SETSIZE なオブジェクトが生成されて死ぬ。
- 実用的とは言いがたい。
- Ruby/EventMachine を使えば、FD_SETSIZE を超える数のネットワーク接続を同時に取り扱える。これは、Ruby 本体を介さずにデスクリプタを操作しているため。
Ruby 1.9 系の場合
- Ruby 本体
- 問題なし。(予定)
- 1.9.1 の時点でバグ入りだが、次のリリースで既知のバグは修正される見込み。r23109
- fd_set 型の代わりに rb_fdset_t という独自の型が導入された。これを使っている限りは FD_SETSIZE 問題は起こらないはず。
- 問題なし。(予定)
- 拡張ライブラリ
- rb_thread_select() が API として色々な拡張ライブラリから呼ばれており、これが問題を引き起こしていることが多いように見える。(rb_thread_select() の引き数は fd_set 型へのポインタ)
- 代わりに rb_thread_fd_select() を使うべし。(こちらの引き数は rb_fd_set 型へのポインタ)
- 付属の readline.so も rb_thread_select() を使っているため、問題あり。
- けっきょく、使用する拡張ライブラリのソースを全て確認するしかない。
- rb_thread_select() が API として色々な拡張ライブラリから呼ばれており、これが問題を引き起こしていることが多いように見える。(rb_thread_select() の引き数は fd_set 型へのポインタ)