I have the following code:
ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();
When running the jar file from my terminal with this command:
java -jar test.jar 135 3 3 appName
Then it works like a charm. The jar pushes some stuff in my database so I see that it is working. But when doing it from my JavaServlet with the processBuilder-code mentioned above, I don't get any data in my database and I don't get any errors either.
However the process itself is running, I checked it with "ps ax" in my terminal. So I wonder where is here the difference? What am I doing wrong?
Has someone an idea?
Edit: more Code:
ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();
System.out.println( "Job running" );
proc.waitFor(); // wait until jar is finished
System.out.println( "Job finished" );
InputStream in = proc.getInputStream();
InputStream err = proc.getErrorStream();
byte result[] = new byte[ in.available() ];
in.read( result, 0, result.length );
System.out.println( new String( result ) );
byte error[] = new byte[ err.available() ];
err.read( error, 0, error.length );
System.out.println( new String( error ) );
I tried to call a shell-script instead of my jar. So I called a shell script with the processbuilder from my java-file.
My shell script does this:
java -jar test.jar "$1" "$2" "$3" "$4"
Well it still didn't work. So I tried this:
gnome-terminal -x java -jar test.jar "$1" "$2" "$3" "$4"
And suddenly it works!! BUT it opens the gnome-terminal, which executes the jar-file.
So I wonder, could this has anything to do with the output which isn't shown in eclipse? I really don't get it. This is now a nice workaround. But I really would like to get this working without having my terminal opening each time the jar gets executed.
Run a Nonexecutable JAR with Arguments To run an application in a nonexecutable JAR file, we have to use -cp option instead of -jar. We'll use the -cp option (short for classpath) to specify the JAR file that contains the class file we want to execute: java -cp jar-file-name main-class-name [args …]
ProcessBuilder. directory(File directory) method sets this process builder's working directory. Subprocesses subsequently started by this object's start() method will use this as their working directory.
First of all, I couldn't reproduce your problem so this answer will be based solely on documentation.
By default, the created subprocess does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and getErrorStream(). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, or even deadlock.
java.lang.Process Documentation
Basically this tells you that you need to handle the streams of your external process properly otherwise it may cause deadlocks in some platforms. Which means if the command we run produces some output you have to read that output.
So lets take a look at your code; you are calling process.waitFor()
to wait until process is finished but the thing is your process can't finish without you read/consume it's output hence you are creating a deadlock.
How to overcome this:
Method is using an InputStreamConsumerThread
to handle input/error stream properly;
public class InputStreamConsumerThread extends Thread
{
private InputStream is;
private boolean sysout;
private StringBuilder output = new StringBuilder();
public InputStreamConsumerThread (InputStream is, boolean sysout)
{
this.is=is;
this.sysout=sysout;
}
public void run()
{
try(BufferedReader br = new BufferedReader(new InputStreamReader(is)))
{
for (String line = br.readLine(); line != null; line = br.readLine())
{
if (sysout)
System.out.println(line);
output.append(line).append("\n");
}
}
}
public String getOutput(){
return output.toString();
}
}
your code will be;
String systemProperties = "-Dkey=value";
ProcessBuilder pb = new ProcessBuilder( "java", systemProperties, "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();
InputStreamConsumerThread inputConsumer =
new InputStreamConsumerThread(proc.getInputStream(), true);
InputStreamConsumerThread errorConsumer =
new InputStreamConsumerThread(proc.getErrorStream(), true);
inputConsumer.start();
errorConsumer.start();
System.out.println( "Job running" );
proc.waitFor(); // wait until jar is finished
System.out.println( "Job finished" );
inputConsumer.join(); // wait for consumer threads to read the whole output
errorConsumer.join();
String processOutput = inputConsumer.getOutput();
String processError = errorConsumer.getOutput();
if(!processOutput.isEmpty()){
//there were some output
}
if(!processError.isEmpty()){
//there were some error
}
Method is using ProcessBuilder
to redirect output. If you just want your sub-process use the same input/output stream with your parent process, you can use ProcessBuilder.Redirect.INHERIT
like this;
ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
pb.redirectErrorStream(true); // redirect error stream to output stream
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process proc = pb.start();
System.out.println( "Job running" );
//since process builder will handle the streams for us
//we can call waitFor() safely
proc.waitFor();
System.out.println( "Job finished" );
Method is using a 3rd party library. If you don't wanna hassle with ProcessBuilder
and the consumer threads yourself, there are 2 libraries that I know of that handles creating sub-process very nicely.
A low-overhead, non-blocking I/O, external Process execution implementation for Java. It is a replacement for java.lang.ProcessBuilder and java.lang.Process.
Have you ever been annoyed by the fact that whenever you spawn a process in Java you have to create two or three "pumper" threads (for every process) to pull data out of the stdout and stderr pipes and pump data into stdin? If your code starts a lot of processes you can have dozens or hundreds of threads doing nothing but pumping data.
NuProcess uses the JNA library to use platform-specific native APIs to achive non-blocking I/O on the pipes between your Java process and the spawned processes.
NuProcess
There are many approaches to take when running external processes from Java. There are the JRE options such as the Runtime.exec() and ProcessBuilder. Also there is the Apache Commons Exec. Nevertheless we created yet another process library (YAPL).
Some of the reasons for this crazy endeavour
Improved handling of streams Reading/writing to streams Redirecting stderr to stdout Improved handling of timeouts Improved checking of exit codes Improved API One liners for quite complex usecases One liners to get process output into a String Access to the Process object available Support for async processes ( Future ) Improved logging with SLF4J API Support for multiple processes
ZT Process Executor
Also there are other pitfalls in Java's Process API, take a look at this JavaWorld article for more info When Runtime.exec() won't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With