173

Specifically, the problem is to write a method like this:

int maybeRead(InputStream in, long timeout)

where the return value is the same as in.read() if data is available within 'timeout' milliseconds, and -2 otherwise. Before the method returns, any spawned threads must exit.

To avoid arguments, the subject here java.io.InputStream, as documented by Sun (any Java version). Please note this is not as simple as it looks. Below are some facts which are supported directly by Sun's documentation.

  1. The in.read() method may be non-interruptible.

  2. Wrapping the InputStream in a Reader or InterruptibleChannel doesn't help, because all those classes can do is call methods of the InputStream. If it were possible to use those classes, it would be possible to write a solution that just executes the same logic directly on the InputStream.

  3. It is always acceptable for in.available() to return 0.

  4. The in.close() method may block or do nothing.

  5. There is no general way to kill another thread.

Gray
  • 115,027
  • 24
  • 293
  • 354

9 Answers9

93

Using inputStream.available()

It is always acceptable for System.in.available() to return 0.

I've found the opposite - it always returns the best value for the number of bytes available. Javadoc for InputStream.available():

Returns an estimate of the number of bytes that can be read (or skipped over) 
from this input stream without blocking by the next invocation of a method for 
this input stream.

An estimate is unavoidable due to timing/staleness. The figure can be a one-off underestimate because new data are constantly arriving. However it always "catches up" on the next call - it should account for all arrived data, bar that arriving just at the moment of the new call. Permanently returning 0 when there are data fails the condition above.

First Caveat: Concrete subclasses of InputStream are responsible for available()

InputStream is an abstract class. It has no data source. It's meaningless for it to have available data. Hence, javadoc for available() also states:

The available method for class InputStream always returns 0.

This method should be overridden by subclasses.

And indeed, the concrete input stream classes do override available(), providing meaningful values, not constant 0s.

Second Caveat: Ensure you use carriage-return when typing input in Windows.

If using System.in, your program only receives input when your command shell hands it over. If you're using file redirection/pipes (e.g. somefile > java myJavaApp or somecommand | java myJavaApp ), then input data are usually handed over immediately. However, if you manually type input, then data handover can be delayed. E.g. With windows cmd.exe shell, the data are buffered within cmd.exe shell. Data are only passed to the executing java program following carriage-return (control-m or <enter>). That's a limitation of the execution environment. Of course, InputStream.available() will return 0 for as long as the shell buffers the data - that's correct behaviour; there are no available data at that point. As soon as the data are available from the shell, the method returns a value > 0. NB: Cygwin uses cmd.exe too.

Simplest solution (no blocking, so no timeout required)

Just use this:

    byte[] inputData = new byte[1024];
    int result = is.read(inputData, 0, is.available());  
    // result will indicate number of bytes read; -1 for EOF with no data read.

OR equivalently,

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in, Charset.forName("ISO-8859-1")),1024);
    // ...
         // inside some iteration / processing logic:
         if (br.ready()) {
             int readCount = br.read(inputData, bufferOffset, inputData.length-bufferOffset);
         }

Richer Solution (maximally fills buffer within timeout period)

Declare this:

public static int readInputStreamWithTimeout(InputStream is, byte[] b, int timeoutMillis)
     throws IOException  {
     int bufferOffset = 0;
     long maxTimeMillis = System.currentTimeMillis() + timeoutMillis;
     while (System.currentTimeMillis() < maxTimeMillis && bufferOffset < b.length) {
         int readLength = java.lang.Math.min(is.available(),b.length-bufferOffset);
         // can alternatively use bufferedReader, guarded by isReady():
         int readResult = is.read(b, bufferOffset, readLength);
         if (readResult == -1) break;
         bufferOffset += readResult;
     }
     return bufferOffset;
 }

Then use this:

    byte[] inputData = new byte[1024];
    int readCount = readInputStreamWithTimeout(System.in, inputData, 6000);  // 6 second timeout
    // readCount will indicate number of bytes read; -1 for EOF with no data read.
