7

I have a BufferedReader (generated by new BufferedReader(new InputStreamReader(process.getInputStream()))). I'm quite new to the concept of a BufferedReader but as I see it, it has three states:

  1. A line is waiting to be read; calling bufferedReader.readLine will return this string instantly.
  2. The stream is open, but there is no line waiting to be read; calling bufferedReader.readLine will hang the thread until a line becomes available.
  3. The stream is closed; calling bufferedReader.readLine will return null.

Now I want to determine the state of the BufferedReader, so that I can determine whether I can safely read from it without hanging my application. The underlying process (see above) is notoriously unreliable and so might have hung; in this case, I don't want my host application to hang. Therefore I'm implementing a kind of timeout. I tried to do this first with threading but it got horribly complicated.

Calling BufferedReader.ready() will not distinguish between cases (2) and (3) above. In other words, if ready() returns false, it might be that the stream just closed (in other words, my underlying process closed gracefully) or it might be that the underlying process hung.

So my question is: how do I determine which of these three states my BufferedReader is in without actually calling readLine? Unfortunately I can't just call readLine to check this, as it opens my app up to a hang.

I am using JDK version 1.5.

Adam Burley
  • 5,551
  • 4
  • 51
  • 72
  • By the way, I'm assuming for the moment that my underlying process can't hang in the middle of a line....though that may be an erroneous assumption, it hasn't happened so far during testing and I've got enough other problems!! – Adam Burley Jan 23 '10 at 20:19
  • N.B. I'm currently looking for other options than threading. The problem was that my app isn't working at the moment (for a number of reasons including the readLine) and I find threads very difficult to debug, especially if they're on a timeout. – Adam Burley Jan 23 '10 at 20:23

8 Answers8

3

Finally I found a solution to this. Most of the answers here rely on threads, but as I specified earlier, I am looking for a solution which doesn't require threads. However, my basis was the process. What I found was that processes seem to exit if both the output (called "input") and error streams are empty and closed. This makes sense if you think about it.

So I just polled the output and error streams and also tried to determine if the process had exited or not. Below is a rough copy of my solution.

public String readLineWithTimeout(Process process, long timeout) throws IOException, TimeoutException {
  BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
  BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
  boolean finished = false;
  long startTime = 0;
  while (!finished) {
    if (output.ready()) {
      return output.readLine();
    } else if (error.ready()) {
      error.readLine();
    } else {
      try {
        process.exitValue();
        return null;
      } catch (IllegalThreadStateException ex) {
        //Expected behaviour
      }
    }
    if (startTime == 0) {
      startTime = System.currentTimeMills();
    } else if (System.currentTimeMillis() > startTime + timeout) {
      throw new TimeoutException();
    }
  }
}
Adam Burley
  • 5,551
  • 4
  • 51
  • 72
  • 1
    I suspect this may still block as Reader#ready() indicates availability of a single character (or more). I also suspect that there could be cases when all readers report false in ready() but the process's pipe has data to read with a potentially blocking read() system call. – eel ghEEz May 27 '15 at 01:01
  • I know this is answered. But since I got stuck with exactly this situation TWICE. A hint: you need to do a wait before any try to read from a process being excuted. you might just be starting to read when the process was yet fully executed, which means ready() or readLine() might block indefenately... – rubmz Oct 18 '15 at 22:24
2

There is a state where some data may be in the buffer, but not necessarily enough to fill a line. In this case, ready() would return true, but calling readLine() would block.

You should easily be able to build your own ready() and readLine() methods. Your ready() would actually try to build up a line, and only when it has done so successfully would it return true. Then your readLine() could return the fully-formed line.

lavinio
  • 23,931
  • 5
  • 55
  • 71
  • I believe this is the best method unless the extra performance of buffering is needed. – ZoFreX Jan 23 '10 at 23:03
  • How is this different from your solution though ZoFrex? It looks like my custom ready() would have to call some byte-level ready() underneath, which couldn't guarantee whether read() would block or not. Still opens my app up to a possible hang if the underlying process hangs. – Adam Burley Jan 24 '10 at 09:11
  • Looking over the Java source code, I found that BufferedReader#ready() is necessarily conservative as it checks own buffer and the underlying reader such as InputStreamReader. http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/io/BufferedReader.java I guess the latter InputStreamReader will report its ready() status conservatively as well. – eel ghEEz May 27 '15 at 00:34
1

This is a pretty fundamental issue with java's blocking I/O API.

I suspect you're going to want to pick one of:

(1) Re-visit the idea of using threading. This doesn't have to be complicated, done properly, and it would let your code escape a blocked I/O read fairly gracefully, for example:

final BufferedReader reader = ...
ExecutorService executor = // create an executor here, using the Executors factory class.
Callable<String> task = new Callable<String> {
   public String call() throws IOException {
      return reader.readLine();
   }
};
Future<String> futureResult = executor.submit(task);
String line = futureResult.get(timeout);  // throws a TimeoutException if the read doesn't return in time

(2) Use java.nio instead of java.io. This is a more complicated API, but it has non-blocking semantics.

skaffman
  • 398,947
  • 96
  • 818
  • 769
0

