How to interrupt BufferedReader's readLine
Asked Answered
A

10

47

I am trying to read input from a socket line by line in multiple threads. How can I interrupt readLine() so that I can gracefully stop the thread that it's blocking?

EDIT (bounty): Can this be done without closing the socket?

Ashbaugh answered 29/8, 2010 at 18:3 Comment(4)
Multithreaded stream reading? Isn't that kinda bad to do?Gulick
@TheLQ: If he is sharing the BufferedReader between the threads and synchronizing the reads, then it's OK. Just there will be no point in doing so.Hagbut
(Note that using readLine on untrusted input could lead to memory usage comparable to an adversaries bandwidth, leading to a denial-of-service.)Wafd
@Denis Tulskiy having for example multiples threads managing sockets with users each with a command terminal, what do you recomend instead of readLine()?Beneficiary
S
24

Close the socket on the interrupting thread. This will cause an exception to be thrown on the interrupted thread.

For more information on this and other concurrency issues, I highly recommend Brian Goetz's book "Java Concurrency in Practice".

Savory answered 29/8, 2010 at 18:38 Comment(5)
Out of curiosity, is it possible to achieve the same effect by closing the BufferedReader itself?Ashbaugh
@Jack: yes, closing BufferedReader will close underlying SocketInputStream and the Socket itself.Hagbut
(SocketInputStream is an internal implementation class of some (I believe) but not necessarily all implementations.)Wafd
@Denis: My experience is that you can't actually preempt readLine or read on a BufferedReader because it synchronizes read and close on the same monitor. Thus, if blocked waiting for data in read(), calling close() from another thread will just block that thread.Diapositive
Mark is completely right. There's a deadlock situation that can happen if you try to stop the BufferedReader with close() while it's in the middle of a read() or readLine(). The correct answer is close to socket so it causes the BufferedReader to except.Hoarsen
W
30

Without closing the socket:

The difficult problem isn't the BufferedReader.readLine, but the underlying read. If a thread is blocked reading, the only way to get it going is to supply some actual data or close the socket (interrupting the thread probably should work, but in practice does not).

So the obvious solution is to have two threads. One that reads the raw data, and will remain blocked. The second, will be the thread calling readLine. Pipe data from the first the second. You then have access to a lock than can be used to wakeup the second thread, and have it take appropriate action.

There are variations. You could have the first thread using NIO, with a single thread instance shared between all consumers.

Alternatively you could write a readLine that works with NIO. This could even take a a relatively simple single-threaded form, as Selector.wakeup exists and works.

Wafd answered 17/9, 2011 at 16:27 Comment(0)
S
24

Close the socket on the interrupting thread. This will cause an exception to be thrown on the interrupted thread.

For more information on this and other concurrency issues, I highly recommend Brian Goetz's book "Java Concurrency in Practice".

Savory answered 29/8, 2010 at 18:38 Comment(5)
Out of curiosity, is it possible to achieve the same effect by closing the BufferedReader itself?Ashbaugh
@Jack: yes, closing BufferedReader will close underlying SocketInputStream and the Socket itself.Hagbut
(SocketInputStream is an internal implementation class of some (I believe) but not necessarily all implementations.)Wafd
@Denis: My experience is that you can't actually preempt readLine or read on a BufferedReader because it synchronizes read and close on the same monitor. Thus, if blocked waiting for data in read(), calling close() from another thread will just block that thread.Diapositive
Mark is completely right. There's a deadlock situation that can happen if you try to stop the BufferedReader with close() while it's in the middle of a read() or readLine(). The correct answer is close to socket so it causes the BufferedReader to except.Hoarsen
M
11

Sorry for being over 6 years late ;-) I had a need for some interruptible readLine when reading from the keyboard, for a simple hobby console application. In other words, I couldn't "close the socket".

As you may know, System.in is an InputStream that apparently already does some buffering (you need to press Enter]). However, it seems to be suggested to wrap it in a BufferedReader for better efficiency, so my input is from:

BufferedReader consoleIn = new BufferedReader(new InputStreamReader(System.in));

