7

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.

progNewbie
  • 4,362
  • 9
  • 48
  • 107
  • Do you have any errors/exceptions within the process ? – Mickael Jun 23 '16 at 14:05
  • Try to give full path to `java`. – PeterMmm Jun 23 '16 at 14:07
  • @MickaëlB: No unfortunately not. – progNewbie Jun 23 '16 at 14:20
  • @PeterMmm: I tryed this too, but same result. Or wait...what do you mean with java. Do you mean full path to the jar-file or the java jre? – progNewbie Jun 23 '16 at 14:20
  • @PeterMmm: I now tried this: /usr/lib64/jvm/java-7-openjdk/bin/java -jar.... but this doesn't work either. – progNewbie Jun 23 '16 at 14:27
  • Did you use `getErrorStream()` (http://docs.oracle.com/javase/7/docs/api/java/lang/Process.html#getErrorStream()) or `getOutputStream()` (http://docs.oracle.com/javase/7/docs/api/java/lang/Process.html#getOutputStream()) to see what's happening ? – Mickael Jun 23 '16 at 14:29
  • @MickaëlB: Yes I did, please see my edit. – progNewbie Jun 23 '16 at 14:31
  • First you said that `java -jar test.jar 135 3 3 appName` is working and now you're saying that `java -jar test.jar "$1" "$2" "$3" "$4"` is not working. It's wierd. Also why do you have 2 different JAR ? – Mickael Jun 24 '16 at 07:19
  • @MickaëlB: Yes this is Weird! But when writing "gnome-terminal -x" infront of the statement it works. I don't get it why. Sorry for the confusing second Name of the jar. This was a copy/paste mistake. – progNewbie Jun 24 '16 at 14:21
  • @MickaëlB: I just realised that I understood your question wrong. I said, that this command 'java -jar test.jar 135 3 3' is working, becuase it works if I type it in the terminal. But when doing this in a file like test.sh and starting this test.sh from my javacode with processbuilder, it does not work. Hope it is clear now. – progNewbie Jun 24 '16 at 16:32
  • You are calling `process.getInputStream()` after process died. Instead you should call it before calling `process.waitFor()` or just use [processBuilder.redirectOutput()](https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html#redirectOutput(java.lang.ProcessBuilder.Redirect)). Also you better wrap `process.getInputStream()` and `process.getErrorStream()` with a [BufferedInputStream](https://docs.oracle.com/javase/7/docs/api/java/io/BufferedInputStream.html) – Onur Jun 25 '16 at 17:04
  • @Onur: Could you write an answer for that, so that I can undestand that in detail? – progNewbie Jun 26 '16 at 15:18

2 Answers2

8

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.

Emadpres
  • 3,466
  • 2
  • 29
  • 44
Onur
  • 5,617
  • 3
  • 26
  • 35
  • Wow thanks for this great answer. I am currently testing Method 1. Is it possible, that I need to do new BufferedReader() instead of new BufferedInputStream() in the run-method of InputStreamConsumerThread? Because It says, that I can't convert from BufferedInputStream to BufferedReader. And how can I read the errors etc. from the errorConsumer? – progNewbie Jun 27 '16 at 09:54
  • @progNewbie yes my mistake. Corrected the answer – Onur Jun 27 '16 at 09:59
  • Thanks. How can I read from the errorConsumer? Because in my original Code I check if there is was an error, after the jar is finished executing. – progNewbie Jun 27 '16 at 10:00
  • @progNewbie added the ability to get output from `InputStreamConsumerThread`. – Onur Jun 27 '16 at 10:23
  • Wow thanks! Although it needs to be processOutput.isEmpty(). – progNewbie Jun 27 '16 at 11:29
  • Just one more question, but first: IT WORKS! WOW. BUT I get also all warnings in my errorStream. But I would like to get just real errors, so that I can check if the program successfully ended. Is this possible? – progNewbie Jun 27 '16 at 11:33
  • @progNewbie yea sorry about that, I should stop writing code without an IDE. If you just wanna know if program ended successfully, you can use the integer value that `process.waitFor()` returns. In your sub-process code, when you encounter an error you should call `System.exit(-1); //or any non-zero value indicates error`. `process.waitFor()` or `process.exitValue()` will return the integer you call `System.exit()` with or `0` if sub-process ended normally. – Onur Jun 27 '16 at 11:44
  • Thank you very much! :) You are great. – progNewbie Jun 27 '16 at 11:47
0

Can you try this instead ?

UPDATE

Code :

// Java runtime
Runtime runtime = Runtime.getRuntime();
// Command
String[] command = {"java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application};
// Process
Process process = runtime.exec(command, null, new File("/home/userName/TestBSC"));
Mickael
  • 4,458
  • 2
  • 28
  • 40
  • Unfortunately it happens the same. The process is executed. But there is no output and nothing gets stored in my database. Like the process is not really processing. This is so strange. – progNewbie Jun 23 '16 at 14:51
  • What is the `exitValue` of the process ? (http://docs.oracle.com/javase/7/docs/api/java/lang/Process.html#exitValue()) – Mickael Jun 23 '16 at 15:01
  • The process doesn't stop. :( – progNewbie Jun 23 '16 at 15:02
  • could this be a priority thing or something like that? – progNewbie Jun 23 '16 at 15:30
  • I don't think so. Do you still have this `waitFor()` ? If yes, can you move it after `System.out.println( new String( error ) );` ? – Mickael Jun 23 '16 at 15:34
  • It didn't change anything. But wow this is so strange. I just ended a process still running from previous tests. And suddenly all the System.out.println output from the jar was in my eclipse console and still no lines in my database. really strange. – progNewbie Jun 23 '16 at 15:47