Java ProcessBuilder: Resultant Process Hangs
Asked Answered
H

10

38

I've been trying to use Java's ProcessBuilder to launch an application in Linux that should run "long-term". The way this program runs is to launch a command (in this case, I am launching a media playback application), allow it to run, and check to ensure that it hasn't crashed. For instance, check to see if the PID is still active, and then relaunch the process, if it has died.

The problem I'm getting right now is that the PID remains alive in the system, but the GUI for the application hangs. I tried shifting the ProcessBuilder(cmd).start() into a separate thread, but that doesn't seem to be solving anything, as I hoped it would have.

Basically the result is that, to the user, the program APPEARS to have crashed, but killing the Java process that drives the ProcessBuilder.start() Process actually allows the created Process to resume its normal behavior. This means that something in the Java application is interfering with the spawned Process, but I have absolutely no idea what, at this point. (Hence why I tried separating it into another thread, which didn't seem to resolve anything)

If anyone has any input/thoughts, please let me know, as I can't for the life of me think of how to solve this problem.

Edit: I have no concern over the I/O stream created from the Process, and have thus taken no steps to deal with that--could this cause a hang in the Process itself?

Hygroscopic answered 19/7, 2010 at 21:54 Comment(1)
Since you've already stated that you're not dealing with the streams of the process, I must chime in to say that "Yes, it is highly likely that it is the cause of the problem. It is important to read the contents of stdout and stderr, and also write to stdin if the child expects you to". It would be worth your while to check this SO question: #883272Malina
M
47

If the process writes to stderr or stdout, and you're not reading it - it will just "hang" , blocking when writing to stdout/err. Either redirect stdout/err to /dev/null using a shell or merge stdout/err with redirectErrorStream(true) and spawn another thread that reads from stdout of the process

Moorer answered 19/7, 2010 at 22:6 Comment(2)
Extremely important it seems! You HAVE TO CONSUME the stdout/stderr from ffmpeg. At least I noticed so for publishing/consuming rtmp connections.Hamer
So, and in the case of the owner process terminate and leave the child process running, what will happen?Chordophone
D
13

You want the trick?

Don't start your process from ProcessBuilder.start(). Don't try to mess with stream redirection/consumption from Java (especially if you give no s**t about it ; )

Use ProcessBuilder.start() to start a little shell script that gobbles all the input/output streams.

Something like that:

#!/bin/bash

nohup $1 >/dev/null 2>error.log &

That is: if you don't care about stdout and still want to log stderr (do you?) to a file (error.log here).

If you don't even care about stderr, just redirect it to stdout:

#!/bin/bash

nohup $1 >/dev/null 2>1 &

And you call that tiny script from Java, giving it as an argument the name of the process you want to run.

If a process running on Linux that is redirecting both stdout and stderr to /dev/null still produce anything then you've got a broken, non-compliant, Linux install ;)

In other word: the above Just Works [TM] and get rid of the problematic "you need to consume the streams in this and that order bla bla bla Java-specific non-sense".

Dworman answered 19/7, 2010 at 22:46 Comment(1)
got a good laugh out of us - it's now friday night and this cost us our evening :)Hosey
F
11

The thread running the process may block if it does not handle the output. This can be done by spawning a new thread that reads the output of the process.

    final ProcessBuilder builder = new ProcessBuilder("script")
                    .redirectErrorStream(true)
                    .directory(workDirectory);

    final Process process = builder.start();
    final StringWriter writer = new StringWriter();

    new Thread(new Runnable() {
        public void run() {
            IOUtils.copy(process.getInputStream(), writer);
        }
    }).start();

    final int exitValue = process.waitFor();
    final String processOutput = writer.toString();
Fever answered 15/10, 2012 at 10:45 Comment(0)
M
7

Just stumbled on this after I had a similar issue. Agreeing with nos, you need to handle the output. I had something like this:

ProcessBuilder myProc2 = new ProcessBuilder(command);
final Process process = myProc2.start();

and it was working great. The spawned process even did output some output but not much. When I started to output a lot more, it appeared my process wasn't even getting launched anymore. I updated to this:

ProcessBuilder myProc2 = new ProcessBuilder(command);
myProc2.redirectErrorStream(true);        
final Process process = myProc2.start();
InputStream myIS = process.getInputStream();
String tempOut = convertStreamToStr(myIS);

and it started working again. (Refer to this link for convertStreamToStr() code)

Mcclurg answered 7/12, 2011 at 16:51 Comment(0)
T
3

Edit: I have no concern over the I/O stream created from the Process, and have thus taken no steps to deal with that--could this cause a hang in the Process itself?

If you don't read the output streams created by the process then it is possible that the application will block once the application's buffers are full. I've never seen this happen on Linux (although I'm not saying that it doesn't) but I have seen this exact problem on Windows. I think this is likely related.

Tibia answered 19/7, 2010 at 22:3 Comment(0)
C
3

JDK7 will have builtin support for subprocess I/O redirection:

http://download.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html

In the meantime, if you really want to discard stdout/stderr, it seems best (on Linux) to invoke ProcessBuilder on a command that looks like:

["/bin/bash", "-c", "exec YOUR_COMMAND_HERE >/dev/null 2>&1"]
Centesimal answered 11/4, 2011 at 16:3 Comment(0)
C
2

Another solution is to start the process with Redirect.PIPE and close the InputStream like this:

ProcessBuilder builder = new ProcessBuilder(cmd);
builder.redirectOutput(Redirect.PIPE);
builder.redirectErrorStream(true); // redirect the SysErr to SysOut
Process proc = builder.start();
proc.getInputStream().close(); // this will close the pipe and the output will "flow"
proc.waitFor(); //wait

I tested this in Windows and Linux, and works!

Chordophone answered 18/11, 2019 at 20:19 Comment(2)
builder.redirectOutput(Redirect.PIPE); doesn't do anything; PIPE is the default.Syndetic
redirectOutput(ProcessBuilder.Redirect.DISCARD) is also an option. Redirects everything to 'null' so it doesn't need to be read.Shrievalty
A
0

In case you need to capture stdout and stderr and monitor the process then using Apache Commons Exec helped me a lot.

Albertalberta answered 2/8, 2016 at 20:38 Comment(0)
H
0

I believe the problem is the buffering pipe from Linux itself.

Try to use stdbuf with your executable

new ProcessBuilder().command("/usr/bin/stdbuf","-o0","*executable*","*arguments*");**

The -o0 says not to buffer the output. The same goes to -i0 and -e0 if you want to unbuffer the input and error pipe.

Hebraist answered 12/10, 2017 at 17:51 Comment(0)
O
0

you need to read the output before waiting to finish the cycle. You will not be notified If the output doesn't fill the buffer. If it does, it will wait until you read the output.

Suppose you have some errors or responses regarding your command which you are not reading. This would cause the application to stop and waitFor to wait forever. A simple way around is to re-direct the errors to the regular output.

I was spent 2 days on this issue.

public static void exeCuteCommand(String command) {
    try {
        boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
        ProcessBuilder builder = new ProcessBuilder();
        if (isWindows) {
            builder.command("cmd.exe", "/c", command);
        } else {
            builder.command("sh", "-c", command);
        }
        Process process = builder.start();
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line;
        while ((line = reader.readLine()) != null)
            System.out.println("Cmd Response: " + line);
        process.waitFor();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
Otha answered 30/6, 2021 at 11:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.