Как закрыть std-потоки из java.lang. Процесс подходит? - PullRequest
4 голосов
/ 15 августа 2011

Этот вопрос касается java.lang.Process и его обработки stdin, stdout и stderr.

В нашем проекте есть класс, который является расширением org.apache.commons.io.IOUtils.Там у нас есть тихий новый метод для закрытия std-потоков Process-Object, соответствующий?Или это не подходит?

/**
 * Method closes all underlying streams from the given Process object.
 * If Exit-Code is not equal to 0 then Process will be destroyed after 
 * closing the streams.
 *
 * It is guaranteed that everything possible is done to release resources
 * even when Throwables are thrown in between.
 *
 * In case of occurances of multiple Throwables then the first occured
 * Throwable will be thrown as Error, RuntimeException or (masked) IOException.
 *
 * The method is null-safe.
 */
public static void close(@Nullable Process process) throws IOException {
    if(process == null) {
      return;
    }

    Throwable t = null;

    try {
      close(process.getOutputStream());
    }
    catch(Throwable e) {
      t = e;
    }

    try{
      close(process.getInputStream());
    }
    catch(Throwable e) {
      t = (t == null) ? e : t;
    }

    try{
      close(process.getErrorStream());
    }
    catch (Throwable e) {
      t = (t == null) ? e : t;
    }

    try{
      try {
        if(process.waitFor() != 0){
          process.destroy();
        }
      }
      catch(InterruptedException e) {
        t = (t == null) ? e : t;
        process.destroy();
      }
    }
    catch (Throwable e) {
      t = (t == null) ? e : t;
    }

    if(t != null) {
      if(t instanceof Error) {
        throw (Error) t;
      }

      if(t instanceof RuntimeException) {
        throw (RuntimeException) t;
      }

      throw t instanceof IOException ? (IOException) t : new IOException(t);
    }
}

public static void closeQuietly(@Nullable Logger log, @Nullable Process process) {
  try {
    close(process);
  }
  catch (Exception e) {
    //log if Logger provided, otherwise discard
    logError(log, "Fehler beim Schließen des Process-Objekts (inkl. underlying streams)!", e);
  }
}

public static void close(@Nullable Closeable closeable) throws IOException {
  if(closeable != null) {
    closeable.close();
  }
}

Методы, подобные этим, в основном используются в блоках finally.

Что я действительно хочу знатьесли я в безопасности с этой реализацией?Принимая во внимание такие вещи, как: всегда ли объект процесса возвращает одни и те же потоки stdin, stdout и stderr в течение своей жизни?Или я могу пропустить закрывающиеся потоки, ранее возвращенные методами getInputStream(), getOutputStream() и getErrorStream()?

На StackOverflow.com есть связанный вопрос: java: закрытие потоков std подпроцесса?

Редактировать

Как указано мной и другими здесь:

  • InputStreams должны быть полностью использованы.Если этого не сделать, подпроцесс может не завершиться, поскольку в его выходных потоках есть ожидающие данные.
  • Все три потока std должны быть закрыты.Независимо от того, используется ли он раньше или нет.
  • Когда подпроцесс завершается нормально, все должно быть в порядке.В противном случае он должен быть принудительно завершен.
  • Когда подпроцесс возвращает код завершения, нам не нужно destroy() его.Это прекратилось.(Даже если не обязательно нормально завершается с кодом выхода 0, но он завершается.)
  • Нам нужно отслеживать waitFor() и прерывать, когда превышено время ожидания, чтобы дать процессу возможность нормально завершиться, но убить его, когда он зависает.

Неотвеченные части:

  • Рассмотрим плюсы и минусы параллельного использования InputStreams.Или они должны потребляться в определенном порядке?

Ответы [ 3 ]

2 голосов
/ 16 августа 2011

Попытка упростить ваш код:

public static void close(@Nullable Process process) throws IOException
{
    if(process == null) { return; }

    try
    {
        close(process.getOutputStream());
        close(process.getInputStream());
        close(process.getErrorStream());

        if(process.waitFor() != 0)
        {
            process.destroy();
        }
    }
    catch(InterruptedException e)
    {
        process.destroy();
    }
    catch (RuntimeException e)
    {
        throw (e instanceof IOException) ? e : new IOException(e);
    }
}

Получив Throwable Я предполагаю, что вы хотите перехватить все непроверенные исключения.Это либо производная от RuntimeException, либо Error.Однако Error никогда не должен быть перехвачен, поэтому я заменил Throwable на RuntimeException.