The other thing one might have discovered is that BufferedReader.readLine() blocks until input is provided (even if the thread is interrupted, which seems to only end the thread once readline() gets its input). It is however possible to predict when BufferedReader.read() will not block, by calling BufferedReader.ready() == true. (However, == false does not guarantee a block, so beware.)

So I have incorporated the above ideas into a method that reads the BufferedReader character by character, checking in between each character if the thread has been interrupted, and also checks for end-of-line, at which point the line of text is returned.

You may find this code useful, pass the consoleIn variable as declared above. (Criticism may be welcomed too...):

private String interruptibleReadLine(BufferedReader reader)
        throws InterruptedException, IOException {
    Pattern line = Pattern.compile("^(.*)\\R");
    Matcher matcher;
    boolean interrupted = false;

    StringBuilder result = new StringBuilder();
    int chr = -1;
    do {
        if (reader.ready()) chr = reader.read();
        if (chr > -1) result.append((char) chr);
        matcher = line.matcher(result.toString());
        interrupted = Thread.interrupted(); // resets flag, call only once
    } while (!interrupted && !matcher.matches());
    if (interrupted) throw new InterruptedException();
    return (matcher.matches() ? matcher.group(1) : "");
}

... And in the thread that is calling this, catch the exceptions and end the thread appropriately.

This was tested in Java 8 on Linux.

Massarelli answered 6/2, 2017 at 18:35 Comment(3)
If readLine() blocks forever, I fail to see how read() is not going to block forever too.Ody
@YngvarKristiansen: read() is only called when we can be sure that it will not block, that is when ready() returns true. Refer to the 4th paragraph in the post, as well as the first line of code inside the do loop. Also refer to the API doc for BufferedReader.ready() about this guarantee.Massarelli
I used this code myself recently for an interactive command-line app and found a lot of processor time being used by it. I'm too lazy to rework the answer, but please use with caution and do own testing and improvement: consider this as only a rudimentary demo of the technique.Massarelli
M
10

I was playing around with this recently (using Scala), and I didn't like the accepted answer of closing the socket and getting an exception.

Eventually I discovered that it's possible to call socket.shutdownInput() in the interrupting thread to get out of the readLine call without an exception. I make this call in a SIGINT handler so that I can clean up and close the socket in the main thread.

Note, that the equivalent exists for the outputstream with socket.shutdownOutput()

Mcclendon answered 2/12, 2011 at 14:50 Comment(2)
+1 for this idea. I was seeing a situation where close() would not release a thread that was waiting on a socket read, but shutdownInput() caused an exception which I could catch in the thread. Still not sure why close() wouldn't break out of the blocking read, but it solve the parallel problem with the reader on the other end.Sailmaker
Thank you so much! This was something I was having problems with for months.Teresita
S
0

you can design a Timer class around the read() block.

you need to set a timeout for your timer.

on timeout just interrupt your thread.

Sweetandsour answered 29/8, 2010 at 18:30 Comment(1)
Interrupting a thread block on I/O is unlikely to work. (I think it was enabled briefly on the Solaris Sun JRE, but was problematic.)Wafd
R
0

Without closing the socket, no question the best solution with the least overhead is to simply avoid using the blocking read methods until the BufferedReader is ready, or a timeout is reached.

public String readLineTimeout(BufferedReader reader, long timeout) throws TimeoutException, IOException {
    long start = System.currentTimeMillis();

    while (!reader.ready()) {
        if (System.currentTimeMillis() - start >= timeout)
            throw new TimeoutException();

        // optional delay between polling
        try { Thread.sleep(50); } catch (Exception ignore) {}
    }

    return reader.readLine(); // won't block since reader is ready
}
Recor answered 3/5, 2017 at 0:18 Comment(0)
E
0

If you want to use readLine on a server socket within a client-server tcp architecture, for instance, you can use setSoTimeout(int timeout) of java.net.Socket.

From the Socket#setSoTimeout(int timeout) Documentation:

Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. 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.

