4.7 KiB
4.7 KiB
title, source_type, status, related_branches, related_projects, tags, created, status_label
| title | source_type | status | related_branches | related_projects | tags | created | status_label | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| error / jdk-httpclient-dns-unresolvedaddress-connectexception-2026-06-11 | error-note | raw |
|
|
|
2026-06-11 | resolved |
error: jdk-httpclient-dns-unresolvedaddress-connectexception-2026-06-11
Layer:
raw/errors/— 작업 중 마주친 단일 실패·트러블슈팅 기록. 원본은 raw에 영구 보관한다.
Parent / 부모
- raw/branch-notes/feature-outbound-http-client-baseline — D12 실패 분류(error mapper) 통합 테스트 작성 중 발견.
증상 / Symptom
- 에러 메시지 (구현 에이전트 보고 원문 발췌 — 테스트 red 단계):
red: ./gradlew :adapter-outbound:test --tests '*.OutboundHttpClientTest' --console=plain → 4 FAILED (... t4 CONNECT_FAILED not DNS_FAILED ...) - 발생 컨텍스트:
OutboundHttpClient.get("http://nonexistent-host-zzz.invalid", ...)호출 시OutboundHttpErrorMapper가DEPENDENCY_DNS_FAILED가 아니라DEPENDENCY_CONNECT_FAILED를 반환. - 발생 환경: local, JDK 21 (
JdkClientHttpRequestFactory+java.net.http.HttpClient). - 재현 가능 여부:
always.
재현 절차 / Reproduction
- JDK 21
HttpClient기반 SpringRestClient로 존재하지 않는 호스트(*.invalid)에 GET 요청. - cause chain 을 단일 패스로 위에서부터 매칭하는 분류기(
UnknownHostException|UnresolvedAddressException규칙이ConnectException규칙보다 우선순위가 높아도, 체인 순서상ConnectException이 먼저 등장)를 통과. - 기대:
DEPENDENCY_DNS_FAILED. - 실제:
DEPENDENCY_CONNECT_FAILED— JDK 21HttpClient가 DNS 실패를ConnectException(cause=ConnectException(cause=UnresolvedAddressException))으로 래핑하기 때문에, 체인을 바깥에서부터 한 번만 훑는 분류기는 바깥쪽ConnectException에서 먼저 멈춘다.
조사 단계 / Investigation log
- 2026-06-11 — t4 red 관측 → 예외 cause chain 출력으로
ConnectException → ConnectException → UnresolvedAddressException중첩 구조 확인 (JDK 21 로컬 검증). - 2026-06-11 — 분류 규칙 순서 조정만으로는 해결 불가(체인 등장 순서 문제) →
ConnectException매칭 시 잔여 서브 체인을hasDnsCauseInChain()으로 추가 스캔, DNS 근원 발견 시DEPENDENCY_DNS_FAILED우선 반환하도록 수정 → t4 green, 기존 mapper 단위테스트 19/19 회귀 없음.
근본 원인 / Root cause
- 직접 원인: 분류기가 cause chain 에서 먼저 등장하는 예외 타입으로 결정 — 바깥 래퍼(
ConnectException)가 안쪽 근원(UnresolvedAddressException)을 가림. - 근본 원인: JDK
HttpClient의 예외 래핑 구조(DNS 실패도ConnectException으로 노출)가 "타입 우선순위 = 체인 등장 순서" 가정과 충돌. - 트리거 조건: JDK 21
HttpClient+ 미해석 호스트명. (needs-confirmation: 다른 JDK 버전/다른ClientHttpRequestFactory의 래핑 구조는 미검증.)
Sources / 근거
- 로컬 검증:
OutboundHttpClientTest.t4_unknown_host_*(JDK 21) — 수정 전 red / 수정 후 green. 외부 공식 문서 인용 없음 (JDK 예외 래핑 구조는 로컬 관측 기반).
해결 / Resolution
- 적용한 조치:
OutboundHttpErrorMapper—ConnectException매칭 시hasDnsCauseInChain()helper 로 서브 체인에서UnknownHostException/UnresolvedAddressException을 추가 탐색, 발견 시 DNS 분류 우선. - 검증 방법:
./gradlew :adapter-outbound:test—OutboundHttpClientTest.t4+OutboundHttpErrorMapperTest19/19 PASS. - 잔여 위험: factory 교체(Apache/Jetty 등) 시 래핑 구조가 달라질 수 있음 — 계약 테스트가 회귀를 잡음.
회고 / Lessons
- 빨리 감지하는 신호: "DNS 실패가 CONNECT_FAILED 로 잡힘" / 분류 테스트에서 인접 카테고리 오분류 → 예외 cause chain 전체를 덤프해 래핑 구조부터 확인.
- 예방 체크리스트: 예외 분류기는 "타입 우선순위" 와 "체인 등장 순서" 를 분리해 설계 — 특정 근원(DNS)이 래퍼(connect)보다 우선해야 하면 서브 체인 스캔을 명시.
- wiki 일반화 후보: "cause-chain 기반 예외 분류기의 우선순위 함정" (wiki/concepts 추출 후보).
Related / 관련
- 같은 red 라운드에서 발견된 인접 오분류: connect-refused 가
HttpConnectTimeoutException extends HttpTimeoutException상속 때문에 TIMEOUT 으로 새는 문제 — 분류 규칙 순서(connect-timeout 을 read-timeout 보다 먼저)로 해결 (branch note §Cluster Errors 기록).