4

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

}

jeffery.yuan
  • 1,177
  • 1
  • 17
  • 27
  • Did you read the javadoc for thread.join? – Scorpion Feb 16 '12 at 11:04
  • Yes, the child process main thread seems to wait for ever. The join call was not needed since the spawned thread is not daemon. According to the question, the problem is not in appC but in appA, which reads from an input stream forever. This happens because the other part of the stream doesn't close it. – Mister Smith Feb 16 '12 at 12:04
  • Thanks very much for the reply. The code I provided is from real project, the process C is meant to be run forever. - the only difference is that in my real project a C# application is starting application B and read its output-same probelm too. After debug the jdk source code, it looks like it hangs at BufferedReader.readLine() which calls fill() method in BufferedReader and finally calls sun.nio.cs.StreamDecoder.read() method. private void fill() throws IOException { do {n = in.read(cb, dst, cb.length - dst); // it hangs here } while (n == 0);} I am not sure whether this is a JDK bug or not. – jeffery.yuan Feb 17 '12 at 06:49

2 Answers2

6

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.

Community
  • 1
  • 1
Fabian Barney
  • 14,219
  • 5
  • 40
  • 60
  • Barney, Thanks very much for the reply. I trace the code, it seems not related with readLine - I tried to use byte-wise read or even use a different thread to read the stream, none works. - please see the updated code. – jeffery.yuan Feb 20 '12 at 11:01
  • Thanks.. mine was stuck at readline and i missed closing an output stream. This worked for me .. :) – Harinder Jul 28 '14 at 12:20
1

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
    }
}
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • Hi, Guillaume Polet: Thanks very much for the reply. The code you provided is much better, cleaner£, also easier to run. The solution you provided works - in main program start a new thread(ReadOutputStreamThread) to read the outputstream, call process.waitFor(), after child process exists, stop the ReadOutputStreamThread. But I am thinking why the old code doesn't work, trying to debug JDK and figure out why this happens. – jeffery.yuan Feb 17 '12 at 06:46
  • Sorry, but seems using another thread to read output will not work, unless when set that thread as daemon. – jeffery.yuan Feb 17 '12 at 10:49
  • set that thread as daemon is unacceptable as it will leave the thread run forever. I guess this is because the readline will hang - even close the stream doesn't work. – jeffery.yuan Feb 17 '12 at 11:17