JVM 소스 코드 시리즈: 자바 의 connection reset 이상 처리 분석
Socked reset case
Linux 에는 흔히 볼 수 있 는 sock reset 의 오류 코드 가 2 개 있 습 니 다.
ECONNRESET
이 오 류 는 'connection reset by peer', 즉 '상대방 리 셋 연결' 으로 묘사 되 어 있 으 며, 이러한 상황 은 일반적으로 서비스 프로 세 스 가 고객 프로 세 스 보다 앞 당 겨 종료 되 는 경우 에 발생 합 니 다.서비스 프로 세 스 가 종 료 될 때 고객 TCP 에 FIN 분 절 을 보 내 고 고객 TCP 는 ACK 에 응답 하 며 서비스 TCP 는 FIN 으로 전 환 됩 니 다.WAIT 2 상태.이 때 클 라 이언 트 프로 세 스 가 이 FIN 을 처리 하지 않 으 면 클 라 이언 트 TCP 는 CLOSE 에 있 습 니 다.WAIT 상태.클 라 이언 트 프로 세 스 가 다시 FINWAIT 2 상태의 서비스 TCP 가 데 이 터 를 보 낼 때 서비스 TCP 는 즉시 RST 에 응답 합 니 다.일반적으로 이러한 상황 은 다른 응용 프로그램 에 이상 을 일 으 킬 수 있 습 니 다. 클 라 이언 트 프로 세 스 는 데 이 터 를 보 낸 후에 네트워크 IO 에서 데 이 터 를 받 기 를 기다 리 는 경우 가 많 습 니 다. 예 를 들 어 read 나 readline 호출 등 전형 적 인 것 입 니 다. 이때 실행 순서 때문에 이 호출 이 RST 분 절 을 받 기 전에 실 행 될 경우.그 결 과 는 고객 프로 세 스 가 예상 치 못 한 EOF 오 류 를 얻 게 될 것 이다.이 때 보통 "server terminated prematurely" - "서버 조기 종료" 오 류 를 출력 합 니 다.
EPIPE
오 류 는 "broken pipe", 즉 "파이프 파열" 로 설명 되 어 있 습 니 다. 이러한 상황 은 일반적으로 클 라 이언 트 프로 세 스 가 소켓 오 류 를 무시 하거나 처리 하지 않 은 상태 에서 발생 합 니 다. 서비스 TCP 에 더 많은 데 이 터 를 계속 기록 할 때 커 널 은 클 라 이언 트 프로 세 스 에 SIGPIPE 신 호 를 보 냅 니 다. 이 신 호 는 기본적으로 프로 세 스 를 종료 합 니 다 (이때 이 프론트 프로 세 스 는 core dump 를 하지 않 았 습 니 다).위의 ECONNRESET 오 류 를 결합 하여 알 수 있 듯 이 FINWAIT 2 상태의 서비스 TCP (ACK 응답 FIN 분 절) 에 데 이 터 를 기록 하 는 것 은 문제 가 되 지 않 지만 RST 를 받 은 Socket 을 쓰 는 것 은 오류 입 니 다.
자바 의 socket input stream / output stream 처리
코드 세 션 먼저 보기
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");
}
자바 가 읽 고 쓰 는 상황 을 볼 수 있 습 니 다. EPIPE 에 대한 상황 은 다 릅 니 다.
read 의 경우 Reset 은 모두 Connection Reset Exception 을 던 집 니 다. 잘못된 정 보 는 Connection Reset 입 니 다.
write 의 경우 Reset 은 ECONNRESET 에 대해 Connection Reset Exception 을 던 졌 고 EPIPE 에 대해 서 는 SocketException 을 던 졌 으 며 잘못된 정 보 는 Broken pipe 입 니 다.
어떻게 정 보 를 출력 합 니까? Broken pipe
SIGPIPE 신호 처리 함수
reset 패 키 지 를 받 은 후 socket 을 읽 고 쓰 면 오류 가 발생 할 수 있 습 니 다.
프로그램 에서 자바 가 write 를 처리 하지 않 은 상태 에서 오류 EPIPE 를 처리 하지 않 은 것 을 볼 수 있 습 니 다. 시작 할 때 잘못된 것 은 신호 처리 함수 로 던 진 것 입 니 다.
먼저 신호 SIGPIPE 에 대한 처리 함 수 를 살 펴 보 겠 습 니 다. Linux: installsignal_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);
반면 함수 setsignal_handler, 대응 하 는 신호 처리 함 수 는 signal Handler 입 니 다.
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;
}
결국 함 수 를 호출 하 였 다. JVM_handle_linux_signal
X86 구조 에서 함수 JVMhandle_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;
}
}
...
}
신호 SIGIPE 에 chained handler 처 리 를 사 용 했 습 니 다. 즉, 시스템 의 원래 신호 처리 함 수 를 사 용 했 습 니 다. 이 는 이상 이 신호 처리 함수 가 던 진 것 이 아니 라 는 것 을 증명 합 니 다.
NET_ThrowByNameWithLastError 함수
신호 처리 함수 가 던 진 이상 이 아 닌 이상 원래 의 outputstream 프로그램 을 계속 보 세 요.
if (errno == ECONNRESET) {
JNU_ThrowByName(env, "sun/net/ConnectionResetException",
"Connection reset");
} else {
NET_ThrowByNameWithLastError(env, "java/net/SocketException",
"Write failed");
}
즉, else 의 경우 EPIPE 의 오류 에 대해 자바 가 던 진 socketexception 입 니 다. 잘못된 정 보 는 Write failed 입 니 다. 사실 우리 가 볼 수 있 는 것 은 SockedException 입 니 다. 이상 하 게 맞 아 떨 어 졌 지만 정 보 는 Write failed 가 아 닌 Broken pipe 입 니 다.
관건 은 함수 에 있다 NET_ThrowByNameWithLastError
void
NET_ThrowByNameWithLastError(JNIEnv *env, const char *name,
const char *defaultDetail) {
char errmsg[255];
sprintf(errmsg, "errno: %d, error: %s
", errno, defaultDetail);
JNU_ThrowByNameWithLastError(env, name, errmsg);
}
함수 JNUThrowByNameWithLastError
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);
}
}
프로그램 에서 먼저 JVM 보이 기GetLast ErrorString 의 정 보 는 정보 가 비어 있어 야 default Detail 의 이상 정 보 를 표시 합 니 다. 즉, 대응 하 는 Write failed 를 시작 하 는 것 입 니 다!
JVM_GetLastErrorString 은 hpi:: lasterror, 즉 함수 sys GetLastErrorString 을 사용 합 니 다. Liux 와 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;
}
}
원래 strerror (errno) 였 습 니 다. 즉, Liux kernel 이 이 error number 에 대응 하 는 오류 내용 을 직접 표시 하 는 것 입 니 다.
결론: Broken pipe 는 커 널 에 대응 하 는 오류 정보 로 자바 가 제공 하 는 정보 가 아 닙 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
GraalVM을 사용해보기GraalVM의 퍼포먼스, 그리고는, Graal을 JIT 컴파일러로서 이용했을 때의 퍼포먼스에 포커스 한 기사이므로, GraalVM의 특징의 하나인 native image나, multiple languages등에 관...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.