For test automation, my program starts the installer (= child process):
Runtime.getRuntime().exec(new String[] { "installer.exe", "-J-javaagent:myagent.jar" });
The installer starts, the agent successfuly initializes and starts a ServerSocket.
java.net.Socket socket = new java.net.ServerSocket(44444).accept();
Then my program successfully connects to the agent:
java.net.Socket socket = new java.net.Socket("localhost", 44444);
After 1-3 request/response-cycles, the agent blocks during a socket.write
call, and my program blocks during a socket.read
call.
However, if I start the installer with cmd /c start
(= independent process), everything works as expected:
Runtime.getRuntime().exec(new String[] { "cmd", "/c", "start", "installer.exe", "-J-javaagent:myagent.jar" });
So, the TCP socket breaks down after a few bytes if there is a parent/child process relationship, but works if the processes are independent. Does anyone have a clue what could be going on here?
When using Runtime.exec
or ProcessBuilder
to launch a sub-process, care must be taken to consume the stdout/stderr. If the sub-process writes to stream which is not consumed, it will freeze (similar to the effect of Ctrl-S in some terminals). Unfortunately a large proportion of StackOverflow answers show incorrect handling.
The issue is apparently fixed when adding "cmd.exe","/c", "start" as the CMD kicks off the sub-process and hides the stdout/stderr from Java.
The simplest way to deal with this is to consume the streams by reading from the Process
as redirect to File, reading stdout+err with different threads, or merge stderr -> stdout and read stdout only, or use inheritIO()
to share the streams with the caller stdout/stderr.
ProcessBuilder
is better way to deal with the streams:
Process p = new ProcessBuilder("installer.exe", "-J-javaagent:myagent.jar")
// Choose one:
.inheritIO()
// or redirectError(stderrFile).redirectOutput(stdoutFile)
// or redirectError(stderrFile) and consume p.getInputStream()
// or redirectErrorStream(true) and consume p.getInputStream()
// or redirectErrorStream(true).redirectOutput(stdoutFile)
// or consume p.getInputStream() and p.getErrorStream() in different threads
.start();
Example code to consume stream when not using redirection to File or inheritIO()
as above:
try(var stdout = p.getInputStream()) {
stdout.transferTo(System.out);
}
Don't forget to check the exit code:
int rc = p.waitFor();