[ruby-list:49598] IO.popen に不具合?
From:
尾川敏也 <ogw@...>
Date:
2013-09-24 13:11:26 UTC
List:
ruby-list #49598
たびたびお騒がせします。尾川と申します。
ruby-2.0.0-p247 を使っているのですが、IO.popen がどうも挙動不審のよ
うに思えます。
IO.popen(cmndline, "w+") で外部コマンドの標準入出力をパイプにつなげ
た際、外部コマンドの出力のパイプへの書き込みが途中で切れてしまい、
それでも Ruby は知らんぷりして先に進む、ということが「たまに」起き
ているようです。
あれこれ試すなかで、IO.popen を使わずに Open3.pipeline_rw を使うよ
うにしてみたら、このような不具合は(今のところ)無くなりました。
こういうものなのでしょうか?それともひょっとしてバグなのでしょうか?
何となく釈然としないので、報告させて頂く次第です。
以下はもう少し詳細です。
環境は以下のとおりです。
Ruby は、32bit Windows XP 上の MS VisualStudio 2008 で自分でビルド
した ruby-2.0.0-p247 を Windows 7 (32bit) で使っています。
ビルドは win32/README.ja の説明にそって
win32\configure, nmake, nmake test, nmake install
しただけです。拡張ライブラリの類は何も入れていません。
問題が起きたのは、以下のようなスクリプトです。
#--------------------------------------------------
class String
def |(cmdline)
IO.popen(cmdline, "w+") do |pipe|
Thread.fork {
pipe.write self
pipe.close_write
}
pipe.read
end
end
end
result = "".|("myprog1 datafile.txt").|("myprog2").|("myprog3")
File.open("result.txt", "w") do |f|
f.write result
end
#--------------------------------------------------
これで、datafile.txt として相応のサイズ(例えば、1 行 400 byte 程度
で 3000 行程度、サイズにして 1MB 程度)のファイルを指定した場合に問
題が起きました。
同じ入力ファイルで同じ事を繰り返し実行してみると、最後までうまく行
くこともありますが、数回〜十数回に1回程度、myprog2 か myprog3 が、
本来受け取るはずの行数のデータを受け取れなくてエラーになります。
エラーで終了するのは外部コマンドですが、myprog2 と myprog3 のどちら
がエラーになるかは一定していません。
また、外部コマンドの出しているメッセージを見ると、読み取り損ねたデー
タのサイズはエラーが発生するたびに毎回微妙に異なります。
ただ、大体、3000 行のうちの 2800 行前後読んだところでエラーになるこ
とが多いような気配です。
という訳で、再現性の無い不審な挙動を示します。
何とかして、せめてメインテナーの方に問題の発生を再現して頂けるよう
なサンプルを用意しようとしたのですが、以下のような事が判っただけで、
よいサンプルは作れず、断念しました。
(1) 入力データが小さい(例えば 10 kB)ときには問題は起きない。
(2) 外部コマンドが単純 (cat 相当の即席の C プログラムとか) な場合に
は、例えば 20MB 程度の大きなデータを渡しても問題は起きない。
以上が問題の様子です。
それで、苦し紛れに IO.popen の替わりに Open3.pipeline_rw を使って
以下のように書き換えてみたら、問題無く動くようになりました。
IO.popen では問題が起きたデータで 40 回ほど繰り返し実行してみても問
題は起きませんでしたので、多分大丈夫なのだろうと思います。
#----------------------------------------------------------
require "open3"
class String
def |(cmdline)
Open3.pipeline_rw(cmdline) do |stdin, stdout, wait_thrs|
Thread.fork {
stdin.write self
stdin.close
}
stdout.read
end
end
end
#----------------------------------------------------------
以上のような次第で、Open3.pipeline_rw では問題無く動くのに、IO.popen
では動いたり動かなかったりというのが、どうにも釈然とせず、ひょっと
して IO.popen にバグでもあるのではないかと思った次第です。
以上、長文で失礼しました。
P.S.
もし、この先 Open3.pipeline_rw でも不具合が起きるような事があったら、
次は以下のように書き換えてしのごうかと思っています。
20 年前の 16bit の MS-DOS のパイプみたいで、スマートな Ruby らしく
なくて不細工ですが、極めて安定して動くようです(笑)。
#----------------------------------------------------------
class String
def |(cmdline)
tmp_i = "tmp_#{$$}_i_fake_pipe.txt"
tmp_o = "tmp_#{$$}_o_fake_pipe.txt"
File.open(tmp_i, "w") { |f| f.write self }
system(cmdline + " < " + tmp_i + " > " + tmp_o)
File.read(tmp_o)
ensure
File.delete tmp_i, tmp_o
end
end
#----------------------------------------------------------
--
尾川敏也 ogw@shizuokanet.ne.jp
http://www6.shizuokanet.ne.jp/ogw/