Runtime.exec().waitFor() not actually waiting for
Asked Answered
H

5

13

I've got some code that uses Runtime.exec() to run an external .jar (built as an IzPack installer).

If I run this external.jar from the command line like so:

java -jar external.jar

Then the command prompt does not return control until the application is finished. However, if I run external.jar from within some java class, using:

Process p = Runtime.getRuntime().exec("java -jar external.jar");
int exitCode = p.waitFor();
System.out.println("Process p returned: " + exitCode);

Then p returns almost instantly with a success code of 0, despite external.jar having not yet completed execution (i've also tried this via the ProcessBuilder route of external file execution).

Why does it wait to return from the command line, but not when executed from within another java program?

I've also set up 3 jars, A, B and C where A calls B which calls C (using Runtime.exec()), where C Thread.sleeps for 10 seconds, as a simple test, and as expected, A doesn't return until 10 seconds after it runs.

I figure this is probably some kind of a threading issue with external.jar where execution is being handed over from one thing to another, but given that it works directly from the command line i kind of expected to see the same behaviour (perhaps naively) when called from within another java program.

I've tested this on Windows and Ubuntu with Java 6.

Thanks!

Hold answered 5/7, 2012 at 18:28 Comment(9)
As far as I know, whatFor() will wait for the the process you've started to end, meaning all non-daemon threads have finished, so I doubt that it's a threading issue alone. I wonder if the jar your calling itself calls an outside process and then exits.Possessive
How do you know that it hasn't finished yet? Do you still see the jar in the process list?Studbook
This is unlikely, any chance that some other thread is calling notify() on your Process field? That is what waitFor() does in UNIXProcess at least.Studbook
external.jar spawns a console window of its own which then runs through various System.out.printlns of its own that log its execution process. This logging is still very much in continuation when the exit code is returned (plus, i know it takes a solid 30s or so to do everything whereas the return is almost instantaneous). Perhaps this ties into what Hovercraft is suggesting?Hold
Gray - Process p is completely self-contained within a method call that does nothing else beyond what's described above, so there's no other interaction with it.Hold
@Ryven: I'm not doubting that the the program takes 30s or more, but I wonder if what you're calling is a launching program that perhaps checks what type of system it's being called on, and then it itself calls the loading program and exits. If you run it from the command line, when does the command line return ready for new input?Possessive
@HovercraftFullOfEels It returns after 30s, as expected (whereas from Java, it returns near instantly).Hold
hey your implementation is wrong otherwise it should work as you expect unless within java process you launch your jar with javaw.exe rather then java.exe. I have tested Process invoking using three threads, mainly input, output and error so once you invoke your process, you should already be listening on input and error streams, and keep printing whatever you get, you would see it works. The problem you have is you are not doing an interactive process, rather just dispatching the command and returning control, open error and input/ouput streams as speerate threads, and it will 100% work!!Troublesome
@Troublesome hi, thanks for the input, but i was actually unloading the streams onto their own threads anyway, just didn't write it in for brevity. Thanks anyway.Hold
C
7

another possible way to achieve this might be to capture the output of the process and wait for it to finish.

For example:

Process tr = Runtime.getRuntime().exec( new String[]{"wkhtmltopdf",mainPage,mainPagePDF});
BufferedReader stdOut=new BufferedReader(new InputStreamReader(tr.getInputStream()));
String s;
while((s=stdOut.readLine())!=null){
       //nothing or print
}

Normally the output stream is tr.getInputStream() but depending on the program you are executing the process output stream migh be:

  • tr.getInputStream()
  • tr.getErrorStream()
  • tr.getOutputStream()

By doing this while loop you force your program to wait the process to finish.

Coffeng answered 30/12, 2013 at 8:32 Comment(1)
This actually worked for me. waitFor() immediately returns (e.g. because the main thread has ended) but the streams will remain accessible as long as the process has not completely terminated.Arian
B
1

You can use Process Builder....

ProcessBuilder pb = new ProcessBuilder("java", "-jar", "/fielname.jar");
Process p = pb.start();
p.waitFor();
Barta answered 23/10, 2015 at 10:52 Comment(1)
Won't failure to consume input and error stream cause hang up here?Hudnall
T
0

Are you spawning a new thread to handle the spawning of the process? If so the origional program will continue to operate independently of the spawned process and therefore waitFor() will only work on the new process and not the parent.

Transducer answered 5/7, 2012 at 19:6 Comment(1)
No, it's all happening in one thread, i.e. I can Thread.sleep in my calling method and have my app sleep, which is currently what i'm doing to work around this issue by sleeping until certain external parameters are met (like certain files existing). This isn't ideal, though.Hold
H
-1

Process.waitFor() is useless for some native system command.

You need to get the process's output to determine if it is returned.

I wrote a sample code for you

/**
 * 
 * @param cmdarray      command and parameter of System call
 * @param dir           the directory execute system call
 * @param returnImmediately   true indicate return after system call immediately; 
     *                          false otherwise.
 *  if set true, the returned call result does not have reference value
 * @return the return code of system call , default is -1
 */
public static int systemCall(String[] cmdarray,File dir,boolean returnImmediately)
{
  int result = -1;
  try {
   Process p = Runtime.getRuntime().exec(cmdarray,null,dir);
  if(!returnImmediately)
  {
   java.io.InputStream stdin = p.getInputStream();
   java.io.InputStreamReader isr = new java.io.InputStreamReader(stdin);
   java.io.BufferedReader br = new java.io.BufferedReader(isr);
   String line = null;
   while ( (line = br.readLine()) != null)
      System.out.println(line);
      }
      try{result =  p.exitValue();}
        catch(Exception ie){;}
      } catch (IOException e) {
        e.printStackTrace();}

  return result;
}

public static void main(String[] argc){             
  String[] cmdarray = {"jar","cvf","s2.jar","*"};
  File dir = new File("D:\\src\\struts-2.3.1");
  int k = systemCall(cmdarray,dir,true);
  System.out.println("k="+k);
 }
Hoist answered 7/9, 2012 at 6:19 Comment(2)
"useless for some native system command" what else is it used for?Civilly
Your opening contention requires some supporting evidence or reasoning, and your code is wrong because you aren't consuming the process's error stream; you aren't closing its input stream; and you are ignoring the exception that is thrown by exitValue() if the process hasn't exited. -1Breathless
C
-2

I had the same problem using processs to execute some software using the console, and i just solved it using process.waitFor()

For me it worked perfectly.

    try{
        Process tr = Runtime.getRuntime().exec( new String[]{ "wkhtmltopdf",frontPage,frontPagePDF});
        tr.waitFor();
    } catch (Exception ex) {
        EverLogger.logEntry("Error al pasar a PDF la portada", "error", "activity");
        return;
    } 
some more code here.
Coffeng answered 10/12, 2013 at 10:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.