Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/grpc-and-streaming/case/case-analysis-finding-a15-f001.md
T
DongHyeonkaandClaude Fable 5.1 b25357c48a docs(clean-architecture-backend-template): fold analysis into final and re-select one topic
- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다
- 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5
  (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를
  techviz 로 만들었다
- 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs
  돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다.
  Concept 이 인용한 코드가 SSOT 에 없어 뺐다
- candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 12:39:20 +09:00

6.7 KiB

kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, body, assets, evidence, source
kind slug title topic project status sourceRevision rootTreeNode evidenceCapturedOn body assets evidence source
CASE analysis-finding-a15-f001 원인 사슬 순회가 2-순환에서 무한 루프에 빠지고, 저장소는 이미 그 사례를 이름으로 적어 두었다 grpc-and-streaming clean-architecture-backend-template 게시 전 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 case:analysis-finding-a15-f001 2026-09-01 case-analysis-finding-a15-f001.body.md
key file
analysis-finding-a15-f001 ../../../final/evidence/rendered/analysis-finding-a15-f001.svg
../../../final/evidence/raw/analysis-finding-a15-f001.txt
원본 분석 절은 final/document.md#a15#L171 이다.

원인 사슬 순회가 2-순환에서 무한 루프에 빠지고, 저장소는 이미 그 사례를 이름으로 적어 두었다

오류 코드 판정의 종료 조건은 자기참조 하나다. 서로를 원인으로 갖는 두 예외에서는 그 조건이 참이 되지 않는다. 같은 저장소의 다른 모듈이 정확히 그 경우를 이름으로 적고 깊이 제한을 채택했다.

관계

  • 원인 사슬은 바깥부터 안쪽까지 걸어야 한다 같은 계열의 순회 규칙이다.
  • 자기참조 검사는 2-순환을 잡지 못한다 이 사례가 그 규칙의 형태다.
  • 같은 문제를 다른 모듈에서는 닫았다 기록하는 이유다.

문제

원격 호출 오류를 코드로 바꾸는 메서드가 원인 사슬을 순회한다.

종료 조건이 무엇인지 확인했다.

결론

자기참조 하나뿐이다. 현재 예외의 원인이 자기 자신이면 멈춘다.

서로를 원인으로 갖는 두 예외에서는 이 조건이 참이 되지 않는다.

현재 지점이 두 예외 사이를 무한히 순환한다.

이 사슬은 평범한 자바로 구성 가능하다. 하나를 만들고 그것을 원인으로 삼는 둘째를 만든 뒤, 첫째의 원인을 둘째로 지정하면 된다.

같은 저장소가 이 정확한 위험을 다른 모듈에서 이름으로 서술하고 다른 관용구를 택했다.

깊이 제한이지 순환 탐지가 아니라는 것이다. 원인 사슬은 순환일 수 있고 두 예외가 서로를 원인으로 지정한 경우가 그것이며, 제한 없는 순회 하나가 발송 스레드를 멈추게 한다는 것이다. 열이면 실제 전송 감싸기보다 훨씬 깊다는 것이다.

즉 그 주석은 자기참조 검사가 놓치는 바로 그 경우를 지목하고 깊이 제한을 그 이유로 채택한다.

저장소의 아홉 순회 지점 중 다섯이 깊이 제한이고 넷이 자기참조 검사다.

실패 시나리오는 이렇다.

기능 서비스가 순환 원인 사슬을 가진 라이브러리 예외를 전파한다. 일부 연결 풀과 재시도 감싸개가 실패 원인을 상호 참조하는 형태로 만든다.

오류 종료 경로가 그 예외를 상태에 실어 정제 종료로 보내고, 오류 코드 판정이 진입해 돌아오지 않는다.

처리기 스레드 하나가 처리기를 태우며 멈추고, 클라이언트는 응답도 상태도 받지 못한 채 마감까지 기다린다.

같은 예외가 반복되면 서버 스레드가 하나씩 소진된다.

나머지 세 지점의 영향도 비슷하다.

두 연결 끊김 탐지기는 요청 처리 중 클라이언트 끊김을 판정하는 곳이고, 트랜잭션 재시도 분류기는 재시도 여부를 판정하는 곳이다. 셋 다 요청 스레드 위에서 실행된다.

권고는 네 지점을 깊이 제한으로 통일하는 것이다.

다른 모듈의 형태가 이미 정본이고 그 근거까지 코드에 있다.

이 리프에서는 순회 반복문을 깊이 상한이 있는 형태로 바꾸면 닫힌다.

판정은 P2 다.

검증 환경

OpenJDK : 21.0.12 확인 방식 : 종료 조건 분석과 저장소 전체 순회 지점 계수 소스 수정 : x

재현 조건

원문은 final/evidence/raw/179 계열에 있다.

  1. 오류 코드 판정의 순회 반복문을 읽는다.
  2. 종료 조건을 확인한다.
  3. 두 예외가 서로를 원인으로 갖는 사슬을 만든다.
  4. 그 사슬에서 종료 조건이 참이 되는지 확인한다.
  5. 저장소의 다른 순회 지점을 세고 관용구를 분류한다.

본문

errorCodeOf의 종료 조건은 current.getCause() == current 하나다. 서로를 원인으로 갖는 두 예외(a.cause = b, b.cause = a)에서는 이 조건이 참이 되지 않고 current가 a→b→a→b로 무한히 순환한다. 이 사슬은 평범한 자바로 구성 가능하다 — a = new RuntimeException(); b = new RuntimeException(a); a.initCause(b);.

MvcDisconnectDetector 참조 위치

:::evidence key="analysis-finding-a15-f001" alt="코드베이스에서 MvcDisconnectDetector 를 검색한 출력 2줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="MvcDisconnectDetector 코드베이스 검색 — 2줄 · exit 0" zoom="true" :::

같은 저장소가 이 위험을 이름으로 적고 다른 관용구를 택했다

JdkNotificationHttpGateway:93-97 — "Depth-bounded rather than cycle-detecting: a cause chain can be circular (two exceptions each initCause'd to the other), and an unbounded walk over one hangs the dispatch thread. Ten is far deeper than any real transport wrapping."

저장소의 아홉 개 순회 지점 중 다섯이 깊이 제한이고 넷이 자기참조 검사다(§3.2).

실패 시나리오

feature gRPC 서비스가 순환 원인 사슬을 가진 라이브러리 예외를 전파한다. closeWithError가 그 예외를 Status.withCause에 실어 sanitizing close로 보내고, errorCodeOf가 진입해 돌아오지 않는다. gRPC 핸들러 스레드 하나가 CPU를 태우며 멈추고, 클라이언트는 응답도 상태도 받지 못한 채 데드라인까지 기다린다. 같은 예외가 반복되면 서버 스레드가 하나씩 소진된다.

나머지 세 지점의 영향도

MvcDisconnectDetectorWebFluxDisconnectDetector는 요청 처리 중 클라이언트 연결 끊김을 판정하는 곳이고, TransactionRetryClassifier는 트랜잭션 재시도 여부를 판정하는 곳이다. 셋 다 요청 스레드 위에서 실행된다. P2.

권고

네 지점을 깊이 제한으로 통일한다. JdkNotificationHttpGateway의 형태가 이미 정본이고 그 근거까지 코드에 있다. 이 leaf에서는 errorCodeOfwhilefor (int depth = 0; current != null && depth < 16; depth++, current = current.getCause())로 바꾸면 닫힌다.

확인하지 못한 것

순환 사슬을 실제로 흘려 스레드가 멈추는 것을 재현하지 않았다. 종료 조건상 그 결과가 나온다.