jeffery.yuan
jeffery.yuan

Reputation: 1255

Main program hang when read output stream of processB which starts another processC that runs forever

There are three Java applications, appB just starts appC which would run forever. appA starts appB and read its output. But appA never exit. Anyone has idea why this happen and how to resolve it. It doesn't hang if I don't read the stream in appA.

I have tried two ways:

  1. Read output stream(readViaInputStream) or BufferedReader(readViaBufferedReader) directly.

They doesn't work, the output would be:

// if we call them, the main app hangs, the output would be:

// Before call child process

// After call child process

// the main program hangs here.

// Never output - "Read stream finished."

In readViaInputStream, it hangs at fill() method in BufferedInputStream.fill() method at int n = getInIfOpen().read(buffer, pos, buffer.length - pos); It calls the native method of FileInputStream class.

Same to readViaBufferedReader method.

  1. Use another thread to read output stream.

It doesn't work too, the output would be:

// Before call child process

// After call child process

// Read stream finished.

// ===> and the main program hang

Thanks very much for any reply : )

The code is as below - updated to use code Guillaume Polet provided in next comment.

public class MainApp {

public static enum APP {
    B, C;
}

public static void main(String[] args) throws Exception,
        InterruptedException {
    if (args.length > 0) {
        APP app = APP.valueOf(args[0]);
        switch (app) {
        case B:
            performB();
            break;
        case C:
            performC();
            break;
        }
        return;
    }
    performA();
}

private static void performA() throws Exception {
    String javaBin = "java";
    String[] cmdArray = { javaBin, "-cp",
            System.getProperty("java.class.path"), MainApp.class.getName(),
            APP.B.name() };
    ProcessBuilder builder = new ProcessBuilder(cmdArray);
    builder.redirectErrorStream(true);
    final Process process = builder.start();

    process.getOutputStream().close();
    process.getErrorStream().close();

    // if we call this, the main app hangs, the output would be:
    // Before call child process
    // After call child process
    // the main program hangs here.
    // Never output - "Read stream finished."
    readViaInputStream(process);

    // if we call this, the main app hangs, the output would be:
    // Before call child process
    // After call child process
    // the main program hangs here.
    // Never output - "Read stream finished."

    // readViaBufferedReader(process);

    // if we call this, the main app still hangs, the output would be:
    // Before call child process
    // After call child process
    // Read stream finished.
    // ===> and the main program hang
    // readOutputViaAnotherThread(process);

    System.err.println("Read stream finished."); // never come here
}

private static void performB() throws Exception {
    System.out.println("Before call child process");
    String javaBin = "java";
    String[] cmdArray = { javaBin, "-cp",
            System.getProperty("java.class.path"), MainApp.class.getName(),
            APP.C.name() };
    ProcessBuilder builder = new ProcessBuilder(cmdArray);
    Process process = builder.start();

    process.getInputStream().close();
    process.getOutputStream().close();
    process.getErrorStream().close();

    System.out.println("After call child process");
    System.exit(0); // no difference with or without this line.
}

private static void performC() throws Exception {
    Thread thread = new Thread() {
        @Override
        public void run() {
            int i = 0;
            while (true) {
                try {
                    Thread.sleep(60 * 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.err.println("child " + ++i);
            }
        }
    };
    thread.start();
    thread.join();

}

private static void readViaInputStream(final Process process)
        throws Exception {

    // System.err.println("exitValue: " + process.waitFor());
    InputStream is = process.getInputStream();
    int result;

    while ((result = is.read()) != -1) {
        System.err.println(result);
    }
}

private static void readViaBufferedReader(final Process process)
        throws Exception {
    BufferedReader in = new BufferedReader(new InputStreamReader(
            process.getInputStream(), "utf-8"));
    String result = "";
    while ((result = in.readLine()) != null) {
        System.err.println(result);
    }
}

private static void readOutputViaAnotherThread(final Process process)
        throws Exception {
    class ReadOutputStreamThread extends Thread {
        public void run() {

            running = true;
            try {
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(process.getInputStream(),
                                "utf-8"));
                String result = "";
                while (running && (result = in.readLine()) != null) {
                    System.err.println(result);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        };

        private volatile boolean running;

        public void shutdown() throws IOException {
            running = false;
            // this has no impact
            process.getInputStream().close();
            interrupt();
        }
    }

    ReadOutputStreamThread readOutputThread = new ReadOutputStreamThread();
    // if we set this readOutputThread as daemon, it works, but the thread
    // will remains run forever.
    // readOutputThread.setDaemon(true);
    readOutputThread.start();

    System.err.println("exitValue: " + process.waitFor());
    readOutputThread.shutdown();
}

}

Upvotes: 4

Views: 6076

Answers (2)

Fabian Barney
Fabian Barney

Reputation: 14559

When your program hangs in BufferedReader.readLine() then this is most probably because the stream does not end with end-of-line character.

There are two ways having readLine() to continue and return:

  • there is a EOL character and readLine() returns the line before that character
  • the stream gets closed and readLine() returns null

In your case none of these 2 possibilities is true and there is no way for BufferedReader.readLine() to decide if there will come more characters in over the stream with a EOL-character later or end reached. So it blocks and looks forward what will happend. It expects more characters coming in and it will return only when the next EOL-char comes in or the underlying stream gets closed.

Try one of the following:

  • do not use readLine() but a byte-wise read-method
  • ensure Process will produce output that ends with EOL-character

Also be aware that you've to close all std-Streams on your own - even when not used.

Upvotes: 6

Guillaume Polet
Guillaume Polet

Reputation: 47627

EDIT: Ok found a solution: You have to move the inputstream reading in another Thread and use process.waitFor() to find out when B exits. It seems that you never get an EOF from the inpustream of the sub-process and therefore reading the input stream never ends.

Sorry this is not an answer, only an SSCCE that can be copy-pasted and started without any further changes and that reproduces your issue:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

public class Test5 {

    public static enum APP {
        B, C;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        if (args.length > 0) {
            APP app = APP.valueOf(args[0]);
            switch (app) {
            case B:
                performB();
                break;
            case C:
                performC();
                break;
            }
            return;
        }
        performA();

    }

    private static void performC() throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run() {
                int i = 0;
                while (true) {
                    try {
                        Thread.sleep(60 * 2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.err.println("child " + ++i);
                }
            }
        };
        thread.start();
        thread.join();

    }

    private static void performB() throws IOException {
        System.out.println("Before call child process");
        String javaBin = "java";
        String[] cmdArray = { javaBin, "-cp", System.getProperty("java.class.path"), Test5.class.getName(), APP.C.name() };
        ProcessBuilder builder = new ProcessBuilder(cmdArray);
        // Process process =
        builder.start();
        System.out.println("After call child process");
        System.exit(0); // no differnce with or without this line.

    }

    private static void performA() throws IOException, UnsupportedEncodingException {
        String javaBin = "java";
        String[] cmdArray = { javaBin, "-cp", System.getProperty("java.class.path"), Test5.class.getName(), APP.B.name() };
        ProcessBuilder builder = new ProcessBuilder(cmdArray);
        builder.redirectErrorStream(true);
        Process process = builder.start();
        BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"));
        String result = "";
        while ((result = in.readLine()) != null) {
            System.err.println(result);
        }
        System.err.println("Read stream finished."); // never come here
    }
}

Upvotes: 1

Related Questions