Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running jar with Processbuilder doesn't work properly

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 ) );

UPDATE:

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.

like image 985
progNewbie Avatar asked Jun 23 '16 14:06

progNewbie


People also ask

How do I run a JAR file in Windows with arguments?

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 …]

How do I change the working directory in ProcessBuilder?

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.


1 Answers

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:

  1. 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
    }

    
  1. 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" );
    
  2. 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.

like image 185
Onur Avatar answered Nov 03 '22 19:11

Onur