Ruby::PTYで外部コマンドの起動終了の制御

Rubyでちょっとした処理プログラムを作ったときの話です。プログラムの構造として、Ruby以外の言語で書かれた実行バイナリが既に複数あり、それを起動したり、終了したりしながら制御をするというものでした。いわゆるプロセス制御なのですが、僕が思い違いをしていたこともあり、ちょっと苦労したのでその時の話をまとめておこうと思います。僕がいきついた答えを先に書いてしまうとRuby::PTYでした。

今回の目的に使えるのでは、とまず一番に思ったのが、Rubyで外部のコマンドを起動する最も簡単な方法である、組み込みコマンドのsystem`(str)`execなどです。これらは外部コマンドの実行結果を得ることができるのですが、外部コマンドの終了まで制御が帰ってこない、execに至っては外部コマンドの起動が成功してしまうと起動したRubyスクリプトに制御が戻ってこないというありさまです。

そこでこれらの組み込みコマンドとRuby並行プログラミング機能である、スレッドやプロセスを使って何とかできないかと思いました。組み込みコマンドをRubyのスレッドやプロセスで包んで、それごと終了してしまえば任意のタイミングで外部コマンドを巻き添えに終了してくれるのではないかという目論見です。
しかしうまくいきません。スレッドは外部コマンドを起動した時点で止まってしまい、一方のプロセスは確かに任意のタイミングで終了できるのですが、外部コマンドを巻き添えにはしてくれませんでした。

仕方がないので、色々と調べた結果、擬似端末PTYライブラリに行き着きました。これを使って外部プロセスを起動すると、そのpid (Process ID)を得ることができるので、Process::kill(:KILL, pid)とすれば外部プロセスを任意のタイミングで終了することができました。

以下、途中の過程がうまくいかなかった原因を調べてみた結果です。

Rubyのスレッドで外部コマンド起動時に止まってしまったのは、どうやら実行環境がWindowsだったのが関係しているようです。詳しくは『WindowsのRubyでThread内でプロセス作成すると止まっちゃう』あたりが参考になります。

Rubyのプロセスで外部コマンドを巻き込んで終了してくれないのは、外部コマンドが孫プロセスとして起動されるために、子プロセスが終了したところで、孫プロセスは生き続けているようです。結局のところ外部コマンドのpidがわからないことには埒が明かないということのようです。

また、よくよく調べてみると今回の目的を達せそうな組み込みコマンドspawnというのがあるのですが、残念ながらこれはRuby 1.9からの機能のようで僕の環境では使えませんでした。

October 17, 2009 18:13 fenrir が投稿 : 固定リンク | | このエントリーを含むはてなブックマーク

コメント

なるほどちょっとしたとんちですな。spawn以外だとそのくらいしか方法がなさそう。
純RubyのコードなんですがdRubyと一緒に使ってたときにプロセスとの合わせ技になっちゃったことがあります。スレッドじゃうまく動かなくて。

Posted by: あおき : October 18, 2009 01:05 AM

>あおきさん
まつもとさん曰く『すべてはソースコードに書いてある』でしたっけ。現象の分析だけで、ソース読んでません、すいません(笑)

Posted by: fenrir : October 18, 2009 07:42 PM

コメントする