libtrusterdのldd機能の負荷テストが失敗したことで子プロセスの扱いが少し理解が進んだ件
おはようございます。先週の実家生活は、初日から次男がロタにかかるなど、どうなることかと心配されましたが、無事乗り切る事が出来、自宅に帰って4月からの新しいクラスで息子たちは保育園生活を始めました。@kjunichiです。
背景
libtrusterdでldd機能の負荷テストをしたらNG。
単発だと問題ない。だからこそ、このldd機能をマージしたのだったものの、h2loadで負荷テストを行うと、途中から動かなくなる。
手元のOSXではh2loadで、リクエストを増やしていったところ、260リクエスト前後でNGとなった。
何度か、リクエスト数を調整している際にpsコマンドで、
(bash)
が大量に表示され、手がかりをつかむことが出来た
分かった事
黙ってForkすると
forkすると、親に子プロセスの終了時にシグナルが通知される。waitpidで、子プロセスのプロセスIDを指定すると、この通知を処理して、子プロセスが完全に終了する。
子プロセスが完全に終了することが出来ず、親プロセスが終了するまでいつまでも残る。
今回はサーバーの処理のため、親プロセスはなかなか終了しない、そうなると、リクエストを受け付けるたびに子プロセスが増えていき、OSの許容するプロセス数の上限に達して、エラーとなる。
暫定対応
waitpidの他に、
親に子プロセスの終了時にシグナルが通知
というこの通知を、あらかじめ受け取らない設定が実はある。
mruby-signalモジュールを使い、
Signal.trap(:SIGCLD, :SIG_IGN)
のように、子プロセスからのシグナルを無視するようにした。
そもろもmrubyで`と `で囲んでrubyの様に外部コマンドの実行結果を受け取れるのは
組み込み系でも利用可能なmrubyは最小構成ではforkなどないので、外部コマンドをバッククォートで囲んで実行できるという、この機能自体も何らかのmrbgemsになっているのではないかとおもい、mruby-process等を始めた疑ったが、
Big Sky :: mruby で xquote が使える様になった。
こちらの記事を書かれた方のお蔭でした。ありがとうございます。
実装箇所を探す
xquoteの記事にも書かれていますが、仕組みをmrubyに組み込んだだけで、これを実装している箇所を調べる必要あった。
ptで以下のようなコードを検索して、定義しているモジュールを探した。
mrb_define_class_method(mrb, mrb->kernel_module, "`"
が、見つからず、「`」で検索して、ようやく見つけたのが以下、
def self.`(cmd)
IO.popen(cmd) { |io| io.read }
end
mruby-ioのkernel.rbであることをつき止めた。
mruby-ioにissueを挙げる
mrubyの世界での標準は英語のようなので、英作文を頑張り、少なくとも作者の方には通じて、早速対応頂けた。
英文を読めばわかるが、自分は相当勘違いして、的外れな指摘ばかりしていたが、丁寧に回答して頂き、Fixまで行っていただけた。ありがとうございました。
https://github.com/iij/mruby-io/issues/37
まとめ
そもそも、ベンチマークなどあまり気にしない行き当たりばったりの超現場主義なんですが、このところ、libtrusterdのメモリ使用量が増加していく問題を調査しており、この一環で、h2loadの向き先をたまたまldd機能に指定して今回の発見やその後の対応に繋がりました。
また、
「forkすると、親に子プロセスの終了時にシグナルが通知される。」に関しても、そのうち記事にまとめたいのだが、kqueueでtrusterdの設定ファイルの監視を行い、自動リロードする機能をlibtrusterdに実装している際に、目にしたドキュメントに書かれていたことで、たまたまこれが、今回の問題と結びつけることが出来た。
関連記事
- なぜ、waitpidをループ処理させるのか
- mrubyのFile.openをちょっと調べたメモ
- mruby-ioでclose_writeを実装したが、不要だった件
- WindowsでDLLを作ろうとしてmrb_context_runの歴史を調べた