Java : processbuilder 標準出力 タイムアウト
(1つ前の投稿から続けて)
Java(1.8)で外部シェルを実行するというお話、その2。
いったん作成したが、「タイムアウトとかほしいよね」とのことでタイムアウト判定を入れることになった。
単純にタイムアウトだけであれば タイムアウト付きwaitFor() が使えたと思う。
ただ、この関数は標準出力バッファが詰まった時点で、エラー扱いで処理が戻ってきた。
理想のタイムアウトの挙動は以下であった。
というわけで、前回の「標準出力バッファを吸い出しながら処理するコード」を元にして、タイムアウト処理を追加する形で実装。
コードは以下。
ちなみに、タイマーに内部クラスを使用しているのは好みです。
当然、System.currentTimeMillis() と if文でも同様のことが出来ますし、ここでは一つの例ということで。
Java(1.8)で外部シェルを実行するというお話、その2。
いったん作成したが、「タイムアウトとかほしいよね」とのことでタイムアウト判定を入れることになった。
単純にタイムアウトだけであれば タイムアウト付きwaitFor() が使えたと思う。
ただ、この関数は標準出力バッファが詰まった時点で、エラー扱いで処理が戻ってきた。
理想のタイムアウトの挙動は以下であった。
- タイムアウトになるまではずーっと標準出力を受け取っていてほしい。
- タイムアウト時点で、そこまでの標準出力を全部受け取っていて、最後に「と、ここまでやったけど、ここでタイムアウトしました」ぐらいの結果返却。
というわけで、前回の「標準出力バッファを吸い出しながら処理するコード」を元にして、タイムアウト処理を追加する形で実装。
コードは以下。
ちなみに、タイマーに内部クラスを使用しているのは好みです。
当然、System.currentTimeMillis() と if文でも同様のことが出来ますし、ここでは一つの例ということで。
// cmd[] は とりあえずこんな → [0]:ユーザー用コマンド名称、[1]:処理開始日時、[2]:外部実行のシェル名、[3]~ : 引数情報(可変長)
private List<String> cmdExecute(String[] cmd) throws Exception {
// タイムアウト用タイマー : 内部クラス
class CmdTimer extends TimerTask {
private boolean isTimeout = false;
private Process p = null;
public CmdTimer( Process p ) {
this.p = p;
}
public boolean getIsTimeOut() {
return this.isTimeout;
}
@Override
public void run() {
this.isTimeout = true;
try {
p.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Timer timer = new Timer();
// 標準出力受け取り用の配列。
List<String> resultStr = new ArrayList<String>();
resultStr.add("Command start time [" + cmd[1] + "]");
resultStr.add("========================================");
resultStr.add("");
// ■準備 : タイムアウト間隔の格納(例 : 5000ms = 5s)
long timeout = 600000; //とりあえず10分
// ■準備
ProcessBuilder pb = new ProcessBuilder(Arrays.copyOfRange(cmd, 2, cmd.length)); //配列は、シェル名、引数~ の順序で固定。
// 標準出力と標準エラー出力を同一視
pb.redirectErrorStream(true);
// ■準備2
Process process = null;
InputStreamReader isr = null;
BufferedReader reader = null;
boolean isTimeOut = false;
try {
// プロセススタート
process = pb.start();
// タイマー設定
CmdTimer task = new CmdTimer(process);
timer.schedule((TimerTask) task, timeout);
// 標準出力受取用のストリーム取得
isr = new InputStreamReader(process.getInputStream(), "UTF-8");
reader = new BufferedReader(isr);
// 標準出力受取用ループ。
// ストリーム内で文字列が4Kバイト?を超えるとプロセスが停止するため、定期的にストリームから抜き出しておく。
// 抜き取り間隔は下のThread.sleepで指定。ここが遅いと、抜き取る前にたくさん貯まるので、値を小さくしたりすること。
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
// プロセス終了、もしくはタイム・アウトしている場合、受取用ループはお役御免なので終了する。
if ( !process.isAlive() || task.getIsTimeOut() ) {
isTimeOut = task.getIsTimeOut();
break;
}
// 標準出力のバッファが貯まるとprocessBuilderが停止するので、抜き出す。
if (reader.ready()) {
resultStr.add(reader.readLine());
}
}
} catch (Exception e) {
// たとえば sh が見つからない時などはここに来る。
resultStr.add("コマンド処理中にエラーが発生しました。");
resultStr.add(e.getMessage());
} finally {
// タイムアウトかどうか。
if ( isTimeOut ) {
// タイムアウトで終わった場合 : 後続の標準出力は無いので、抜ける。
resultStr.add("コマンドがタイムアウトしました。");
resultStr.add("詳細はシステム担当者にお問い合わせください。");
resultStr.add("現在のタイムアウト間隔 : " + timeout + "(ms)");
} else {
// タイムアウト以外(正常終了)
// エラー判定 : リーダーがnullという状況は異常で、上部のcatchを経由してきた状況なのでそのまま返却。
if ( reader == null ) {
// ここはtimer.cancel()が必要かも。
return resultStr;
}
// 標準出力結果を取得。
// ここまでループ内で細かく取得しており、最後に1回抜き取る。
String tempLine = "";
while (true) {
tempLine = reader.readLine();
if (tempLine == null) {
break;
} else {
resultStr.add(tempLine);
}
}
// 結果用文言の付与 : 0なら正常終了、それ以外は異常終了ってことで。sh次第だけど、いちおう一般的。
resultStr.add("");
if ( process.exitValue() == 0 ) {
// 0 : 正常終了
resultStr.add("コマンドが正常終了しました。");
} else {
// !0 : 異常終了
resultStr.add("コマンドが異常終了しました。");
}
}
// 各種close
reader.close();
isr.close();
}
// 返却
timer.cancel();
return resultStr;
}
コメント
コメントを投稿