0

I have a thread that's reading from an input stream, and (just for simplicity) printing it back out to the console. See below for the code snippet.

I want to be able to terminate this thread at some point, so I should be able to do this by closing the input stream. This should cause an exception to be thrown, interrupting the BufferedReader::readLine method, closing the thread.

For some reason, the close() invocation just hangs.

What's the reason that this is happening? Is there a way I can close down the thread without requiring the user to input any further text?

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

public class Test {

  public static void main(String[] args) throws Exception {
    ReaderTest readerTest = new ReaderTest(System.in);
    new Thread(readerTest).start();
    Thread.sleep(1000L);
    System.out.println("About to close...");
    System.in.close(); // Should cause thread to throw exception and terminate, but just hangs
    System.out.println("All closed...");
  }

  private static class ReaderTest implements Runnable {
    private final InputStream inputStream;

    public ReaderTest(InputStream inputStream) {
      this.inputStream = inputStream;
    }

    @Override
    public void run() {
      try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String line;
        while ((line = reader.readLine()) != null) {
          System.out.println(line);
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}

Ekos IV
  • 337
  • 1
  • 10

2 Answers2

2

For some reason, the close() invocation just hangs.

Your thread is waiting for readLine() to release lock: You are using a blocking IO.

public ReaderTest(InputStream inputStream) {
   this.inputStream = inputStream; // This inputStream (in from System.in) is a monitor
}
while ((line = reader.readLine()) != null) {
       System.out.println(line);
}

The readLine() method proceeds in following steps:

  • Take lock on inputStream - Here, it's System.in
  • Go in an infinite loop (Blocking) waiting for an input
  • When you enter a line (and hit return or enter) on your console, it reads them and returns the output, releasing the lock momentarily
  • It then goes into the same infinite (Blocking) loop again (You're calling readLine in a while loop)

If you provide some input, the main thread gets to execute System.in.close(); (because the reader thread released lock momentarily)

So, if you provide an input after 1 second (Thread.sleep(1000L);), the readLine() will find that stream is closed and it's this time java.io.IOException: Stream closed is thrown.

You can see the last line you entered (before IOException is thrown) by updating read block as:

@Override
public void run() {
    String line = null; // make line reachable inside catch block
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        System.out.println("The last line I read before stream was closed : " + line);
        e.printStackTrace();
    }
}
adarsh
  • 1,393
  • 1
  • 8
  • 16
  • Your explanation makes sense, thank you. That is what I observed -- entering some text in the terminal after the sleep has concluded does indeed allow the thread to terminate. In this case, I'd like the thread to close *without* requiring the user to input any further text. Is this possible? I'll amend my question to make the desired answer more clear. – Ekos IV Apr 15 '21 at 17:22
  • 1
    @EkosIV You can *attempt* to make it non-blocking as suggested [here](https://stackoverflow.com/questions/7872846/how-to-read-from-standard-input-non-blocking). But, the Streams are designed to be [blocking](https://stackoverflow.com/questions/5049319/how-to-create-a-java-non-blocking-inputstream-from-a-httpsurlconnection) – adarsh Apr 15 '21 at 17:38
0

It is probably related to the System.in, changing your main in:

public static void main(String[] args) throws Exception {
        ServerSocket socket = new ServerSocket(8883);
        InputStream is = socket.accept().getInputStream();
        ReaderTest readerTest = new ReaderTest(is);
        new Thread(readerTest).start();
        Thread.sleep(1000L);
        System.out.println("About to close...");
        is.close(); // cause thread to throw exception        System.out.println("All closed...");
    }

It does what you expect.

(watch out you need to telnet or nc to localhost 8883 to let the code pass the .accept() method of the socket)

rascio
  • 8,968
  • 19
  • 68
  • 108