The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.1 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-a13-f007 | "상한을 두고 읽는다"는 본문 핸들러가 전부 읽은 뒤에 자른다 | notification-and-delivery | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:analysis-finding-a13-f007 | 2026-09-01 | case-analysis-finding-a13-f007.body.md |
|
|
|
"상한을 두고 읽는다"는 본문 핸들러가 전부 읽은 뒤에 자른다
자바독이 상한에서 읽기를 멈춘다고 적는다. 구현은 무제한 상류 구독자의 결과에 자르기 함수를 얹는다. 자르기는 전체가 힙에 모인 다음에 일어난다. 호출부 주석이 쓰지 말라고 명시한 바로 그 상류를 두 줄 아래 함수가 쓴다.
관계
- 음수 Retry-After 가 확정 거절을 영구 중지되는 모호한 제출로 바꾼다 같은 리프의 다른 원격 입력 사례다.
- 이름이 상한을 말한다고 상한이 걸리는 것은 아니다 이 사례가 그 규칙의 형태다.
- 두 연산자가 이름만 있고 아무것도 하지 않는다 같은 계열의 서술과 코드 어긋남이다.
문제
응답 본문을 읽는 처리기가 있다. 자바독이 상한에서 읽기를 멈추는 본문 처리기라고 적는다.
호출부 주석도 이유를 길게 적는다.
제공자 응답은 진단이라는 것이다. 상태와 헤더 몇 개와 오류 문서라는 것이다. 상한 없이 읽으면 보내는 쪽의 힙이 저쪽이 보내기로 한 양의 함수가 된다는 것이다. 끝나지 않는 조각 응답은 요청 하나짜리 장애라는 것이다.
결론
구현이 그것을 하지 않는다.
바이트 배열 구독자를 상류로 두고 그 결과에 자르기 함수를 얹는다.
사상 구독자의 마무리 함수는 상류가 완료된 뒤 그 결과에 적용된다.
상류는 무제한으로 요청하여 본문 전체를 힙에 모은다. 자르기는 그다음이다.
호출부 주석이 막겠다고 선언한 것과 정확히 반대다.
주석은 그 상류를 쓰지 말라고 쓰여 있고, 두 줄 아래 함수가 그것을 상류로 쓴다.
실패 시나리오는 이렇다.
제공자나 그 자리에 들어온 무엇이든 응답으로 큰 본문을 빠르게 보낸다.
요청 시간 제한이 시간은 제한하므로 끝나지 않는 조각 응답은 시간 제한에서 끊긴다.
그러나 주석이 두 번째로 든 위험은 그대로다. 보내는 쪽의 힙이 저쪽이 보내기로 한 양의 함수가 되는 상황이다.
시간 제한 안에 수 기가바이트를 받을 수 있는 연결에서는 그만큼이 전부 배열로 쌓인 뒤 상한 크기로 잘린다.
동시 발송이 많을수록 배수로 늘어난다. 이 리프는 발송 작업자가 가상 스레드로 퍼져 나간다.
판정은 P2 다.
검증 환경
OpenJDK : 21.0.12 확인 방식 : 구현과 자바독, 호출부 주석 대조 소스 수정 : x
재현 조건
원문은 final/evidence/raw/173 계열에 있다.
- 본문 처리기의 자바독을 읽는다.
- 구현이 어떤 상류 구독자를 쓰는지 확인한다.
- 사상 구독자의 마무리 함수가 언제 적용되는지 확인한다.
- 호출부 주석을 읽는다.
- 주석이 배제한 상류와 구현의 상류를 대조한다.
본문
본문 핸들러는 다음이다.
// JdkNotificationHttpGateway.java:116-128
/**
* A body handler that stops reading at the cap.
*/
private static HttpResponse.BodyHandler<byte[]> boundedBody(int maxBytes) {
return responseInfo ->
HttpResponse.BodySubscribers.mapping(
HttpResponse.BodySubscribers.ofByteArray(),
body -> body.length <= maxBytes ? body : java.util.Arrays.copyOf(body, maxBytes));
}
본문 핸들러가 도는 순서
:::evidence key="analysis-finding-a13-f007" alt="분석 문서 analysis/13-adapter-outbound-notification.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/13-adapter-outbound-notification.md 발췌 — 15줄" zoom="true" :::
finisher는 upstream이 끝난 뒤에 돈다
BodySubscribers.mapping(upstream, finisher)의 finisher는 upstream이 완료된 뒤 그 결과에 적용된다. upstream은 ofByteArray()이고, 그것은 무제한으로 요청하여 본문 전체를 힙에 모은다. 잘라내기는 그 다음이다.
호출부 주석이 선언한 것과 정확히 반대다
:60-62 — "Bounded, not ofByteArray(). A provider response is diagnostic — a status, some headers, an…". P2.
확인하지 못한 것
큰 응답을 보내는 가짜 제공자로 힙 증가를 측정하지 않았다. 구독자 규약상 그 결과가 나온다.