Glen Best
  • 22,769
  • 3
  • 58
  • 74
  • 4
    If `is.available() > 1024` this suggestion will fail. There are certainly streams which do return zero. SSLSockets for example until recently. You can't rely on this. – user207421 Aug 16 '15 at 01:25
  • The case 'is.available() > 1024' is specifically dealt with via readLength. – Glen Best Sep 11 '15 at 04:20
  • Comment re SSLSockets incorrect - it returns 0 for available iff there's no data in the buffer. As per my answer. Javadoc: "If there are no bytes buffered on the socket, and the socket has not been closed using close, then available will return 0. " – Glen Best Sep 11 '15 at 04:35
  • 1
    @GlenBest My comment re SSLSocket is not incorrect. *Until recently* [my emphasis] it used to return zero at all times. You're talking about the present. I'm talking about the entire history of JSSE, and I've worked with since before it was first included in [Java 1.4 in 2002](http://www.oracle.com/technetwork/java/javase/codenames-136090.html). – user207421 Dec 16 '15 at 04:55
  • By changing the while loop conditions to "while (is.available() > 0 && System.currentTimeMillis() < maxTimeMillis && bufferOffset < b.length) {" saved me a ton of CPU overhead. – Logic1 May 07 '16 at 07:14
  • @EJP Is it your assessment that "until recently" the input stream from an SSLSocket violated the contract of available()? Or was the contract for InputStream changed and SSLSocket was updated accordingly? – Teto Sep 09 '16 at 02:16
  • @Teto It is my *experience* over many years that `SSLSocket.getInputStream().available()` returned zero; **nowhere** have I asserted that this 'violated the contract of `available()`'; nowhere have I asserted that 'the contract for `InputStream` changed': these are all your assertions; not mine. – user207421 Oct 20 '16 at 09:40
  • @EJP. I said "assessment" not "assertion". But let me rephrase. Glen Best's answer explains why "Permanently returning zero" would violate the contract (JavaDoc) of any implementation of available() unless the inputstream could never receive input. But you stated that "until recently" the input stream returned by SSLSocket is would "return zero at all times". This violates the current contract. Was the contract for available() different back? Or was SSLSocket returning an InputStream that violates the contract? Also, do you happen to know in which version of Java this changed? Thanks – Teto Nov 05 '16 at 02:39
  • available() is a blocking unreliable method as stated in the docs – Matei Suica Jan 26 '17 at 13:24
  • While I did like this answer (and use a slightly modified version), a problem with this is that the input only appears in the console (cmd.exe) after pressing the Enter key. – No. 7892142 Sep 09 '17 at 11:23
  • 1
    This is actually not a good answer. 1) as already stated, available() may return 0, depending on JVM, version, OS, implementations. 2) If you are trying to access erroneous files, any read() call may never return (or at least not within a decent timeout, some are 10 minutes). So using this solution is a bad idea. Ian Jones' answer is much better and better readable if written properly. – JayC667 May 30 '19 at 15:08
  • I realize hat for practical purposes it may not happen, but the timeout check should not use less-than like that. It doesn't handle the case where adding the timeout offset wraps the long. Better to do the subtraction, (current - start) and check the difference hasn't exceeded the timeout. – swpalmer Apr 12 '22 at 14:26
  • This suggestion is not "blocking". It is busy-wait / spinlock. – Daniel Chin Aug 02 '23 at 10:05
73

