Repository navigation
fix(python): 3.14 의 응답 버퍼 선읽기가 바이트 예산을 본문 상한보다 먼저 터뜨렸다 — 예산이 걸린 원문 읽기를 8 KiB 로 묶는다 - #925
Merged
Merged
Conversation
… 로 묶는다
CI Python 3.14(3.14.9)에서 test_token_response_cap.py::test_a_huge_body_is_refused_holding_only_what_was_read
의 청크 레인이 간헐적으로 실패했다 — 16 MiB 토큰 응답을 `KeycloakAuthError("… exceeds 1048576 bytes")`
대신 `KeycloakTransportError("Can't connect to server")` 로 보고했다. run 41170195060 시도 1(cc-chunked)·
시도 2(cc-chunked·admin-chunked), run 41025781639 시도 1(introspect-chunked)·시도 2(admin-chunked).
같은 실행들의 3.10~3.13 은 초록.
원인 — `_internal/wire.py` 의 응답당 바이트 예산(본문 상한 + 틀 몫 65,536 = 1,114,112)은 http.client
응답 버퍼(`sock.makefile("rb")`, 크기 `io.DEFAULT_BUFFER_SIZE`) 아래의 원문 `readinto` 에서 센다 —
버퍼가 미리 받아 http.client 가 아직 쓰지 않은 바이트도 센다.
- Docker 프로브: python:3.14-slim(3.14.10) 131,072 · python:3.13-slim(3.13.18) 8,192. `readline`·
`read(2)`(청크 끝 CRLF)가 원문에 청하는 크기도 같다. 빈 버퍼의 `read1(n)` 은 n 그대로라 길이
레인은 머리 뒤로 미리 읽지 않는다(CI 실패도 청크 레인뿐).
- 그래서 3.14 에서는 상한 아래 64 KiB 안의 청크 경계에서 버퍼가 차면 본문이 1,048,577 바이트에
닿기 전에 예산이 끊는다. 실 3.14.10 에서 사슬을 읽었다(경계가 떼기 전의 원인을 읽는 진단
플러그인 · 수정 전 원래 시험 20 회 중 실패 7 건 전부): KeycloakConnectionError("Can't connect to
server") ← urllib3 ProtocolError ← http.client.HTTPException("HTTP response exceeds 1114112 bytes
on the wire"). `lower.refused_body` 가 원인에서 `ResponseRefused` 를 못 찾아 전송 오류가 된다.
수정 — 예산이 걸린 동안 원문 읽기 하나가 청하는 바이트를 8 KiB(`_READ_AHEAD_BYTES` = 3.13 까지의
`io.DEFAULT_BUFFER_SIZE`)로 묶는다. 「이번 읽기가 청해도 되는 바이트」의 정의인 `exchange` 의
`room()` 을 `min(남은 예산 + 1, 8,192)` 로 — `_metered.readinto` 가 이미 그 값으로 버퍼를 자른다.
버퍼의 선읽기는 원문 읽기 하나를 넘지 않으므로 8 KiB 이하가 되고, 머리·청크 틀이 나머지 56 KiB
안이면 상한 너머 본문은 판과 무관하게 본문 상한이 먼저 판정한다. 예산이 없거나 풀린 본문(admin
성공 본문의 `lift`)은 묶지 않는다. 문서화된 수(1,114,112 · 116,736 · 65,536)는 그대로다.
기각 — 틀 몫을 버퍼 크기만큼 늘리기(문서화된 수와 교차 언어 65,536 이 바뀐다) · http.client 의
버퍼 갈아 끼우기(응답의 `fp` 를 바꾼다 — 침습적).
시험 — 타이밍에 기대지 않는 RED 둘:
- test_wire.py::test_a_budgeted_read_asks_for_8_kib_however_large_the_buffer — 131,072 버퍼를 늘
꽉 채우는 가짜 원문 읽기: 예산 아래 첫 읽기는 8,192 · `lift` 뒤와 예산 없는 교환은 131,072.
- test_token_response_cap.py::test_the_body_cap_wins_over_buffer_read_ahead[레인 다섯 × 길이·청크] —
`makefile()` 기본 버퍼 131,072(3.14 와 같게) · 클라이언트 응답의 원문 읽기는 늘 꽉 참(서버가
한참 앞섬) · 첫 조각을 상한 512 바이트 아래까지 늘려 버퍼가 빈 청크 경계를 상한 바로 아래에
둔다. 수정 전에는 그 경계의 채우기가 예산 + 1 에 닿는다. 길이 레인은 대조군.
실측(RED → GREEN):
- 로컬 Windows 3.13.11: 수정 전 20 회 모두 같은 6 실패(청크 다섯 + 화이트박스 — 120/120)·길이 다섯
통과, 수정 뒤 20 회 모두 11 통과.
- Docker(LF 트리, 새 시험 둘 × 3 회): 3.10.22 · 3.11.18 · 3.12.16 · 3.13.18 · 3.14.10 모두 수정 전
「6 failed, 5 passed」 3/3 → 수정 뒤 「11 passed」 3/3.
- 원래 시험, 실 3.14.10 Docker 30 회 반복(-k test_a_huge_body_is_refused_holding_only_what_was_read):
수정 전 30 회 중 12 회 실패 — 청크 사례 150 개 중 16 실패(admin 5 · cc 3 · code 3 · refresh 3 ·
introspect 2), 길이 사례 150 개 중 0. 수정 뒤 30 회 중 0 회 — 30 회 모두 「10 passed」.
(같은 반복을 CRLF 스냅숏에서 먼저 돌렸을 때 수정 전 9/30 회 · 14/150.)
게이트 — 로컬 3.13.11(워크트리의 새 venv, keycloak_sdk 를 워크트리에서 import): pytest -m "not
integration" --cov=keycloak_sdk 1545 passed · 커버리지 100.00% · ruff check · ruff format --check ·
mypy src(strict) · check-docs --strict rc=0. Docker 3.14.10(CI 단계 그대로 — `--only-binary :all:`
설치 · urllib3 2.8.0 · requests 2.34.2): ruff check · ruff format --check · 1545 passed · 커버리지
100.00% · mypy src.
문서 — wire.py 머리말에 ⚠️ 한 절(묶음과 이유), `.claude/rules/python.md` Gotchas 에 한 줄(3.14 버퍼
선읽기 → 시험 포인터). 예산 4,690 B 는 올리지 않고 같은 파일에서 바꿨다(적재 4,676 → 4,686 B):
`_internal/` 예시 나열(추출 가능한 사실 — 지금 16 모듈 중 셋) · py.typed 측정(2 → 0)을 되풀이한 구
하나 · 리다이렉트 절에서 앞 문장을 되풀이한 두 문장.
[no-changelog: 미게시 결함 — 바이트 예산은 py-v1.0.6 에 없었고 1.0.7 은 아직 게시 전이라 소비자에게 보이는 변화가 없다]
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS4tpj7usahf8MK5dXo
|
Owner
Author
|
PM 재측정(구현자와 다른 실행):
경계 판정(코드): 빨강 둘은 php-mirror( |
xzawed
added a commit
that referenced
this pull request
Oct 31, 2026
step 8 확인 뒤(node scripts/check-registry-truth.mjs, 2026-10-31): LIVE python keycloak-sdk 1.0.7. 게시 바이트 ↔ 태그(내려받아 대조, autocrlf 끈 git archive — scratch bytes.sh): wheel · sdist 의 keycloak_sdk/ == git archive py-v1.0.7 python/src/keycloak_sdk(42 파일 — #925 의 3.14 수정 포함). 선행 조건: python 은 태그만 — 있다. python 은 3.14 회귀(#925) 수정 뒤에 요청했다(#926 → py-v1.0.7 = 9b68dd7). node·.NET·ruby 는 #924. java · kotlin(Portal Publish) · php(미러 토큰)는 게시 뒤에 따로 올린다. 틀: #863 의 python 줄. 가드: test-deploy-facts 184/0 · test-publication-claims(dash, LANG=) 241/0 · check-versions rc 0 · test-check-versions 99/0 · check-docs --strict rc 0. 진짜 검증은 이 PR 의 griffe 가 py-v1.0.7 과 비교하는 실행이다. Claude-Session: https://claude.ai/code/session_01R4jYS4tpj7usahf8MK5dXo Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



무엇
Python 3.14 에서 상한(1,048,576 B)을 넘는 토큰·introspection·logout·admin 그랜트 응답이 가끔
KeycloakAuthError("… exceeds 1048576 bytes")대신KeycloakTransportError("Can't connect to server")로 보고됐다. 바이트 예산(_internal/wire.py, py-v1.0.6 뒤 #893 에서 들어옴 — 미게시)이 본문 상한보다 먼저 터졌기 때문이다. 예산이 걸린 동안 원문 읽기 하나를 8 KiB 로 묶어, 판과 무관하게 본문 상한이 먼저 판정하게 한다. 문서화된 숫자(65,536 · 1,114,112 · 116,736)는 그대로다.python 1.0.7 의 릴리스 요청은 이 PR 뒤에 낸다(일곱째 물결의 나머지 여덟은 진행 중).
원인 (측정)
io.DEFAULT_BUFFER_SIZE: Python 3.14.10 = 131,072 · 3.13.18 = 8,192 (Docker). http.client 응답 버퍼는sock.makefile("rb")라 그 크기다.readinto를 센다 → 3.14 에서 청크 경계의 채우기 하나(readline·CRLFread(2))가 131,072 B 를 청해, 서버가 앞서 있으면 미리 받은 몫이 틀 몫(65,536)을 넘었다.KeycloakConnectionError("Can't connect to server")← urllib3ProtocolError←HTTPException("HTTP response exceeds 1114112 bytes on the wire")— 본문 상한이 아니라 예산.수정
room()=min(남은 예산 + 1, _READ_AHEAD_BYTES = 8_192)(예산이 없거나lift뒤면None— admin 성공 본문은 묶지 않는다). 버리친 대안: 틀 몫을 버퍼 크기만큼 키우기(문서화된 숫자·교차언어 동형이 바뀐다) · http.client 버퍼 교체(침습적).시험 (RED → GREEN)
새 결정적 시험 둘 —
test_wire.py::test_a_budgeted_read_asks_for_8_kib_however_large_the_buffer(131,072 B 버퍼를 늘 채우는 가짜 읽기) ·test_token_response_cap.py::test_the_body_cap_wins_over_buffer_read_ahead(다섯 레인 × content-length/chunked, 버퍼 131,072 강제 · 소켓 읽기가 늘 가득 · 청크 경계를 상한 바로 아래에).wire.py대 이 커밋게이트(구현자): pytest 1545 passed · 커버리지 100.00% · ruff · ruff format · mypy strict — 로컬 3.13 과 3.14 컨테이너 모두. check-docs rc 0.
문서
wire.py머리말에.claude/rules/python.mdGotchas 에 한 줄(3.14 의 131,072 B 선읽기 · 시험 포인터) — doc-budget(4,690 B)은 올리지 않고 같은 파일의 되풀이 세 군데를 깎아 맞췄다(판정 방법 — mypy 2 → 0 측정 ·client_secret이 든 POST 본문 — 은 그대로).[no-changelog: 미게시 결함 — 바이트 예산은 py-v1.0.6 에 없었고 1.0.7 은 아직 게시 전이라 소비자에게 보이는 변화가 없다]🤖 Generated with Claude Code
https://claude.ai/code/session_01R4jYS4tpj7usahf8MK5dXo