- PR -

Perl の標準エラー出力を Process.getErrorStream() で読むとブロックしてしまう

投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-09-06 22:27
unibon です。こんにちわ。

Java から exec したプロセスが出力する標準エラー出力(stderr)を、
Java の側で Process.getErrorStream() で読むと、
ブロックしてしまって Java のプログラムが終わらなくて悩んでいます。

例としては、ActiveState の ActivePerl(perl.exe) の、
構文チェックと警告のオプション(-c と -w)を付けて呼び出した場合、
つぎのようになります。
(1) わざと誤った Perl のソースコードを渡すと、
  Perl が構文チェックでエラーを出力しますが、
  Java の側でそれを読みこめて特に問題なありません。
(2) 正しい Perl のソースコードを渡すと、
  構文チェックで Perl がエラーをなにも返さないのですが、
  そのようなときに Java の側で read(BufferedReader.readLine()) すると
  そこでブロックしてしまいます。

再現用のソースコードはつぎのとおりです。
簡単な Perl のソースコードをハードコーディングしてあり、
わざと間違ったコード(下記のように for を faor と間違えている)だと、
エラーの文字列がちゃんと取得できてブロックされることもないのですが、
faor を for に直すと、エラーの文字列が全然なくなってしまうためなのか、
ブロックされていつまでたっても終わりません。
#原理的には別に文字列がないからといってブロックされるわけではないはずですが。

コード:
import java.io.*;

public class RedirTester {

    public static void main(String[] args) throws IOException {
        Runtime runtime = Runtime.getRuntime();
        String[] cmdArray = new String[] { "perl.exe", "-c", "-w" };
        Process process = runtime.exec(cmdArray, null, null);

        OutputStream outputStream = process.getOutputStream(); // stdin of perl.exe
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream), 0x1000);
        writer.write("faor ($i = 0; $i < 10; $i++)\n" // Perl の文法をわざと間違えている。
        + "{\n"
        + "    print $i;\n"
        + "}\n");
        writer.close();

        InputStream inputStream = process.getErrorStream(); // stderr of perl.exe
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream), 0x1000);
        while (true) {
            String line = reader.readLine(); // ここでブロックしてしまう。
            if (line == null) {
                break;
            }
            System.out.println(line);
        }
        reader.close();
    }
}


なお、環境は Windows 98 + JDK 1.4.2_01 + ActivePerl 5.6.1 です。

背景としては、EPIC の Eclipse 用の Perl プラグイン
http://e-p-i-c.sourceforge.net/
を Windows 98 で動かそうとしたところ構文チェックの機能がうまく動かず、
ソースコードを見てたらどうもこのあたりが絡んでいそうだなと思って、
上記のようなサンプルプログラムで試すと似たような挙動になったためです。
なお、このプラグインは以前に Windows XP で動かすとちゃんと動きました。
#上記の Java のサンプルプログラムは、
#私は都合によりすぐには Windows XP では試せないのですが。
Kissinger
ぬし
会議室デビュー日: 2002/04/30
投稿数: 428
お住まい・勤務地: 愛知県
投稿日時: 2003-09-07 16:52
unibonさん、こんにちは。

なんか、execしたサブプロセスと、perlの受け渡しと、エラー入力の3者の
どれかとどれかが原因でデッドロックしてるような気がします。

試しにというのは良くないかも知れませんが、エラーの入力を別 Threadで
やってみたらどうでしょう。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-09-07 23:09
unibon です。こんにちわ。

引用:

Kissingerさんの書き込み (2003-09-07 16:52) より:
なんか、execしたサブプロセスと、perlの受け渡しと、エラー入力の3者の
どれかとどれかが原因でデッドロックしてるような気がします。


ありがとうございます。
デッドロックということは2つ以上の個所で待ちが発生しているということでしょうかね。

引用:

Kissingerさんの書き込み (2003-09-07 16:52) より:
試しにというのは良くないかも知れませんが、エラーの入力を別 Threadで
やってみたらどうでしょう。


私のアプリケーションの中では、
ブロックする(される?)個所は readLine の1個所だけですので、
単に Perl の標準エラー出力を入力とする Java のコード部分だけを別スレッドで動かしても、
別スレッドにしないのと違いがないと思います。
デッドロックに関与する2つ(以上)の登場人物が特定できないと、
難しそうですね。
もしかしたら、Perl の標準入力に Java から出力する段階や、
あるいはもっと以前の exec する段階で、
すでに別スレッドを使う必要があるのかも、と思えてきました。

