2011/02/26

Ruby::ゾンビプロセスを量産するopenメソッドの使い方 - IO::popenと%x{}の違い

DTIサーバのディスク容量監視にyadaemon.rbを使ったRubyスクリプトを走らせています。

ディスク容量を把握するのは、ちょっと面倒なので内部では手を抜いてopen("|df")を実行しています。

しばらく走らせてみたらdefunctプロセス(いわゆるゾンビプロセス)が大量発生したので、その原因を考えてみました。

問題のあったコード

"df"プロセスが終了していなかったのは次の部分です。

問題のあるコード部分抜粋

module YaWatchDisks
  def yield_df_perms
    open("|df ").each_line do |line|
      #       "Filesystem"  "1K-blocks"    "Used"    "Available" "Use%" "Mounted on"
      # perms: ["/dev/md0", "484041584", "453132936", "6514352", "99%", "/"]
      perms = line.split(/\s+/)
      yield perms if perms.length == 6 and perms[1].to_i > 0 and perms[2].to_i > 0 and perms[3].to_i > 0
    end
  end
...

考えてみれば、each_lineメソッドを実行する主体はeach_lineメソッドのブロックを終了してからcloseメソッドを呼び出すなんていう責任はないわけです。

内部ではopen("|df")の部分がGCの対象になるまで常に滞留していたはずです。

でも実際にはGCの対象にもならず、元プロセスは常駐していますから、プロセスが大量に残っていたという事になります。

問題の修正

変に省略せずに、ブロックをつけて呼び出してあげれば、何の問題もなくcloseされるようになります。

修正したコード部分抜粋

module YaWatchDisks
  def yield_df_perms
    open("|df ") do |f|
      f.each_line do |line|
        #       "Filesystem"  "1K-blocks"    "Used"    "Available" "Use%" "Mounted on"
        # perms: ["/dev/md0", "484041584", "453132936", "6514352", "99%", "/"]
        perms = line.split(/\s+/)
        yield perms if perms.length == 6 and perms[1].to_i > 0 and perms[2].to_i > 0 and perms[3].to_i > 0
      end
    end
  end
...

しばらくしてからmuninで確認すると、プロセス数が純増していたグラフからzombie分が消えていて解決したことが確認できました。

ruby-forum.comで見つけた理由らしきもの

Googleでドキュメントを検索していたらhttp://www.ruby-forum.com/topic/62435で、外部プログラムの結果を得るなら「バッククォートや%x{}構文を使ったら?」という記述がありました。

これを、試してみると古いコードでも確かにゾンビプロセスは発生しなくなり、問題なく動くようになりました。

0 件のコメント: