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