Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

close process streams explicitly after the process exits #2053

Merged
merged 8 commits into from
Apr 5, 2018

Conversation

vladak
Copy link
Member

@vladak vladak commented Mar 29, 2018

With this change the history part of the indexing works again, hurrah!

Also, I renamed the single letter Timer instance and reordered the check so it happens early in the finally block.

@vladak
Copy link
Member Author

vladak commented Mar 29, 2018

I introduced closeStreams() method that is I believe similar to closeQuietly() found in Apache commons or Guava. I left it deliberately private because of google/guava#1118

// Close the pipes to avoid file descriptors from being drained
// quickly. Then set the process object to null in the high hopes
// that the garbage collector will finish rest of the cleanup soon.
closeStreams(process);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I am missing something but why are you closing the process here and then you are doing the same in finally? I think this is basically redundant...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true, moved the close to the finally block. I never liked setting objects to null anyway.

@@ -136,6 +136,21 @@ public int exec(boolean reportExceptions) {
return ret;
}

/**
* Close all the 3 streams of a process.
* @param process
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add description to the parameter or delete it. This way it says nothing and just takes one line of code...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

ret = process.exitValue();
}
} catch (IllegalThreadStateException e) {
if (process != null) {
closeStreams(process);
process.destroy();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process will be destroyed twice. Is it supported? JavaDoc does not say...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was an oversight, removed.

try {
if (process != null) {
closeStreams(process);
ret = process.exitValue();
}
} catch (IllegalThreadStateException e) {
if (process != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it wise to do basically the same thing in the exception handler? Won't be the same exception thrown once again?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

*/
private static void closeStreams(Process process) {
try {
process.getOutputStream().close();
Copy link
Contributor

@ahornace ahornace Mar 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really necessary? Process API was refactored in Java 9 and I have basically the same code in Java 10 as is here. As you can see, there is different handling for Solaris, it might be a good idea to investigate why. Maybe they have good reason.

screen shot 2018-03-29 at 14 03 44

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Java 8 Netbeans complain if the try/catch block for the IOException is not there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I mean what is the purpose of closing the streams right before the destroy() call which under the hood does exactly the same. Does destroy() throw an exception which prevents closing the streams?

@tarzanek
Copy link
Contributor

lgtm, after you sort out comments from Adam, feel free to merge ... sanity checking wise it's OK

@tarzanek tarzanek added this to the 1.1 milestone Mar 29, 2018
@idodeclare
Copy link
Contributor

Hi, @vladak. Please see the old OpenGrok IOUtils.close().

@vladak
Copy link
Member Author

vladak commented Mar 29, 2018

@idodeclare thanks, redone. Got rid of the method as a result.

@ahornace
Copy link
Contributor

I looked into the process implementation because I was quite curious how it is done.

I believe it would be safer to call .destroy() instead of directly closing the streams. Mainly because .destroy() method takes into account DeferredCloseInputStream which is used on Solaris.

Rationale for using DeferredCloseInputStream:

    // A FileInputStream that supports the deferment of the actual close
    // operation until the last pending I/O operation on the stream has
    // finished.  This is required on Solaris because we must close the stdin
    // and stdout streams in the destroy method in order to reclaim the
    // underlying file descriptors.  Doing so, however, causes any thread
    // currently blocked in a read on one of those streams to receive an
    // IOException("Bad file number"), which is incompatible with historical
    // behavior.  By deferring the close we allow any pending reads to see -1
    // (EOF) as they did before.

@vladak you know OS stuff much much better than I do so if you think it's okay then I have no problem with it :)

@vladak
Copy link
Member Author

vladak commented Apr 4, 2018

I don't quite like the close() triplet either. That said, while calling destroy() after exitValue() in the finally block would do the trick, it would make an implicit assumption of destroy() closing the streams - the javadoc for it (https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html#destroy--) is fairly silent on what is going on under the hood. Surely it would be nice if Process offered a method to close all of the streams explicitly.

As for the Solaris (and AIX where the constrains are actually worse according to the comments in the UNIXProcess source code) specific semantics, it seems the reason is a difference of what happens if pipe file descriptor is closed while read() syscall on it is in progress. Unlike Linux/BSD, the processReaperExecutor thread does not close the streams so on Solaris one has to close them explicitly or call destroy(). So I think on these 2 systems close() syscall triggers EOF while on Solaris it results in EBADF errno that is possibly translated into IOException. My simple test program with 2 POSIX threads and named pipe (one continuously reading, and the other did close() on the pipe file descriptor after some time) confirms such behaviour on Solaris (EBADF) however I am getting the same on Linux.

@vladak vladak merged commit 7482970 into oracle:master Apr 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants