connection reset exception handling analysis in java

  • 2020-06-19 10:15:26
  • OfStack

Several connection rest exception, Broken pipe, Connection reset, Connection reset by peer are commonly seen in Java

Socked reset case

Linux has two common error codes for sock reset

ECONNRESET

The error is described as "connection reset by peer", or "party reset connection", which usually occurs when the service process terminates earlier than the client process. When the service process terminates, FIN subsection is sent to customer TCP, customer TCP responds to ACK, and service TCP switches to FIN_WAIT2. At this point, if the client process does not process the FIN (such as blocking on other calls without closing Socket), client TCP will be in the CLOSE_WAIT state. When the client process again sends data to service TCP in FIN_WAIT2 status, service TCP immediately responds to RST. 1 generally speaking, this kind of situation can also trigger another application exceptions, customer process in after sending the data, often can wait IO receive data from the network, is typical of such as read or readline calls, at this time due to the reason of the execution sequence, if the call occurred in RST section received before execution, then the result is a customer process will get an unexpected EOF errors. "server terminated prematurely" - "Server prematurely terminated" error.

EPIPE

The error is described as "broken pipe" or "pipe break". This typically occurs when the client process ignores (or does not process) the Socket error and continues to write more data to service TCP. The kernel sends an SIGPIPE signal to the client process, which terminates the process by default (the foreground process is not performing core dump at this time). In combination with the above ECONNRESET error, it is not a problem to write data to a service TCP in FIN_WAIT2 status (ACK responded to FIN subsection), but it is an error to write to an Socket that has received RST.

Processing of socket input stream/output stream in Java

Let's look at the code snippet first

SocketInputStream.c


switch (errno) { 
case ECONNRESET: 
case EPIPE: 
  JNU_ThrowByName(env, "sun/net/ConnectionResetException",   
  "Connection reset"); 
  break; 
         .... 

SocketOutputStream.c


if (errno == ECONNRESET) { 
          JNU_ThrowByName(env, "sun/net/ConnectionResetException", 
            "Connection reset"); 
    } else { 
      NET_ThrowByNameWithLastError(env, "java/net/SocketException",  
      "Write failed"); 
    } 

You can see that java is reading and writing and that the EPIPE case is not handled in the same way

In the case of read, Reset throws all ConnectionResetException with the error message Connection Reset

In the case of write, Reset throws ConnectionResetException to ECONNRESET and SocketException to EPIPE, with the error message Broken pipe

How do I print out the message Broken pipe

SIGPIPE signal processing function

When the reset packet is received, if socket is read or written, an EPIPE error occurs and the SIGPIPE signal is often received

In the program, you can see that java did not handle the error EPIPE in the case of write. At the beginning, the exception that was thrown with the error was thrown by the signal handler

Let's first look at the signal SIGPIPE handler, which calls the function in Linux::install_signal_handlers


set_signal_handler(SIGSEGV, true); 
set_signal_handler(SIGPIPE, true); 
set_signal_handler(SIGBUS, true); 
set_signal_handler(SIGILL, true); 
set_signal_handler(SIGFPE, true); 
set_signal_handler(SIGXFSZ, true); 

The corresponding signal processing function in the function set_signal_handler is signalHandler


sigAct.sa_handler = SIG_DFL; 
 if (!set_installed) { 
  sigAct.sa_flags = SA_SIGINFO|SA_RESTART; 
 } else { 
  sigAct.sa_sigaction = signalHandler; 
  sigAct.sa_flags = SA_SIGINFO|SA_RESTART; 
 } 

Finally, the function JVM_handle_linux_signal is called

In the X86 architecture, the function JVM_handle_linux_signal


extern "C" int 
JVM_handle_linux_signal(int sig, 
            siginfo_t* info, 
            void* ucVoid, 
            int abort_if_unrecognized) { 
 ucontext_t* uc = (ucontext_t*) ucVoid; 
 
 Thread* t = ThreadLocalStorage::get_thread_slow(); 
 
 SignalHandlerMark shm(t); 
 
 // Note: it's not uncommon that JNI code uses signal/sigset to install 
 // then restore certain signal handler (e.g. to temporarily block SIGPIPE, 
 // or have a SIGILL handler when detecting CPU type). When that happens, 
 // JVM_handle_linux_signal() might be invoked with junk info/ucVoid. To 
 // avoid unnecessary crash when libjsig is not preloaded, try handle signals 
 // that do not require siginfo/ucontext first. 
 
 if (sig == SIGPIPE || sig == SIGXFSZ) { 
  // allow chained handler to go first 
  if (os::Linux::chained_handler(sig, info, ucVoid)) { 
   return true; 
  } else { 
   if (PrintMiscellaneous && (WizardMode || Verbose)) { 
    char buf[64]; 
    warning("Ignoring %s - see bugs 4229104 or 646499219", 
        os::exception_name(sig, buf, sizeof(buf))); 
   } 
   return true; 
  } 
 } 
... 
} 

chained handler processing is used for signal SIGPIPE, that is, the original signal processing function of the system is used, which proves that the exception is not thrown by signal processing function

NET_ThrowByNameWithLastError function

Since it is not an exception thrown by the signal handler, proceed to look at the original outputstream program


if (errno == ECONNRESET) { 
          JNU_ThrowByName(env, "sun/net/ConnectionResetException", 
            "Connection reset"); 
    } else { 
      NET_ThrowByNameWithLastError(env, "java/net/SocketException",  
      "Write failed"); 
    } 

That's the case with else, so for the error of EPIPE, java throws socketexception, the error message is Write failed, in fact what we see is SockedException, the exception is paired, but the message is Broken pipe, not Write failed.

The key point is the function NET_ThrowByNameWithLastError


void 
NET_ThrowByNameWithLastError(JNIEnv *env, const char *name, 
          const char *defaultDetail) { 
  char errmsg[255]; 
  sprintf(errmsg, "errno: %d, error: %s\n", errno, defaultDetail);  
  JNU_ThrowByNameWithLastError(env, name, errmsg);  
} 

Function JNU_ThrowByNameWithLastError


JNIEXPORT void JNICALL 
JNU_ThrowByNameWithLastError(JNIEnv *env, const char *name, 
         const char *defaultDetail) 
{ 
  char buf[256]; 
  int n = JVM_GetLastErrorString(buf, sizeof(buf)); 
 
  if (n > 0) { 
  jstring s = JNU_NewStringPlatform(env, buf); 
  if (s != NULL) { 
    jobject x = JNU_NewObjectByName(env, name, 
            "(Ljava/lang/String;)V", s); 
    if (x != NULL) { 
    (*env)->Throw(env, x); 
    } 
  } 
  } 
  if (!(*env)->ExceptionOccurred(env)) { 
  JNU_ThrowByName(env, name, defaultDetail); 
  } 
} 

The program can see that the JVM_GetLastErrorString message is displayed first. If the message is empty, the exception message of defaultDetail is displayed, that is, the corresponding Write failed!

JVM_GetLastErrorString USES hpi::lasterror, the function sysGetLastErrorString is the same as linux and solaris


int 
sysGetLastErrorString(char *buf, int len) 
{ 
  if (errno == 0) { 
  return 0; 
  } else { 
  const char *s = strerror(errno); 
  int n = strlen(s); 
  if (n >= len) n = len - 1; 
  strncpy(buf, s, n); 
  buf[n] = '\0'; 
  return n; 
  } 
} 

It turns out to be strerror(errno), which directly shows the error content of linux kernel corresponding to this error number

Conclusion: Broken pipe is the kernel error message, not java itself


Related articles: