2011-08-15 28 views
4

這個問題是關於java.lang.Process及其對stdin,stdout和stderr的處理。如何關閉來自java.lang.Process的std-streams?

我們在我們的項目中有一個班級,是org.apache.commons.io.IOUtils的擴展。在那裏,我們有一個安靜的新方法來關閉Process-Object的std-streams適當的?還是不適合?

/** 
* 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(); 
    } 
} 

方法等這些在最後塊基本上使用。

我真的很想知道的是,如果我在這個實現中安全嗎?考慮如下事情:過程對象是否在其生命週期中始終返回相同的stdin,stdout和stderr流?或者我可能會錯過之前由過程'getInputStream()getOutputStream()getErrorStream()方法返回的流?

有上StackOverflow.com一個相關的問題:java: closing subprocess std streams?

編輯

正如指出的我和其他人在這裏:

  • InputStreams必須完全消耗掉。未完成時,子進程可能無法終止,因爲其輸出流中有未完成的數據。
  • 所有三個標準流都必須關閉。無論之前是否使用過。
  • 當子進程正常終止時,一切都應該沒問題。當不是那麼它必須被強行終止。
  • 當子程序返回退出代碼時,我們不需要destroy()它。它已經終止。 (即使不一定以正常終止代碼0終止,但終止。)
  • 我們需要監視waitFor()並在超時超時時中斷,以使進程有機會正常終止但​​在掛起時將其終止。

未答覆的部分:

  • 考慮優點和並聯消耗InputStreams的缺點。或者必須按特定順序消費?
+0

只是一個說明。你的代碼中有一些嚴重的反模式。 1.嘗試/捕捉幾乎每一條語句。 2.每次抓住一切「可扔」。 3.嵌套的try語句也可以是一個,有兩個catch語句。 –

+0

如何在不將其放入單獨的try-catch塊時關閉其他流? –

+0

由於Throwable可能發生在嵌套的catch-block中,因此必須嵌套try-catch-block。對於捕捉'Throwable':什麼更合適?我想抓住一切值得我努力工作的方式來做最好的關閉資源。之前捕獲時我會拋出一個Throwables。我能在這裏做些什麼? –

回答

1

只是爲了讓你知道我有目前在我們的代碼庫:

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(...)是無效的實現來關閉的東西,但拋出異常時,出現了問題。

2

在簡化你的代碼的嘗試:

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

(抓住所有RuntimeException s仍然不是一個好主意。)

+0

您的代碼在關閉輸出流之前不會嘗試關閉輸入流。 追捕Throwable時的意圖是提供最高可能的保證,以繼續關閉資源的工作。如果Throwable發生,第一次發生Throwable被拋出。這樣做是因爲如果出現問題,最有可能的就是你想要的Throwable。 爲什麼不捕捉在這種情況下可拋?我應該抓住'Exception'嗎?爲什麼? –

+0

'IOException'不能是'RuntimeException'。所以最後一個catch塊的代碼可以簡化。但是你的實現並沒有盡其所能來關閉流。當這個異常發生在catch-interruptedException塊中時,它可能會拋出異常而不是IOException。無論如何,因爲至少有人想過它。 –

1

由於您鏈接到狀態的問題,最好是讀取並放棄輸出和錯誤流。如果您使用的是Apache公地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(); 

你想閱讀並丟棄輸出和錯誤在一個單獨的線程,以避免出現問題,如處理阻塞時將其寫入足夠的信息到stderr或標準輸出填寫緩衝區。

如果你擔心有兩個多個線程,請參見本question

我不認爲你需要擔心複製標準輸出的時候趕上的IOExceptions,標準輸入到NullOutputStream,因爲如果有一個IOException從讀處理標準輸出/標準輸入,這可能是由於進程本身死了,寫入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 
     } 

    } 
} 
+0

非常感謝您的回答。給它一個快速閱讀,稍後會更詳細地研究它。 首先我要說:你不知道以前發生了什麼事。在大多數情況下,我已經寫入OutputStream,通過InputStream獲取結果等。 您說當我不使用它們時,不需要關閉流。那麼,忽略這一點,我們不知道這裏。這[鏈接](http://mark.koli.ch/2011/01/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec.html)說,即使你不使用它們。他錯了嗎?任何來源? –

+0

感謝您的鏈接,我不知道你必須關閉流。我跑了一些測試,你需要關閉這些流。然而,destroy()方法會爲你關閉它們(如果你看看destroy的來源,至少對於它的UnixProcess來說,很可能在windows上)。我已經更新了我的答案。 – sbridges

+0

我對這裏的兩件事感到有些「不舒服」,但也許你讓我感覺更舒適。 :-) 1.)強烈認爲destroy()將處理適當的流。 2.)爲消耗輸入流創建未引用的「盲」線程,然後可能四處閒逛。 另一個問題:當waitFor()正常返回時,然後destroy()被稱爲關閉流,對吧?當它由於超時而中斷時,waitFor()會拋出一個異常,並且destroy()不會被執行,但是在這種情況下,爲了強制終止子進程,強烈需要這個異常。 –