しかし、そもそも、単にプロセスとパイプでやりとりしたいだけなのに、
別スレッドにする必要があるのかが良く分かりません。

[ メッセージ編集済み 編集者: unibon 編集日時 2003-09-07 23:13 ]
うのきち
ベテラン
会議室デビュー日: 2003/02/17
投稿数: 55
投稿日時: 2003-09-08 13:17
このperlプログラムは、正常に実行されると、標準出力に書き出しを行いますよね。
Java側で標準出力も読み出してあげないと、つまっちゃって止まってしまうのでは?
未記入
大ベテラン
会議室デビュー日: 2003/06/28
投稿数: 219
投稿日時: 2003-09-08 13:30
かなり外している可能性がありますが、
perlプログラムの最後に
__END__
がないとperlプロセスが終了しないために止まるのではないでしょうか?
佐々木
大ベテラン
会議室デビュー日: 2003/03/30
投稿数: 121
投稿日時: 2003-09-09 02:10
引用:

うのきちさんの書き込み (2003-09-08 13:17) より:
このperlプログラムは、正常に実行されると、標準出力に書き出しを行いますよね。
Java側で標準出力も読み出してあげないと、つまっちゃって止まってしまうのでは?



おそらくそうです。
こんなのがありますね。
http://www.gimlay.org/~javafaq/S103.html#S103-12
http://developer.java.sun.com/developer/bugParade/bugs/4062587.html
子プロセスのSTDOUT,STDERRをそれぞれ別のスレッドから吸い出してやるしかないようです。

手元のWindows2000, J2SDK1.4.1, ActivePerl5.8だと再現しないので、95系特有の問題なのかもしれません。
佐々木
大ベテラン
会議室デビュー日: 2003/03/30
投稿数: 121
投稿日時: 2003-09-09 02:13
引用:

Ken-Labさんの書き込み (2003-09-08 13:30) より:
かなり外している可能性がありますが、
perlプログラムの最後に
__END__
がないとperlプロセスが終了しないために止まるのではないでしょうか?


__END__がなくてもperlはきちんと停止しますよ。

(オマケのPerlクイズ)
__END__と__DATA__の違いを述べよ。(5点)

[ メッセージ編集済み 編集者: サ 編集日時 2003-09-09 02:14 ]
未記入
大ベテラン
会議室デビュー日: 2003/06/28
投稿数: 219
投稿日時: 2003-09-09 16:15
Ken-Labです。

> 子プロセスのSTDOUT,STDERRをそれぞれ別のスレッドから吸い出してやるしかないようです。

確かにこれが図星のような気がします。
では、なぜENDリテラルの有無を問題にしたかと言いますと、サンプルを手で
perl -c -w
for ($i=0; $i < 10;$i++){
print $i;
}
と打った場合このままになり、__END__を打ってから -Syntax OK が戻り停止します。
このサンプルを誤って入力した場合は } を入力した直後
Syntax error at - line 1, near "0;"
Syntax error at - line 1, near "++)"
- had compilation errors.
となり即停止します。普通ストリームをcloseした段階でperlが実行されるはずですが、
それが終端であると判断されなかった場合、perlから応答が得られず
BufferedReader#readLineが入力待ちになるのでは??という推測をたてたためです。
引用:

サさんの書き込み (2003-09-09 02:13) より:
(オマケのPerlクイズ)
__END__と__DATA__の違いを述べよ。(5点)


__DATA__は、スクリプトの実行部分の終わりであることを意味する以外に、DATAファイル
ハンドルをカレントパッケージの名前空間にOPENするのではないか、と思います。
具体的には、__END__も__DATA__も、このリテラルに続いてデータを置いた場合、
DATA を通じて取得できますが、requireされた側に置いた場合は__DATA__を使わないと
取得できなかったはずです。(ここでperl話を広げてもなんですので、このへんで。

[ メッセージ編集済み 編集者: Ken-Lab 編集日時 2003-09-09 16:23 ]

[ メッセージ編集済み 編集者: Ken-Lab 編集日時 2003-09-09 17:03 ]

スキルアップ/キャリアアップ(JOB@IT)