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; }
コメント
コメントを投稿