-
Notifications
You must be signed in to change notification settings - Fork 5.8k
8263729: [test] divert spurious output away from stream under test in ProcessBuilder Basic test #3049
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
Conversation
…sBuilder Basic test
👋 Welcome back rriggs! A progress list of the required criteria for merging this PR into |
@RogerRiggs The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
@@ -2179,7 +2179,7 @@ public void run() { | |||
|
|||
thread.start(); | |||
latch.await(); | |||
Thread.sleep(10); | |||
Thread.sleep(100L); // Wait for child initialization to settle | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Roger,
Shouldn't the code that follows this sleep already be enough to wait for child thread to get a lock on the stream?
Thread.sleep(100L); // Wait for child initialization to settle
if (s instanceof BufferedInputStream) {
// Wait until after the s.read occurs in "thread" by
// checking when the input stream monitor is acquired
// (BufferedInputStream.read is synchronized)
while (!isLocked(s, 10)) {
Thread.sleep(100);
}
}
From 1st glance I would conclude that the 1st sleep is unnecessary. Is there a platform where Input/Error stream is not a BufferedInputStream? Or is it calling Process.destory() immediately after the child thread enters BufferedInputStream.read synchronized method perhaps too early to trigger appropriate exception in the child thread? In that case the additional sleep should be added after the while(!isLocked... loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That loop is checking that the Thread (in the parent) reading from the child is in the correct state (blocked). On Windows, it is a BufferedInputStream.
But it does not indicate anything about the state of the child process.
From the scant information from previous failures, it appears that the the creation of some thread (not a java thread) in the child has failed.
The additional time is to avoid destroying the child while it is still initializing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The failures happened in tiers 6 and 8. The system may be overloaded so even 100ms may not be enough for the child process to start sleeping. From the error log, the child process tried to spawn a thread (probably one of those usually started during VM bootstrap) at around 118ms
[0.118s][warning][os,thread] Failed to start thread - _beginthreadex failed (EACCES) for attributes: stacksize: default, flags:
The test runs 4 times. Each time it checks only STDOUT or STDERR, but not both. So I think we can use the other stream to signal to the main process that the child process is ready. That would be more reliable than an arbitrary wait time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That complicates the test and the child quite a bit for minimal gain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arbitrary time out has been a reliable source of intermittent failures.
Since we have spent a lot of time analyzing this failure, I think it's worthwhile to fix it properly, which doesn't seem that complicated. That's better than the same bug happening again a year later and a different set of people would spend hours to analyze it again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test expects there to be zero output from the child (and it doesn't matter what state the child is in).
Can the logging from the VM be disabled or re-directed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test expects there to be zero output from the child (and it doesn't matter what state the child is in).
Can the logging from the VM be disabled or re-directed?
Not to the extend that it would be guaranteed never to happen. Even if we control all output in the hotspot, there are other libraries. E.g. glibc writes a lengthy report to stderr in case of a heap corruption, which I believe does not result in a hs-err file.
One simple solution, simpler than using two threads, could be to use ProcessBuilder::redirectError(Redirect.INHERIT) if reading stdout resp. ProcessBuilder::redirectOutput(Redirect.INHERIT) if reading stderr. One line, takes care of the stream you don't read does not block, and, we can see the child output in the parent stdout/err.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That complicates the test and the child quite a bit for minimal gain.
Suppose that the child process writes 1 byte to STDOUT and 1 byte to STDERR. In the monitoring thread in the parent process, you could read 1 byte from either STDOUT or STDERR, then count-down the latch and then block in next read. The main thread could then just wait for the latch, wait for the monitoring thread to re-enter the synchronized read method and then destroy the process... It doesn't sound too complicated. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redirecting the stream not being tested to INHERIT may be useful.
But the failing case is when the vm output is being directed to the stream being tested.
The command line arguments are:
Arrays.asList(javaExe,
"-XX:+DisplayVMOutputToStderr",
"-classpath", absolutifyPath(classpath),
"Basic$JavaChild");```
A combination of redirecting to inherited and sending the VM output to the same (untested) stream seems like a good option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@plevart, I prototyped using that technique but was concerned that it might change the test case to be different in some subtle way from the original. It adds a new subcommand to the JavaChild to add the specific behavior.
Not particularly complex but more code and dependencies to explain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Roger, thanks for taking my suggestion. Minor point below.
Cheers, Thomas
break; | ||
case 1: | ||
childArgs.set(1, "-XX:+DisplayVMOutputToStdout"); | ||
pb.redirectInput(INHERIT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not redirectOutput()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct. will fix.
(The Process streams are from the point of view of parent).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I have to look this up every time. Its not intuitive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Just a small nit.
List<String> childArgs = new ArrayList<String>(javaChildArgs); | ||
List<String> childArgs = new ArrayList<>(javaChildArgs); | ||
final ProcessBuilder pb = new ProcessBuilder(childArgs); | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was a little confused until I looked up the JavaDoc of ProcessBuilder.
Maybe add a comment like "ProcessBuilder does NOT make a copy of childArgs so it's OK to modify it below"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, if the intent was to fix potential write blockages in the child process (in which case maybe reformulate the issue title).
I'm sorry I derailed your original wait idea, as well as Ioi's ideas of syncing with the child. Both may still be needed after this fix.
Cheers, Thomas
@RogerRiggs This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 68 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
Thanks for the reviews and the suggestion to redirect the output to the inherited streams. |
@RogerRiggs Since your change was applied there have been 68 commits pushed to the
Your commit was automatically rebased without conflicts. Pushed as commit 0abbfb2. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Intermittent failures on Windows in a test of destroying the child warrant extending the time the parent waits after starting the child before destroying the child.
Progress
Issue
Reviewers
Download
To checkout this PR locally:
$ git fetch https://git.openjdk.java.net/jdk pull/3049/head:pull/3049
$ git checkout pull/3049
To update a local copy of the PR:
$ git checkout pull/3049
$ git pull https://git.openjdk.java.net/jdk pull/3049/head