public class MainApp {
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ServerSocket serverSocket = new ServerSocket(11370);
        Socket clientSocket = serverSocket.accept();
        clientSocket.setSoTimeout(2000);
        executorService.execute(new ReadingThread(clientSocket));
        // ... some async operations
        executorService.shutdown();
    }
}

public class ReadingThread implements Runnable {
    private final Socket clientSocket;
    public ReadingThread(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        BufferedReader socketReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        String readInput = null;
        while (!Thread.currentThread().isInterrupted()) {
            try {
                readInput = socketReader.readLine();
            } catch (SocketTimeoutException e) {
                continue; 
            }
        }
        // operations with readInput
    }
}

The main application implements a server socket which listens to connections and has a thread pool. If an incoming client communication is accepted, then a new Thread from the pool is assigned and the run function is invoked in ReadingThread (can be adjusted to allow multiple threads). On the socket used for communicating to the client the property setSoTimeout(int timeout) has been set. Therefore if readLine does not return within the specified timeout a SocketTimeoutException is thrown. You can check in a loop whether the ReadingThread has been interrupted by the main application, and if so stop reading from the socket.

Expropriate answered 22/10, 2018 at 19:40 Comment(0)
J
0

When the buffered reader is being used to read the input stream from a socket then you can achieve this by having the read call timeout. Once this timeout is triggered you will be able to check if your thread should be stopped. To do this call setSoTimeout on the socket. The read call will then have a SocketTimeoutException and you can use that to stop the thread.

@Override
public void run() {
    running = true;

    try {
        socket.setSoTimeout(1000); // This will determine how quick your thread responds to the shutdown call
        var inputStream = socket.getInputStream();
        bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
    } catch (IOException e) {
        Logger.error("IOException while setting up input stream");
        Logger.error(e);
        return;
    }

    StringBuilder stringBuilder = null;
    while (running) {
        try {
            int singleChar = bufferedReader.read();
            // Do something with the data
        } catch (SocketTimeoutException e) {
            // SocketTimeoutException is expected periodically as we do setSoTimeout on the socket, 
            // this makes the above read call not block for ever and allows the loop to be interrupted 
            // cleanly when we want to shut the thread down.
            Logger.trace("Socket timeout exception");
            Logger.trace(e);
        } catch (IOException e) {
            Logger.error("IOException while reading from socket stream");
            Logger.error(e);
            return;
        }
    }
}

public void stopThread() {
    running = false;
    try {
        bufferedReader.close();
    } catch (IOException e) {
        Logger.error("IOException while closing BufferedReader in SocketThread");
        Logger.error(e);
    }
}

Answer found here: Any way of using java.nio.* to interrupt a InputStream#read() without closing socket?

Jourdain answered 23/7, 2021 at 17:24 Comment(0)
R
-1

I think that you might have to use something other than readLine(). You could use read() and at every loop iteration check to see if the thread was interrupted and break out of the loop if it was.

BufferedReader reader = //...
int c;
while ((c = reader.read()) != -1){
  if (Thread.isInterrupted()){
    break;
  }
  if (c == '\n'){
    //newline
  }
  //...
}
Rickey answered 29/8, 2010 at 18:12 Comment(2)
Might this cause a sort of busy-waiting situation? Or, if read() blocks as well and does not respond to Thread.interrupt(), then it might have the same problem as I originally had.Ashbaugh
Yes, if read() blocks, then you're still stuck with the same problem. You might be able to just close the BufferedReader instance from another thread. If this works, it might cause the read()/readLine() methods to return. It would probably throw an IOException too.Rickey
O
-1

A sketch for a solution might be this: NIO provides methods for nonblocking IO, so you have to implement something called Foo that uses nonblocking NIO on the socket end but also provides a InputStream or Reader interface on the other end. If the BufferedReader enters its own read, it will call Foo, which will call Selector.select with read intent. select will either return indicating the presence of more data or it will block until more data is available.

If another thread wants to unblock the reader, it must call Selector.wakeup and the selector can return gracefully by throwing an exception the by BufferedReader.

The socket should be still open after that.

Variation A: call Selector.select(timeout) to do busy polling light.

Olwen answered 19/9, 2011 at 21:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.