Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blocking on stdin makes Java process take 350ms more to exit

Tags:

I have a Java program as follows:

public class foo{    public static void main(String[] args) throws Exception{     Thread t = new Thread(       new Runnable() {         public void run() {           try{System.in.read();}catch(Exception e){}         }       }     );     t.setDaemon(true);     t.start();     Thread.sleep(10); // Make sure it hits the read() call     t.interrupt();     t.stop();     System.exit(0);   } } 

Running this (time java foo) with the System.in.read call present takes ~480ms to exit while running it with the System.in.read call commented out takes ~120ms to exit

I had thought that once the main thread reaches the end, the program terminates, but clearly, there's another 300ms lag hanging around (you can see this by adding a println after the Thread.sleep). I tried t.interrupt t.stop System.exit which should stop things "immediately", but none of them seem able to make the program skip it's 350ms extra exit latency seemingly doing nothing.

Anyone know why that is the case, and if there's anything I can do to avoid this latency?

like image 830
Li Haoyi Avatar asked Feb 23 '18 15:02

Li Haoyi


2 Answers

As it turns out besides collecting bad news, there is some workaround too, see the very bottom of this post.

This is not a full reply, just a check popped into my mind, with sockets.
Based on this and the System.in.read() experiment I have reproduced too, the delay may be the cost of having an outstanding synchronous I/O request towards the OS. (Edit: actually it is an explicit wait which kicks in when threads do not exit normally when the VM is shutting down, see below the horizontal line)

(I am ending the thread(s) with a while(true);, so it (they) never exits prematurely)