(по-прежнему не рекомендуется перехватывать все RuntimeException с.)

1 голос
/ 22 августа 2011

Просто чтобы вы знали, что у меня есть в настоящее время в нашей кодовой базе:

public static void close(@Nullable Process process) throws IOException {
  if (process == null) {
    return;
  }

  Throwable t = null;

  try {
    flushQuietly(process.getOutputStream());
  }
  catch (Throwable e) {
    t = mostImportantThrowable(t, e);
  }

  try {
    close(process.getOutputStream());
  }
  catch (Throwable e) {
    t = mostImportantThrowable(t, e);
  }

  try {
    skipAllQuietly(null, TIMEOUT, process.getInputStream());
  }
  catch (Throwable e) {
    t = mostImportantThrowable(t, e);
  }

  try {
    close(process.getInputStream());
  }
  catch (Throwable e) {
    t = mostImportantThrowable(t, e);
  }

  try {
    skipAllQuietly(null, TIMEOUT, process.getErrorStream());
  }
  catch (Throwable e) {
    t = mostImportantThrowable(t, e);
  }

  try {
    close(process.getErrorStream());
  }
  catch (Throwable e) {
    t = mostImportantThrowable(t, e);
  }

  try {
    try {
      Thread monitor = ThreadMonitor.start(TIMEOUT);
      process.waitFor();
      ThreadMonitor.stop(monitor);
    }
    catch (InterruptedException e) {
      t = mostImportantThrowable(t, e);
      process.destroy();
    }
  }
  catch (Throwable e) {
    t = mostImportantThrowable(t, e);
  }

  if (t != null) {
    if (t instanceof Error) {
      throw (Error) t;
    }

    if (t instanceof RuntimeException) {
      throw (RuntimeException) t;
    }

    throw t instanceof IOException ? (IOException) t : new IOException(t);
  }
}

skipAllQuietly(...) потребляет полные InputStreams.Он внутренне использует реализацию, аналогичную org.apache.commons.io.ThreadMonitor, для прерывания потребления, если превышено заданное время ожидания.

mostImportantThrowable(...) решает, какой Throwable должен быть возвращен.Ошибки по всему.Сначала произошел более высокий prio, чем позже.Ничего очень важного здесь, так как эти Throwable, скорее всего, будут отброшены в любом случае позже.Мы хотим продолжать работать здесь, и мы можем бросить только один, поэтому мы должны решить, что мы бросим в конце, если вообще когда-либо.что-то пошло не так.

1 голос
/ 18 августа 2011

Поскольку вопрос, который вы связали с состояниями, лучше читать и отбрасывать потоки вывода и ошибок.Если вы используете apache commons io, что-то вроде

new Thread(new Runnable() {public void run() {IOUtils.copy(process.getInputStream(), new NullOutputStream());}}).start();
new Thread(new Runnable() {public void run() {IOUtils.copy(process.getErrorStream(), new NullOutputStream());}}).start();

Вы хотите читать и отбрасывать stdout и stderr в отдельном потоке, чтобы избежать проблем, таких как блокировка процесса, когда он записывает достаточно информации в stderr или stdoutчтобы заполнить буфер.

Если вы беспокоитесь о наличии множества потоков, см. этот вопрос

Не думаю, что вам нужно беспокоиться о перехвате исключений IOException при копированииstdout, stdin для NullOutputStream, поскольку при чтении IOException из процесса stdout / stdin это, вероятно, связано с тем, что сам процесс мертв, и запись в NullOutputStream никогда не вызовет исключение.

Вы не 'Не нужно проверять возвращаемый статус waitFor ().

Хотите дождаться завершения процесса?Если это так, вы можете сделать,

while(true) {
     try
     {
         process.waitFor();
         break;
     } catch(InterruptedException e) {
         //ignore, spurious interrupted exceptions can occur
     }

}

Просматривая предоставленную вами ссылку, вам нужно закрыть потоки после завершения процесса, но уничтожение сделает это за вас.

Итакв конце концов, метод становится,

public void close(Process process) {

    if(process == null) return;

    new Thread(new Runnable() {public void run() {IOUtils.copy(process.getInputStream(), new NullOutputStream());}}).start();
    new Thread(new Runnable() {public void run() {IOUtils.copy(process.getErrorStream(), new NullOutputStream());}}).start();
    while(true) {
        try
        {
            process.waitFor();
            //this will close stdin, stdout and stderr for the process
            process.destroy();
            break;
        } catch(InterruptedException e) {
            //ignore, spurious interrupted exceptions can occur
        }

   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...