상대의 FIN을 받아 ACK까지 보냈지만, 내 쪽 애플리케이션이 아직 close()를
호출하지 않은 상태.
어느 지점인가
상대 ──── FIN ────► 나
나 ──── ACK ────► 상대 ← 여기서 내 소켓은 CLOSE_WAIT
(커널은 할 일을 다 했다)
나 : close() 호출 필요 ─────┐
▼
나 ──── FIN ────► 상대 LAST_ACK → 종료
커널은 "상대가 닫았다"까지만 처리하고, 애플리케이션이 소켓을 닫아 주기를 기다린다. 아직 보낼 데이터가 남았을 수도 있으니 커널이 마음대로 닫을 수 없기 때문이다.
쌓이면 100% 버그다
TIME_WAIT는 시간이 지나면 사라지지만, CLOSE_WAIT는 close()를 부르기 전까지
영원히 남는다. 그래서 개수가 단조 증가한다면 코드 문제다.
흔한 원인
// ❌ 예외가 나면 close 가 안 된다
InputStream in = socket.getInputStream();
process(in); // 여기서 예외
in.close();
// ✅ try-with-resources
try (InputStream in = socket.getInputStream()) {
process(in);
}
- 예외 경로에서
close()누락 - 커넥션 풀에서 빌린 자원 미반납
- HTTP 클라이언트에서 응답 본문을 안 읽거나 안 닫음 (본문을 다 읽어야 커넥션이 풀로 돌아가는 구현이 많다)
어떻게 터지나
소켓 하나가 파일 디스크립터 하나다. CLOSE_WAIT이 쌓이면 FD 한도에 걸려
java.net.SocketException: Too many open files
가 나고, 그 시점엔 새 연결도 파일 열기도 전부 실패한다. 서버가 통째로 멈춘다.
진단 순서
ss -tan state close-wait | wc -l # 개수 추이
lsof -p <PID> | wc -l # 프로세스별 FD 수
ulimit -n # 한도 확인
개수가 계속 늘어나면 어느 코드 경로가 닫지 않는지를 찾는다. 상대 포트를 보면 어떤 외부 시스템과의 연결인지 좁힐 수 있다.