  • if you create a bound socket final ServerSocket srv=new ServerSocket(0);, exit remains 'normal'
  • if you srv.accept();, you suddenly have the extra wait
  • if you create an "inner" daemon thread with Socket s=new Socket("localhost",srv.getLocalPort());, and Socket s=srv.accept(); outside, it becomes 'normal' again
  • however if you invoke s.getInputStream().read(); on any of them, you have the extra wait again
  • if you do it with both sockets, extra wait extends a bit longer (much less than 300, but consistent 20-50 ms for me)
  • having the inner thread, it is also possible to get stuck on the new Socket(...); line, if accept() is not invoked outside. This also has the extra wait

So having sockets (just bound or even connected) is not a problem, but waiting for something to happen (accept(), read()) introduces something.

Code (this variant hits the two s.getInputSteam().read()-s)

import java.net.*; public class foo{   public static void main(String[] args) throws Exception{     Thread t = new Thread(       new Runnable() {         public void run() {           try{             final ServerSocket srv=new ServerSocket(0);              Thread t=new Thread(new Runnable(){               public void run(){                 try{                   Socket s=new Socket("localhost",srv.getLocalPort());                   s.getInputStream().read();                    while(true);                 }catch(Exception ex){}             }});             t.setDaemon(true);             t.start();              Socket s=srv.accept();              s.getInputStream().read();              while(true);           }catch(Exception ex){}         }       }     );     t.setDaemon(true);     t.start();     Thread.sleep(1000);   } } 

I also tried what appears in the comments: having access (I just used static) to ServerSocket srv, int port, Socket s1,s2, it is faster to kill things on the Java side: close() on srv/s1/s2 shuts down accept() and read() calls very fast, and for shutting down accept() in particular, a new Socket("localhost",port) also works (just it has a race condition when an actual connection arrives at the same time). A connection attempt can be shut down too with close(), just an object is needed for that (so s1=new Socket();s1.connect(new InetSocketAddress("localhost",srv.getLocalPort())); has to be used instead of the connecting constructor).

TL;DR: does it matter to you? Not at all: I tried System.in.close(); and it had absolutely no effect on System.in.read();.


New bad news. When a thread is in native code, and that native code does not check for 'safepoint', one of the final steps of the shutdown procedure waits for 300 milliseconds, minimum:

// [...] In theory, we // don't have to wait for user threads to be quiescent, but it's always // better to terminate VM when current thread is the only active thread, so // wait for user threads too. Numbers are in 10 milliseconds. int max_wait_user_thread = 30;                  // at least 300 milliseconds 

And it is waiting in vain, because the thread is executing a simple fread on /proc/self/fd/0

While read (and recv too) is wrapped in some magical RESTARTABLE looping thing (https://github.com/openjdk-mirror/jdk7u-hotspot/blob/master/src/os/linux/vm/os_linux.inline.hpp#L168 and read is a bit lower - it is a wrapper for fread in yet another file), which seems to be aware of EINTR

#define RESTARTABLE(_cmd, _result) do { \     _result = _cmd; \   } while(((int)_result == OS_ERR) && (errno == EINTR))  [...]  inline size_t os::restartable_read(int fd, void *buf, unsigned int nBytes) {   size_t res;   RESTARTABLE( (size_t) ::read(fd, buf, (size_t) nBytes), res);   return res; } 

, but that is not happening anywhere, plus there are some comments here and there that they did not want to interfere with libpthread's own signalling and handlers. According to some questions here on SO (like How to interrupt a fread call?), it might not work anyway.

On the library side, readSingle (https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/native/java/io/io_util.c#L38) is the method which has been invoked:

jint readSingle(JNIEnv *env, jobject this, jfieldID fid) {     jint nread;     char ret;     FD fd = GET_FD(this, fid);     if (fd == -1) {         JNU_ThrowIOException(env, "Stream Closed");         return -1;     }     nread = (jint)IO_Read(fd, &ret, 1);     if (nread == 0) { /* EOF */         return -1;     } else if (nread == JVM_IO_ERR) { /* error */         JNU_ThrowIOExceptionWithLastError(env, "Read error");     } else if (nread == JVM_IO_INTR) {         JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);     }     return ret & 0xFF; } 

which is capable of handling 'being interrupted' on JRE level, but that part just will not get executed as fread does not return (in case of everything non-Windows, that IO_Read is #define-d to JVM_Read, and that is just a wrapper for the restartable_read mentioned earlier)

So, it is by design.

One thing which works is to provide your own System.in (despite of being final there is a setIn() method for this purpose, doing the nonstandard swap in JNI). But it involves polling, so it is a bit ugly:

import java.io.*; public class foo{   public static void main(String[] args) throws Exception{      System.setIn(new InputStream() {       InputStream in=System.in;       @Override       public int read() throws IOException {         while(in.available()==0)try{Thread.sleep(100);}catch(Exception ex){}         return in.read();       }     });      Thread t = new Thread(       new Runnable() {         public void run() {           try{             System.out.println(System.in.read());             while(true);           }catch(Exception ex){}         }       }     );     t.setDaemon(true);     t.start();     Thread.sleep(1000);   } } 

With the Thread.sleep() inside InputStream.read() you can balance between being unresponsive or cooking with the CPU. Thread.sleep() correctly checks for being shut down, so even if you put it up to 10000, the process will exit fast.

like image 169
tevemadar Avatar answered Sep 19 '22 21:09

tevemadar


As others have commented, Thread interrupt does not cause a blocking I/O call to immediately stop. The wait is happening because system in is waiting on input from a File (stdin). If you were to supply the program with some initial input you will notice it finishes much faster:

$ time java -cp build/libs/testjava.jar Foo  real    0m0.395s user    0m0.040s sys     0m0.008s $ time java -cp build/libs/testjava.jar Foo <<< test  real    0m0.064s user    0m0.036s sys     0m0.012s 

If you want to avoid waiting on I/O threads, then you can check the availability first. You can then perform a wait using a method which is interruptable such as Thread.sleep:

while (true) {     try {         if (System.in.available() > 0) {             System.in.read();         }         Thread.sleep(400);     }     catch(Exception e) {      } } 

This program will exit quickly each time:

$ time java -cp build/libs/testjava.jar Foo  real    0m0.065s user    0m0.040s sys     0m0.008s $ time java -cp build/libs/testjava.jar Foo <<< test  real    0m0.065s user    0m0.040s sys     0m0.008s 
like image 30
flakes Avatar answered Sep 20 '22 21:09

flakes