«前の日記(2006年01月23日) 最新 次の日記(2006年01月26日)» 編集

ema log


2006年01月24日 [長年日記]

_ [Programming][Ruby] ペアプロ風味 ー タイムアウトを設定してコマンドを実行するスクリプト

今日、Sが実験のプログラムを走らせるのにタイムアウトが欲しいと言い出す。問題によって実行時間が爆発するため必要とのこと(バッチで走らせて翌日以降に結果をとる)。 二人ともRubyが使えるので一緒に実装してみる。後から考えるとペアプロに近い感じ。 さて、Rubyで外部プログラムを実行するメジャーな方法をまとめると以下の通りです。なお、最終的には open を使いました。

機能systemexecopen``(バッククオート)spawn
制御が戻ってくる×
Ruby 1.8で使える×
pid が利用できる×××
返り値bool値なしIO出力された文字列pid

面白かったので、ちょっと再現風。以下、私を e 、相方を S とします。

S:コマンドを実行するときにタイムアウトを設定できる Linux のコマンド無いかな?
e:しらないなぁ
S:探してみたけどなさそうなんだ。Ruby でスクリプトを作ってみてんけど上手くいかへんねん。
e:どれどれ、面白そうw
S:Timeout とかいうクラスがあったから使ってみてんけど・・・
e:うーん、ちょっとコード書いても良いかな?

といった感じで始まる。まず、私が emacs を起動して s のコードから引数解析部をそのまま拝借して思いついたままにコードを書いた。仕組みはきわめて単純だ。

  • タイムアウト待ちスレッドを作り、タイムアウト時間分 sleep する。
  • メインスレッドから system でコマンドを実行。
  • コマンドの実行が無事終われば exit 0 で終了。Ruby のスレッドはソフトウェアスレッドなので問題なく終了できる。
  • タイムアウト待ちスレッドが sleep から起きれば exit 1 で終了。きっと、system で起動したコマンドも終了してくれるはず。

S が動作確認用の puts をしこんで動作を確認する。

$ ./timeout.rb 2 sleep 1
command was success!!
$ ./timeout.rb 2 sleep 3
Timeout!!

どうやら動作したようだ。これにて一件落着・・・のはずだったのだが。S が Ruby でバッチを書いて走らせてみると・・・

S:なんか Linux が処理落ちする
e:あらら??とりあえず、ps やら top で確認してみたら?
S:なんか一杯起動してる
e:ほんまやw
S:CTRL+C 押しても一個ずつしかおわらへんねん・・・
e:どうしよっか?シェルスクリプトかける?俺無理w

というわけで、Google 先生にお伺いを立てて、ps の結果を加工して一気に kill するスクリプトをこしらえる。満足感が漂うw

S:どうも system で起動したプロセスがスクリプトが終わった後も生きてるんじゃないか?
e:そんな感じっぽいなぁ・・・じゃあタイムアウト時に kill できないかな?

どうやら、system で起動したプロセスは、インタプリタが終了しても独立して動き続けるようです。リファレンスマニュアルを調べるとよさげな関数が。

S:system は成功したかどうかしか返さないっぽいで
e:ほんまやな。じゃあ他のやつ無いかな?(spawn を見つけて)これとかいけるんちゃう?
S:pid かえってくるんや。ならいけそうだなぁ
e:(Process モジュールのマニュアル見ながら)Process.kill でシグナル送ると良さそうやで

というわけで、スクリプトを書き換える。ところが・・・「undefined method `spawn' for main:Object (NoMethodError)」とエラーが出た。マニュアルを見返すと太字で「ruby 1.9 feature」とある(笑。そりゃ駄目だ。仕方がないので、色々組み込み関数のページをあさってみるが、exec, ``, open のいずれも(直接には)pid を返してはくれません。fork だの syscall だの迷走しまくります。

e:pid さえわかればいいねんし、ps コマンドの結果を加工して kill できないかな?
S:まぁ、できるとは思うけど・・・
e:あ、出力結果がコンソールの幅に切られるなぁ
S:オプションで何とかできるだろうけど・・・
e:やっぱりスマートじゃないよねぇ
S:うん
e:じゃあ boost でw
S:いやいや
e:うーん、Perl や Pythonw
S:いやいやいや。それは・・・

一人だととっくの昔にあきらめて他の方法に逃げている私がいました。そこは相方がカバーしてくれました。 RAA やらリファレンスマニュアルを手分けして横道にそれつつ漁り続けます。

e:お。IO#pid ってあるで。これいけるんちゃう?
S:おお?

どうやらゴールにたどり着いたようです。open は popen がよりふさわしいようですが、私は使ったことがなかったのでwさて、動作させてみます。

$ ./timeout.rb 2 sleep 1
command was success!!
$ ./timeout.rb 2 sleep 20
Timeout!!
$ ps aux | grep sleep
$

どうやら、上手くプロセスを殺せたようです!!後は最後の仕上げに入ります。このままだとタイムアウトした場合に、read から一切読み出されず、出力がでないので puts io.read を書き換えます。

e:white line = io.gets っと
S:each 使おうよ
e:あ、そんなん使えたんだ。じゃあ io.each do |line| っと
S:できたね
e:できたね

最終的に、動作確認後にデバッグコードを除去しました。本来なら Log4r や Logger を使ってログ出力を使うと良いかもと思いつつも小品だしいっかと思いつつ心の中で省略。さて、長々と記憶を頼りに雰囲気だけでも再現してみようと思ったのですが、どないなものでしょう?個人的には一人で書くよりも確実に品質・効率共に向上したように感じました。(色々な意味で)さぼれないのが良い感じです。

オチではないけれど

e:うーん、念のため JM 調べてみるかー
S:man してみたけど無かったよ?
e:あれ? timeout コマンドがある(笑
S:えええええ

正直、ちょっと焦りました(笑。問題の timeout コマンド の正体は Netatalk という Appletalk 互換を実現するソフトウェアに含まれているコマンドのようでした。何はともあれ無事完成しました!

本日のツッコミ(全2件) [ツッコミを入れる]
_ iwaiwa (2006年01月25日 00:54)

似たようなのをJavaで作るかもw

_ ema (2006年01月25日 05:10)

じゃあ C# と C++ と Perl と Python あたりで(ry