ひでっぷの技術メモ

はてなダイアリーから移行しました

Runtime.exec()にてprocess.getInputStream().close()だけではだめ?

Javaで外部スレッドを起動する時はProcessの標準入力、標準出力とエラー出力のstreamをclose()してあげないと「Too many open files」というIOExceptionが出てしまう。
また、標準出力やエラー出力が多くてstreamのバッファ容量を超えるとProcessがデッドロックしてしまうため出力が多い場合は別スレッドにして読み込んでやる必要がある。
今回Runtime.exec()で動かしているプログラムはArmadillo-9のGPIO出力プログラム。
ArmadilloのソフトウェアマニュアルについているサンプルをもとにCで作成した。
とっても簡単なもので引数で渡された値を特定ポートに出力するだけ。
標準出力にはポートの出力の値をprintfで出しているだけ。
こんだけ少ない値なら読み込まなくてもいいかと思ってただclose()するだけにした。

 
/**
* 出力する
* @param high boolean :0-7bit目の値 high:true low:false
*/
public void output(boolean
high) {

int parameter = makeParameter(high);//booleanを数値に変換する

String parameters = {
"/home/hogehoge/gpio_output"((GPIOの出力プログラム)), Integer.toString(parameter)};

Process process = null;
try {
process = Runtime.getRuntime().exec(parameters);
try {
process.waitFor();
}
catch (InterruptedException ex) {
process.destroy();
}
}
catch (IOException ex) {
ex.printStackTrace();
}
finally {
if (process != null) {
try {
if(process.getOutputStream() != null){
process.getOutputStream().close();
}
if(process.getInputStream() != null){
process.getInputStream().close();
}
if(process.getErrorStream() != null){
process.getErrorStream().close();
}
}
catch (IOException ex1) {
ex1.printStackTrace();
}
}
}
}

ところが、これでしばらく動かしていると1つのプロセスのCPU負荷が突然80%近くになり、最終的にSignal11で落ちてしまう。
試しに100回連続でoutputメソッドを呼び50ms待ってまた100回連続でoutputメソッドを呼ぶというサンプルプログラムを作ったら1時間ほどでCPU負荷が上がりSignal11で止まってしまた。
もしかしてきちんと出力を読み込んでからclose()しないとだめ??
確かにネットで色々なサイトを調べてみたがすべてprocess.getInputStream()とprocess.getErrorStream()のデータを読み込んでからclose()している。
2,3バイト程度の出力データだし、closeすれば問題なさそうなんだけど・・・??
少なくともphoneMEではだめみたいです。
ということで以下のように別スレッドにして読み込むようにしました。


/**
* 出力する
* @param high boolean
*/
public void output(boolean high) {
ReadInputStreamThread thread = null;
ReadInputStreamThread errorThread = null;

int parameter = makeParameter(high);
String parameters = {
"/home/hogehoge/gpio_output", Integer.toString(parameter)};

Process process = null;
try {
process = Runtime.getRuntime().exec(parameters);
//標準出力読込スレッドの生成と起動
thread = new ReadInputStreamThread(process.getInputStream());
thread.start();
//エラー出力読込スレッドの生成と起動
errorThread = new ReadInputStreamThread(process.getErrorStream());
errorThread.start();

try {
process.waitFor();
}
catch (InterruptedException ex) {
process.destroy();
}
}
catch (IOException ex) {
ex.printStackTrace();
}
finally {
if (thread != null) {
try {
//標準出力読込スレッドの停止待ち
thread.join();
}
catch (InterruptedException ex2) {}
}
if (errorThread != null) {
try {
//エラー出力読込スレッドの停止待ち
errorThread.join();
}
catch (InterruptedException ex3) {}
}

if (process != null) {
try {
if(process.getOutputStream() != null){
process.getOutputStream().close();
}
}
catch (IOException ex1) {
ex1.printStackTrace();
}
}
}
}

/**
* InputStreamを読んで破棄するスレッド
*/
private class ReadInputStreamThread
extends Thread {
/**読み込むInputStream*/
private InputStream in;

/**
* コンストラクタ
*/
public ReadInputStreamThread(InputStream in) {
this.in = in;
}

public void run() {
try {
while (in.read() >= 0) {
//ただひたすら読み込むだけ
}
}
catch (IOException ex) {
ex.printStackTrace();
}
finally {
if (in != null) {
try {
in.close();//inputStreamのclose
}
catch (IOException ex1) {}
}
}
}
}


改造してから上のサンプルプログラムを再び走らせましたが、3時間たった今でも順調に動いています。
これで解決したかな・・・?
時間がある時にJavaSEでも同じ現象が起きるか試してみたいと思います。