【问题标题】:How to close std-streams from java.lang.Process appropriate?如何从 java.lang.Process 适当地关闭标准流?
【发布时间】:2011-10-27 06:17:30
【问题描述】:

这个问题是关于java.lang.Process 及其对标准输入、标准输出和标准错误的处理。

我们的项目中有一个类是org.apache.commons.io.IOUtils 的扩展。我们有一种安静的新方法可以适当地关闭 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-blocks中。

我真正想知道的是我是否可以安全地使用此实现?考虑以下事项:进程对象在其生命周期内是否总是返回相同的标准输入、标准输出和标准错误流?或者我可能会错过关闭之前由进程'getInputStream()getOutputStream()getErrorStream() 方法返回的流?

StackOverflow.com 上有一个相关问题:java: closing subprocess std streams?

编辑

正如我和其他人在这里指出的:

  • InputStreams 必须被完全消耗掉。如果未完成,则子进程可能不会终止,因为其输出流中有未完成的数据。
  • 必须关闭所有三个标准流。不管之前是否使用过。
  • 当子进程正常终止时,一切都应该没问题。如果没有,则必须强制终止。
  • 当子进程返回退出代码时,我们不需要destroy()它。它已终止。 (即使不一定以退出代码 0 正常终止,但它已终止。)
  • 我们需要监控waitFor(),并在超时时间超过时中断,让进程有机会正常终止,但在挂起时将其杀死。

未回答的部分:

  • 考虑并行使用 InputStream 的优缺点。还是必须按特定顺序食用?

【问题讨论】:

  • 只是一个注释。您的代码中有一些严重的反模式。 1. 尝试/捕捉几乎每一条语句。 2. 每次都能捕捉到所有“可投掷”的东西。 3.嵌套的try语句也可以是一个,两个catch语句。
  • 不将其他流放入单独的 try-catch 块时,如何继续关闭其他流?
  • 嵌套的 try-catch-block 必须是,因为 Throwable 可以出现在嵌套的 catch-block 中。对于追星Throwable:什么更合适?我想抓住一切值得继续努力的事情,以尽我最大的努力关闭资源。之前被抓到时,我在最后扔了一个 Throwables。我可以在这里做得更好吗?
  • 请注意,这是一个实用程序类,用于关闭其他地方的 finally 块中的资源。处理异常不是它的工作。它抛出第一个发生的异常。调用者必须处理它。但它的意图是尽一切可能释放调用者提供的资源。

标签: java process stream resources


【解决方案1】:

简化代码的尝试:

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 我假设您希望捕获所有未经检查的异常。这是RuntimeExceptionError 的派生词。但是Error 永远不应该被抓住,所以我用RuntimeException 替换了Throwable

(捕获所有RuntimeExceptions 仍然不是一个好主意。)

【讨论】:

  • 之前发生关闭输出流时发生异常时,您的代码不会尝试关闭输入流。捕获 Throwable 的目的是尽可能保证在关闭资源的情况下继续工作。如果发生了 Throwable,则最后抛出第一个发生的 Throwable。这样做是因为如果出现问题,它很可能是您想要的 Throwable。在这种情况下为什么不捕捉 Throwable 呢?我应该抓住Exception 吗?为什么?
  • IOException 不能是 RuntimeException。因此可以简化最后一个 catch-block 的代码。但是您的实现并没有尽一切可能关闭流。当此异常发生在 catch-interruptedException 块中时,它可能会抛出除IOException 之外的其他异常。无论如何 +1,因为至少有人想过。
【解决方案2】:

作为您链接到状态的问题,最好读取并丢弃输出和错误流。如果您使用的是 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 写入足够的信息以填充缓冲区时阻塞等问题。

如果你担心有两个多线程,看这个question

我认为在将 stdout、stdin 复制到 NullOutputStream 时不必担心捕获 IOException,因为如果从进程 stdout/stdin 读取到 IOException,可能是由于进程本身已死,并且正在写入到 NullOutputStream 永远不会抛出异常。

你不需要检查waitFor()的返回状态。

您要等待该过程完成吗?如果是这样,你可以这样做,

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

}

查看您提供的链接,您确实需要在该过程完成后关闭流,但 destroy 会为您完成。

所以最后,方法变成了,

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
        }

   }
}

【讨论】:

  • 非常感谢您的回答。快速阅读它,稍后将更详细地研究它。首先我要说:你不知道 Process-Object 之前发生了什么。在大多数情况下,我实际上已经写入了 Outputstream,通过 InputStream 获取结果等。你说当我不使用它们时关闭流是不必要的。好吧,忽略我们在这里无法知道的一点。这个link 说即使你不使用它们也必须这样做。他错了吗?有什么来源吗?
  • 感谢您的链接,我不知道您必须关闭流。我进行了一些测试,您确实需要关闭这些流。但是,destroy() 方法会为您关闭它们(如果您查看销毁的来源,至少对于 UnixProcess 是这样,很可能在 Windows 上也是如此)。我已经更新了我的答案。
  • 我对这里的两件事感到有点“不舒服”,但也许你让我感觉更舒服。 :-) 1.) 坚信 destroy() 将适当地处理流。 2.)创建非引用的“盲”线程来消耗输入流,然后可能会闲逛。另一个问题:当 waitFor() 正常返回时,才调用 destroy() 来关闭流,对吗?而当它因为超时而被中断时,waitFor() 会抛出一个异常并且destroy() 不会被执行,但是在这种情况下,为了强行终止子进程是非常需要的?!
  • 问得更准确些:如果子进程不能正常终止而必须强制终止会怎样?这没有在您的代码中处理,还是我错过了什么?
  • “可能发生虚假的中断异常” ???不,他们不能。你为什么这么认为?
【解决方案3】:

只是为了让您知道我目前在我们的代码库中拥有什么:

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。错误超过一切。首先发生的优先级高于后来发生的。这里没有什么很重要的,因为这些 Throwable 很可能在以后被丢弃。我们想继续在这里工作,我们只能扔一个,所以我们必须决定最后扔什么,如果有的话。

close(...) 是 null 安全的实现,用于关闭内容,但在出现问题时抛出异常。

【讨论】:

    猜你喜欢
    • 2018-08-29
    • 2011-08-08
    • 2020-10-20
    • 2015-12-11
    • 1970-01-01
    • 1970-01-01
    • 2020-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多