Assuming your stream is not backed by a socket (so you can't use Socket.setSoTimeout()), I think the standard way of solving this type of problem is to use a Future.

Suppose I have the following executor and streams:

    ExecutorService executor = Executors.newFixedThreadPool(2);
    final PipedOutputStream outputStream = new PipedOutputStream();
    final PipedInputStream inputStream = new PipedInputStream(outputStream);

I have writer that writes some data then waits for 5 seconds before writing the last piece of data and closing the stream:

    Runnable writeTask = new Runnable() {
        @Override
        public void run() {
            try {
                outputStream.write(1);
                outputStream.write(2);
                Thread.sleep(5000);
                outputStream.write(3);
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    executor.submit(writeTask);

The normal way of reading this is as follows. The read will block indefinitely for data and so this completes in 5s:

    long start = currentTimeMillis();
    int readByte = 1;
    // Read data without timeout
    while (readByte >= 0) {
        readByte = inputStream.read();
        if (readByte >= 0)
            System.out.println("Read: " + readByte);
    }
    System.out.println("Complete in " + (currentTimeMillis() - start) + "ms");

which outputs:

Read: 1
Read: 2
Read: 3
Complete in 5001ms

If there was a more fundamental problem, like the writer not responding, the reader would block for ever. If I wrap the read in a future, I can then control the timeout as follows:

    int readByte = 1;
    // Read data with timeout
    Callable<Integer> readTask = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            return inputStream.read();
        }
    };
    while (readByte >= 0) {
        Future<Integer> future = executor.submit(readTask);
        readByte = future.get(1000, TimeUnit.MILLISECONDS);
        if (readByte >= 0)
            System.out.println("Read: " + readByte);
    }

which outputs:

Read: 1
Read: 2
Exception in thread "main" java.util.concurrent.TimeoutException
    at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
    at java.util.concurrent.FutureTask.get(FutureTask.java:91)
    at test.InputStreamWithTimeoutTest.main(InputStreamWithTimeoutTest.java:74)

I can catch the TimeoutException and do whatever cleanup I want.

Ian Jones
  • 1,998
  • 1
  • 17
  • 17
  • 17
    But what about the blocking thread ?! Will it stay in the memory till the application terminates ? If I'm correct, this may produce endless threads the application is heavy loaded and even more, block further threads from using your pool which has it's threads occupied and blocked. Please correct me if I'm wrong. Thank you. – Muhammad Gelbana Sep 25 '12 at 19:37
  • 4
    Muhammad Gelbana, you are right: the blocking read() thread stays running and that is not OK. I have found a way to prevent this though: when the timeout hits, close from the calling thread the input stream (in my case I close the android bluetooth socket from which the input stream comes). When you do that, the read() call will return immediately.. Well in my case I use the int read(byte[]) overload, and that one returns immediately. Maybe the int read() overload would throw an IOException since I don't know what it would return... To my mind that is the proper solution. – Emmanuel Touzery Nov 08 '12 at 11:09
  • 5
    -1 as the threads reading stay blocked until the application terminates. – Ortwin Angermeier Aug 22 '13 at 08:26
  • 11
    @ortang That's kind of what I meant by "catch the TimeoutException and do whatever cleanup..." For example I might want to kill the reading thread: ... catch (TimeoutException e) { executor.shutdownNow(); } – Ian Jones Aug 27 '13 at 09:05
  • 13
    `executer.shutdownNow` will not kill the thread. It will try to interrupt it, with no effect. There is no cleanup possible and this is a serious issue. – Marko Topolnik Sep 16 '14 at 08:07
  • If you want to kill the thread, the best thing to do is to close the input stream after catching the TimeoutException. – AbuZubair May 06 '15 at 22:27
  • Should use buffers for I/O - java hands this task to the O/S for high performance. Java threads managing byte-by-byte are inefficient. If you apply buffering, your method doesn't handle this scenario: i) buffer empty ii) 2 second timeout iii) 50% of the buffer full within the 2 sec iv) want to read & return the 50% & not wait for 100% buffer fill. i.e. We'd always have a loop around calls to maybeRead(InputStream in, long timeout). – Glen Best Sep 11 '15 at 04:50
  • You can make field with buffer and fill it. If Timeout occur you can read any info from buffer if it present here. See code of `readLine()` and make similar code but inside `call()` of `Future`. – Enyby Oct 22 '16 at 19:44
  • This suggestion is also not "blocking". It is busy-wait / spinlock. – Daniel Chin Aug 02 '23 at 10:07
27

If your InputStream is backed by a Socket, you can set a Socket timeout (in milliseconds) using setSoTimeout. If the read() call doesn't unblock within the timeout specified, it will throw a SocketTimeoutException.

Just make sure that you call setSoTimeout on the Socket before making the read() call.

AlexB
  • 473
  • 2
  • 7
  • 16
Templar
  • 5,067
  • 7
  • 34
  • 39
18

I would question the problem statement rather than just accept it blindly. You only need timeouts from the console or over the network. If the latter you have Socket.setSoTimeout() and HttpURLConnection.setReadTimeout() which both do exactly what is required, as long as you set them up correctly when you construct/acquire them. Leaving it to an arbitrary point later in the application when all you have is the InputStream is poor design leading to a very awkward implementation.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • 10
    There are other situations where a read could potentially block for a significant time; e.g. when reading from a tape drive, from a remotely mounted network drive or from an HFS with a tape robot at the back end. (But the main thrust of your answer is right.) – Stephen C Oct 24 '11 at 06:20
  • 1
    @StephenC +1 for your comment and examples. To add more your example, a simple case could be where socket connections was made correctly but read attempt was blocked as the data was to be fetched from DB but it somehow didn't happen (lets' say DB was not responding to and query went in Locked state). In this scenario you need to have a way to explicitly timeout the read operation on socket. – sactiw May 13 '13 at 10:30
  • 1
    The whole point of the InputStream abstraction is to not think about the underlying implementation. Its fair to argue about the pros and cons of the posted answers. But, to question the problem statement, is not going to help the disussion – pellucide May 12 '16 at 02:57
  • @pellucide The whole point of the `InputStream` is that it doesn't have a timeout method, and neither do most of the underlying implementations. `Sockets` are a prominent exception and the only one I can think of off-hand. Ignoring facts is not going to help the discussion. – user207421 May 12 '16 at 04:12
  • 3
    InputStream works on a stream and it blocks, yet it does not provide a timeout mechanism. So the InputStream abstraction is not an aptly designed abstraction. Hence asking for a way to timeout on a stream isn't asking for much. So the question is asking for a solution to a very practical problem. Most of the underlying implementations will block. Thats the very essence of a stream. Sockets, Files, Pipes will block if the other side of the stream isn't ready with new data. – pellucide May 12 '16 at 04:33
  • @pellucide So you agree with me in questioning the problem statement. – user207421 May 28 '16 at 00:41
  • 2
    @EJP. I dont know how you got that. I didn't agree with you. The problem statement "how to timeout on a InputStream" is valid. Since the framework doesn't provide a way to timeout, it is appropriate to ask such a question. – pellucide May 31 '16 at 15:16
  • 1
    This is a terrible answer. There's no need to 'question the problem statement'. As an example: The Android BluetoothSocket has no way to set a timeout and only presents an InputStream over its API. It'll happily block forever on a read(). But according to this answer every App is "poorly designed". (I'd happily agree that the Android BT framework is poorly designed, however) – Pod Mar 23 '17 at 12:25
  • @Pod That is a terrible comment. You have completely misread the answer. Any application that leaves setting the read timeout to an arbitrary point in application where all it has is an input stream is poorly designed. As there are no such applications except for this one, because they can't be written that way, that means exactly one application. The timeout should indeed be set on the socket, not the input stream, and that is what it says in my answer, and what is wrong with the question, and the problem statement. – user207421 Mar 23 '17 at 23:04
  • @EJP what if the setSoTimeout is insufficient, which seems to be the case in this question: https://stackoverflow.com/questions/50721594/modify-so-timeout-between-consecutive-reads – bvdb Jun 06 '18 at 14:38
  • @bvdb The question doesn't really mean anything as stated. If `setSoTimeout()` isn't sufficient, what would be? And NB your assertion in that question that it doesn't work for client sockets is not correct. – user207421 Jun 12 '18 at 07:13
  • That you need only timeouts when reading from the console or sockets is not true. There are situations where you need timeouts when reading from pipes or followed files. – Christian Hujer Nov 23 '18 at 07:10
6

I have not used the classes from the Java NIO package, but it seems they might be of some help here. Specifically, java.nio.channels.Channels and java.nio.channels.InterruptibleChannel.

jt.
  • 7,625
  • 4
  • 27
  • 24
  • 2
    +1: I don't believe that there is a reliable way to do what the OP is asking for with InputStream alone. However, nio was created for this purpose, among others. – Eddie Apr 30 '09 at 02:19
  • 3
    OP has already basically ruled this out. InputStreams are inherently blocking and may be non-interruptible. – user207421 Jun 27 '12 at 12:43
5

Here is a way to get a NIO FileChannel from System.in and check for availability of data using a timeout, which is a special case of the problem described in the question. Run it at the console, don't type any input, and wait for the results. It was tested successfully under Java 6 on Windows and Linux.

import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;

public class Main {

    static final ByteBuffer buf = ByteBuffer.allocate(4096);

    public static void main(String[] args) {

        long timeout = 1000 * 5;

        try {
            InputStream in = extract(System.in);
            if (! (in instanceof FileInputStream))
                throw new RuntimeException(
                        "Could not extract a FileInputStream from STDIN.");

            try {
                int ret = maybeAvailable((FileInputStream)in, timeout);
                System.out.println(
                        Integer.toString(ret) + " bytes were read.");

            } finally {
                in.close();
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /* unravels all layers of FilterInputStream wrappers to get to the
     * core InputStream
     */
    public static InputStream extract(InputStream in)
            throws NoSuchFieldException, IllegalAccessException {

        Field f = FilterInputStream.class.getDeclaredField("in");
        f.setAccessible(true);

        while( in instanceof FilterInputStream )
            in = (InputStream)f.get((FilterInputStream)in);

        return in;
    }

    /* Returns the number of bytes which could be read from the stream,
     * timing out after the specified number of milliseconds.
     * Returns 0 on timeout (because no bytes could be read)
     * and -1 for end of stream.
     */
    public static int maybeAvailable(final FileInputStream in, long timeout)
            throws IOException, InterruptedException {

        final int[] dataReady = {0};
        final IOException[] maybeException = {null};
        final Thread reader = new Thread() {
            public void run() {                
                try {
                    dataReady[0] = in.getChannel().read(buf);
                } catch (ClosedByInterruptException e) {
                    System.err.println("Reader interrupted.");
                } catch (IOException e) {
                    maybeException[0] = e;
                }
            }
        };

        Thread interruptor = new Thread() {
            public void run() {
                reader.interrupt();
            }
        };

        reader.start();
        for(;;) {

            reader.join(timeout);
            if (!reader.isAlive())
                break;

            interruptor.start();
            interruptor.join(1000);
            reader.join(1000);
            if (!reader.isAlive())
                break;

            System.err.println("We're hung");
            System.exit(1);
        }

        if ( maybeException[0] != null )
            throw maybeException[0];

        return dataReady[0];
    }
}

Interestingly, when running the program inside NetBeans 6.5 rather than at the console, the timeout doesn't work at all, and the call to System.exit() is actually necessary to kill the zombie threads. What happens is that the interruptor thread blocks (!) on the call to reader.interrupt(). Another test program (not shown here) additionally tries to close the channel, but that doesn't work either.

  • doesn't work on mac os, neither with JDK 1.6 nor with JDK 1.7. The interrupt is only recognized after pressing return during the read. –  Mar 09 '12 at 00:35
4

As jt said, NIO is the best (and correct) solution. If you really are stuck with an InputStream though, you could either

  1. Spawn a thread who's exclusive job is to read from the InputStream and put the result into a buffer which can be read from your original thread without blocking. This should work well if you only ever have one instance of the stream. Otherwise you may be able to kill the thread using the deprecated methods in the Thread class, though this may cause resource leaks.

  2. Rely on isAvailable to indicate data that can be read without blocking. However in some cases (such as with Sockets) it can take a potentially blocking read for isAvailable to report something other than 0.

  • 6
    `Socket.setSoTimeout()` is an equally correct and much simpler solution. Or `HttpURLConnection.setReadTimeout()`. – user207421 Oct 19 '11 at 23:08
  • 4
    @EJP - these are only "equally correct" under certain circumstances; e.g. if the input stream is a socket stream / HTTP connection stream. – Stephen C Oct 24 '11 at 06:16
  • 2
    @Stephen C NIO is only non-blocking and selectable under the same circumstances. There is no non-blocking file I/O for example. – user207421 Oct 24 '11 at 07:02
  • 2
    @EJP but there's non-blocking pipe IO (System.in), non-blocking I/O for files (on local disk) is nonsense – woky Jan 09 '12 at 11:53
  • @woky System.in is neither non-blocking nor a pipe. I agree with your last sentence, which is why I said it. – user207421 Feb 04 '12 at 09:09
  • 1
    @EJP On most (all?) Unices System.in is actually a pipe (if you didn't tell shell to replace it with file) and as a pipe it can be non-blocking. – woky Feb 07 '12 at 21:07
  • @woky System.in is a Java `InputStream,` which is neither non-blocking nor a pipe. If fd 0 happens to be a pipe it can be obtained via System.inheritedChannel() but that has nothing to do with System.in. – user207421 Feb 08 '12 at 09:49
  • 1
    @EJP Arguing what InputStream isn't is plain BS since it's abstract superclass. InputStream actually _MAY_ be a pipe (hence non-blocking) and 99% of time System.in is a pipe. I can make JNI code which gets fd from System.in and do non-blocking IO without problem. But I can't do it through Java API (inheritedChanel() doesn't return SelectableChannel). Blame NIO makers. And by the way, there is no such thing as C NIO ;). – woky Feb 08 '12 at 17:05
  • @woky You're just wasting time. (1) An InputStream cannot be non-blocking, in any circumstances, regardless of what concrete class is extending it. (2) The standard input is mostly a terminal, sometimes a file, sometimes a pipe. 99% for the latter is wildly incorrect. (3) C NIO is a red herring introduced by you, as is (4) the claim that System.in is non-blocking pipe I/O, which remains false. – user207421 Feb 10 '12 at 03:02
  • 1
    @woky More timewasting. (1) and (2): socket channels and terminals can indeed be put into non-blocking mode , and (1) can be done in Java: as I never said otherwise, your point eludes me. (3) 'Stephen C NIO' contains part of Stephen C's name, the poster I was responding to. The point of your (4) also escapes me as it doesn't contradict anything I have said. As for the rest, you still need to stop confusing the inherited Unix fd=0, an FD, with System.in, an InputStream, which cannot be put into non-blocking mode. Period. Line 264 of the code you cite uses an FD not an InputStream. – user207421 Feb 13 '12 at 09:37
0

Inspired in this answer I came up with a bit more object-oriented solution.

This is only valid if you're intending to read characters

You can override BufferedReader and implement something like this:

public class SafeBufferedReader extends BufferedReader{

    private long millisTimeout;

    ( . . . )

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return 0;
        }
        return super.read(cbuf, off, len);
    }

    protected void waitReady() throws IllegalThreadStateException, IOException {
        if(ready()) return;
        long timeout = System.currentTimeMillis() + millisTimeout;
        while(System.currentTimeMillis() < timeout) {
            if(ready()) return;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                break; // Should restore flag
            }
        }
        if(ready()) return; // Just in case.
        throw new IllegalThreadStateException("Read timed out");
    }
}

Here's an almost complete example.

I'm returning 0 on some methods, you should change it to -2 to meet your needs, but I think that 0 is more suitable with BufferedReader contract. Nothing wrong happened, it just read 0 chars. readLine method is a horrible performance killer. You should create a entirely new BufferedReader if you actually want to use readLine. Right now, it is not thread safe. If someone invokes an operation while readLines is waiting for a line, it will produce unexpected results

I don't like returning -2 where I am. I'd throw an exception because some people may just be checking if int < 0 to consider EOS. Anyway, those methods claim that "can't block", you should check if that statement is actually true and just don't override'em.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.CharBuffer;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

/**
 * 
 * readLine
 * 
 * @author Dario
 *
 */
public class SafeBufferedReader extends BufferedReader{

    private long millisTimeout;

    private long millisInterval = 100;

    private int lookAheadLine;

    public SafeBufferedReader(Reader in, int sz, long millisTimeout) {
        super(in, sz);
        this.millisTimeout = millisTimeout;
    }

    public SafeBufferedReader(Reader in, long millisTimeout) {
        super(in);
        this.millisTimeout = millisTimeout;
    }



    /**
     * This is probably going to kill readLine performance. You should study BufferedReader and completly override the method.
     * 
     * It should mark the position, then perform its normal operation in a nonblocking way, and if it reaches the timeout then reset position and throw IllegalThreadStateException
     * 
     */
    @Override
    public String readLine() throws IOException {
        try {
            waitReadyLine();
        } catch(IllegalThreadStateException e) {
            //return null; //Null usually means EOS here, so we can't.
            throw e;
        }
        return super.readLine();
    }

    @Override
    public int read() throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return -2; // I'd throw a runtime here, as some people may just be checking if int < 0 to consider EOS
        }
        return super.read();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return -2;  // I'd throw a runtime here, as some people may just be checking if int < 0 to consider EOS
        }
        return super.read(cbuf);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return 0;
        }
        return super.read(cbuf, off, len);
    }

    @Override
    public int read(CharBuffer target) throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return 0;
        }
        return super.read(target);
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        super.mark(readAheadLimit);
    }

    @Override
    public Stream<String> lines() {
        return super.lines();
    }

    @Override
    public void reset() throws IOException {
        super.reset();
    }

    @Override
    public long skip(long n) throws IOException {
        return super.skip(n);
    }

    public long getMillisTimeout() {
        return millisTimeout;
    }

    public void setMillisTimeout(long millisTimeout) {
        this.millisTimeout = millisTimeout;
    }

    public void setTimeout(long timeout, TimeUnit unit) {
        this.millisTimeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
    }

    public long getMillisInterval() {
        return millisInterval;
    }

    public void setMillisInterval(long millisInterval) {
        this.millisInterval = millisInterval;
    }

    public void setInterval(long time, TimeUnit unit) {
        this.millisInterval = TimeUnit.MILLISECONDS.convert(time, unit);
    }

    /**
     * This is actually forcing us to read the buffer twice in order to determine a line is actually ready.
     * 
     * @throws IllegalThreadStateException
     * @throws IOException
     */
    protected void waitReadyLine() throws IllegalThreadStateException, IOException {
        long timeout = System.currentTimeMillis() + millisTimeout;
        waitReady();

        super.mark(lookAheadLine);
        try {
            while(System.currentTimeMillis() < timeout) {
                while(ready()) {
                    int charInt = super.read();
                    if(charInt==-1) return; // EOS reached
                    char character = (char) charInt;
                    if(character == '\n' || character == '\r' ) return;
                }
                try {
                    Thread.sleep(millisInterval);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // Restore flag
                    break;
                }
            }
        } finally {
            super.reset();
        }
        throw new IllegalThreadStateException("readLine timed out");

    }

    protected void waitReady() throws IllegalThreadStateException, IOException {
        if(ready()) return;
        long timeout = System.currentTimeMillis() + millisTimeout;
        while(System.currentTimeMillis() < timeout) {
            if(ready()) return;
            try {
                Thread.sleep(millisInterval);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // Restore flag
                break;
            }
        }
        if(ready()) return; // Just in case.
        throw new IllegalThreadStateException("read timed out");
    }

}
DGoiko
  • 346
  • 2
  • 19