Have you confirmed by experiment your assertion that ready() will return false even if the underlying stream is at end of file? Because I would not expect that assertion to be correct (although I haven't done the experiment).

Paul Clapham
  • 1,074
  • 5
  • 6
  • He is unfortunately correct. It's one of the biggest "gotchas" in Java IO, and it makes me sad :( – ZoFreX Jan 23 '10 at 18:23
0

You could use InputStream.available() to see if there is new output from the process. This should work the way you want it if the process outputs only full lines, but it's not really reliable.

A more reliable approach to the problem would be to have a seperate thread dedicated to reading from the process and pushing every line it reads to some queue or consumer.

x4u
  • 13,877
  • 6
  • 48
  • 58
  • Does InputStream.available() distinguish between cases (2) and (3) in my original post any better than ready() does? If so, why do you say it's not reliable? – Adam Burley Jan 23 '10 at 20:09
  • Reader.ready() ultimately uses InputStream.available() to determine whether there is any data available. So it's neither more nor less reliable. The problem is that it is not guaranteed that InputStream.available() will report any available data even when there is some. This is because the stream might itself not be able to find out if there is anything available without blocking. And even if it reports something, you can not be sure that it will end with a linefeed. If it doesn't end with a linefeed, calling readLine on ready()==true would still block. I'd recommend to use a dedicated thread. – x4u Jan 23 '10 at 20:29
  • Thanks, it sounds to me like available() doesn't distinguish between cases (2) and (3) any better than ready() does, so this probably doesn't solve my problem. – Adam Burley Jan 23 '10 at 20:56
0

In general, you have to implement this with multiple threads. There are special cases, like reading from a socket, where the underlying stream has a timeout facility built-in.

However, it shouldn't be horribly complicated to do this with multiple threads. This is a pattern I use:

private static final ExecutorService worker = 
  Executors.newSingleThreadExecutor();

private static class Timeout implements Callable<Void> {
  private final Closeable target;
  private Timeout(Closeable target) {
    this.target = target;
  }
  public Void call() throws Exception {
    target.close();
    return null;
  }
}

...

InputStream stream = process.getInputStream();
Future<?> task = worker.schedule(new Timeout(stream), 5, TimeUnit.SECONDS);
/* Use the stream as you wish. If it hangs for more than 5 seconds, 
   the underlying stream is closed, raising an IOException here. */
...
/* If you get here without timing out, cancel the asynchronous timeout 
  and close the stream explicitly. */
if(task.cancel(false))
  stream.close();
erickson
  • 265,237
  • 58
  • 395
  • 493
0

You could make your own wrapper around InputStream or InputStreamReader that works on a byte-by-byte level, for which ready() returns accurate values.

Your other options are threading which could be done simply (look into some of the concurrent data structures Java offers) and NIO, which is very complex and probably overkill.

ZoFreX
  • 8,812
  • 5
  • 31
  • 51
  • This sounds like my best option (similarly with lavinio's answer), but when you say "ready() returns accurate values", how would ready() distinguish between my cases (2) and (3)? – Adam Burley Jan 23 '10 at 20:10
  • Incidentally, InputStream does not support the ready() method, and InputStreamReader supports it but the javadoc still says "Note that returning false does not guarantee that the next read will block." – Adam Burley Jan 23 '10 at 20:15
  • If it returns true however it does guarantee that the next read will not block. I think. I don't know how to distinguish between cases 2 & 3, and can only suggest testing it! – ZoFreX Jan 23 '10 at 23:02
  • Thanks, but my entire question was about distinguishing between cases 2 and 3. Looks like threading is my only option...sigh... – Adam Burley Jan 24 '10 at 09:09
0

If you just want the timeout then the other methods here are possibly better. If you want a non-blocking buffered reader, here's how I would do it, with threads: (please note I haven't tested this and at the very least it needs some exception handling added)

public class MyReader implements Runnable {
    private final BufferedReader reader;
    private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();
    private boolean closed = false;

    public MyReader(BufferedReader reader) {
        this.reader = reader;
    }

    public void run() {
        String line;
        while((line = reader.readLine()) != null) {
            queue.add(line);
        }
        closed = true;
    }

    // Returns true iff there is at least one line on the queue
    public boolean ready() {
        return(queue.peek() != null);
    }

    // Returns true if the underlying connection has closed
    // Note that there may still be data on the queue!
    public boolean isClosed() {
        return closed;
    }

    // Get next line
    // Returns null if there is none
    // Never blocks
    public String readLine() {
        return(queue.poll());
    }
}

Here's how to use it:

BufferedReader b; // Initialise however you normally do
MyReader reader = new MyReader(b);
new Thread(reader).start();

// True if there is data to be read regardless of connection state
reader.ready();

// True if the connection is closed
reader.closed();

// Gets the next line, never blocks
// Returns null if there is no data
// This doesn't necessarily mean the connection is closed, it might be waiting!
String line = reader.readLine(); // Gets the next line

There are four possible states:

  1. Connection is open, no data is available
  2. Connection is open, data is available
  3. Connection is closed, data is available
  4. Connection is closed, no data is available

You can distinguish between them with the isClosed() and ready() methods.

ZoFreX
  • 8,812
  • 5
  • 31
  • 51