Java : processbuilder 標準出力 タイムアウト

1つ前の投稿から続けて)

Java(1.8)で外部シェルを実行するというお話、その2。

いったん作成したが、「タイムアウトとかほしいよね」とのことでタイムアウト判定を入れることになった。

単純にタイムアウトだけであれば タイムアウト付きwaitFor() が使えたと思う。
ただ、この関数は標準出力バッファが詰まった時点で、エラー扱いで処理が戻ってきた。

理想のタイムアウトの挙動は以下であった。

  • タイムアウトになるまではずーっと標準出力を受け取っていてほしい。
  • タイムアウト時点で、そこまでの標準出力を全部受け取っていて、最後に「と、ここまでやったけど、ここでタイムアウトしました」ぐらいの結果返却。


というわけで、前回の「標準出力バッファを吸い出しながら処理するコード」を元にして、タイムアウト処理を追加する形で実装。

コードは以下。

ちなみに、タイマーに内部クラスを使用しているのは好みです。
当然、System.currentTimeMillis() と if文でも同様のことが出来ますし、ここでは一つの例ということで。

  1. // cmd[] は とりあえずこんな → [0]:ユーザー用コマンド名称、[1]:処理開始日時、[2]:外部実行のシェル名、[3]~ : 引数情報(可変長)
  2. private List<String> cmdExecute(String[] cmd) throws Exception {
  3.  
  4. // タイムアウト用タイマー : 内部クラス
  5. class CmdTimer extends TimerTask {
  6.  
  7. private boolean isTimeout = false;
  8. private Process p = null;
  9.  
  10. public CmdTimer( Process p ) {
  11. this.p = p;
  12. }
  13.  
  14. public boolean getIsTimeOut() {
  15. return this.isTimeout;
  16. }
  17.  
  18. @Override
  19. public void run() {
  20. this.isTimeout = true;
  21. try {
  22. p.waitFor();
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }
  28.  
  29. Timer timer = new Timer();
  30.  
  31. // 標準出力受け取り用の配列。
  32. List<String> resultStr = new ArrayList<String>();
  33. resultStr.add("Command start time [" + cmd[1] + "]");
  34. resultStr.add("========================================");
  35. resultStr.add("");
  36.  
  37. // ■準備 : タイムアウト間隔の格納(例 : 5000ms = 5s)
  38. long timeout = 600000; //とりあえず10分
  39.  
  40. // ■準備
  41. ProcessBuilder pb = new ProcessBuilder(Arrays.copyOfRange(cmd, 2, cmd.length)); //配列は、シェル名、引数~ の順序で固定。
  42. // 標準出力と標準エラー出力を同一視
  43. pb.redirectErrorStream(true);
  44.  
  45. // ■準備2
  46. Process process = null;
  47. InputStreamReader isr = null;
  48. BufferedReader reader = null;
  49. boolean isTimeOut = false;
  50.  
  51. try {
  52.  
  53. // プロセススタート
  54. process = pb.start();
  55.  
  56. // タイマー設定
  57. CmdTimer task = new CmdTimer(process);
  58. timer.schedule((TimerTask) task, timeout);
  59.  
  60. // 標準出力受取用のストリーム取得
  61. isr = new InputStreamReader(process.getInputStream(), "UTF-8");
  62. reader = new BufferedReader(isr);
  63.  
  64. // 標準出力受取用ループ。
  65. // ストリーム内で文字列が4Kバイト?を超えるとプロセスが停止するため、定期的にストリームから抜き出しておく。
  66. // 抜き取り間隔は下のThread.sleepで指定。ここが遅いと、抜き取る前にたくさん貯まるので、値を小さくしたりすること。
  67. while (true) {
  68. try {
  69. Thread.sleep(100);
  70. } catch (InterruptedException e) {
  71. }
  72.  
  73. // プロセス終了、もしくはタイム・アウトしている場合、受取用ループはお役御免なので終了する。
  74. if ( !process.isAlive() || task.getIsTimeOut() ) {
  75. isTimeOut = task.getIsTimeOut();
  76. break;
  77. }
  78.  
  79. // 標準出力のバッファが貯まるとprocessBuilderが停止するので、抜き出す。
  80. if (reader.ready()) {
  81. resultStr.add(reader.readLine());
  82. }
  83. }
  84. } catch (Exception e) {
  85. // たとえば sh が見つからない時などはここに来る。
  86. resultStr.add("コマンド処理中にエラーが発生しました。");
  87. resultStr.add(e.getMessage());
  88.  
  89. } finally {
  90.  
  91. // タイムアウトかどうか。
  92. if ( isTimeOut ) {
  93. // タイムアウトで終わった場合 : 後続の標準出力は無いので、抜ける。
  94. resultStr.add("コマンドがタイムアウトしました。");
  95. resultStr.add("詳細はシステム担当者にお問い合わせください。");
  96. resultStr.add("現在のタイムアウト間隔 : " + timeout + "(ms)");
  97. } else {
  98. // タイムアウト以外(正常終了)
  99.  
  100. // エラー判定 : リーダーがnullという状況は異常で、上部のcatchを経由してきた状況なのでそのまま返却。
  101. if ( reader == null ) {
  102. // ここはtimer.cancel()が必要かも。
  103. return resultStr;
  104. }
  105. // 標準出力結果を取得。
  106. // ここまでループ内で細かく取得しており、最後に1回抜き取る。
  107. String tempLine = "";
  108. while (true) {
  109. tempLine = reader.readLine();
  110. if (tempLine == null) {
  111. break;
  112. } else {
  113. resultStr.add(tempLine);
  114. }
  115. }
  116.  
  117. // 結果用文言の付与 : 0なら正常終了、それ以外は異常終了ってことで。sh次第だけど、いちおう一般的。
  118. resultStr.add("");
  119. if ( process.exitValue() == 0 ) {
  120. // 0 : 正常終了
  121. resultStr.add("コマンドが正常終了しました。");
  122. } else {
  123. // !0 : 異常終了
  124. resultStr.add("コマンドが異常終了しました。");
  125. }
  126. }
  127.  
  128. // 各種close
  129. reader.close();
  130. isr.close();
  131. }
  132.  
  133. // 返却
  134. timer.cancel();
  135. return resultStr;
  136. }

コメント

このブログの人気の投稿

windows10 で nvidia のグラボのcode43現象を解決した

GTX560Ti がおかしい(code 43が出る)(2018年)→解決しました(2019)