0

There is no way to properly block an InputStream, unless it's created by a Socket.

1. Never busy-wait

"Busy-waiting" refers to using CPU-heavy while loops to poll an IO resource. Never busy-wait. The proper way is to mark the current thread as "blocked" and allow the OS to elect other threads to run. When the IO resource becomes available OR the timeout expires, the OS is in charge of marking your thread as "pending" and electing it again. Java's builtin read() implementations do just that.

Consequences of busy-waiting:

  • If you have one busy-waiter, the CPU will be running when it's supposed to be idling.
  • If you have multiple busy-waiters, they will starve one another and introduce latencies in the application up to the OS quantum/tick time (usually 10ms-100ms). CPU effective utilization rate also drops, and can approach 0% depending on the situation.
  • If your busy-waiters have thread priorities higher than other real-time threads do, they even starve external applications and introduce latencies.

InputStream.read() is an IO call. It should not occupy any CPU(s) while waiting for a byte / a timeout. From what I can tell, the read() that Java provides support proper blocking, but without any timeout capability. Most other answers to this StackOverflow question provide timeout, but use busy-waiting.

it's impossible to implement timeout for InputStream without busy-waiting.

2. Java violates OOP by allowing Socket.setSoTimeout

The InputStream returned by Socket.getInputStream() extends class InputStream. InputStream.read() is an abstract method that isn't allowed to throw anything other than IOException according to its API. However, quoting Socket.setSoTimeout()'s API:

With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid.

Literally throwing an exception not declared in the interface.

This issue further pronounces the fact that the interface of InputStream is not designed to support timeouts. Java made sockets return InputStreams that support timeout by breaking OOP principles. In conclusion, if you have a non-TCP-based InputStream and want to have proper timeout blocking, you are out of luck - it's impossible.

Daniel Chin
  • 174
  • 1
  • 4
  • 12