Watch at Ease세상을 읽고, 일상의 답을 찾다.

기술 · 실용 가이드

zlib가 eof에서도 계속 도나요? 남은 입력과 다음 gzip 멤버를 구분하세요

로컬 검사28개로 종료 후 꼬리 재입력, 첫 멤버만 출력하는 경우, 평문을 전부 내고도 미완료인 두 번째 멤버를 구분했습니다.

자체 제작 설명 일러스트: 종료 후 꼬리를 세 번 재입력하면 출력 없이 unused_data만 늘어납니다.
자체 제작 설명 일러스트: CPython 3.12.14/zlib 1.3.2 실측이며 무한 실행 대신 세 번으로 제한했습니다. Watch at Ease

해제 객체가 eof=True여도 gzip 입력에는 다음 멤버가 남을 수 있습니다. 설치된 CPython 3.12.14에서는 eof 뒤에도 unconsumed_tail이 남았고, 이를 종료한 같은 객체에 반복해서 주자 새 출력 없이 unused_data에 뒤쪽 바이트가 계속 추가됐습니다.

한 멤버가 끝난 뒤에도 반복하는 문제와 너무 일찍 멈춰 다음 멤버를 누락하는 문제는 다릅니다. 작은 대조28개에서는 두 번째 멤버가 잘려도 두 멤버의 예상 평문9바이트가 모두 나오지만 두 번째 eof는 False인 경우도 확인했습니다.

1. 출력 길이와 함께 객체 상태 기록하기

sys.version, zlib.ZLIB_VERSION, zlib.ZLIB_RUNTIME_VERSION, wbits와 단일·다중 멤버 허용 정책을 기록합니다. 실측은 CPython 3.12.14/Linux이며 빌드·실행 zlib는 모두1.3.2였습니다. 참고한 C 라이브러리 상위 매뉴얼은1.3.1이며 실행 버전과 구분했습니다.

작은 진단 호출마다 출력 길이, eof, unconsumed_tail·unused_data 길이를 기록하세요. eof 전에는 max_length가 출력을 제한해 남은 입력을 다시 처리해야 할 수 있습니다. eof 후에는 형식 정책에 따라 후행 바이트를 해석하고 두 속성을 무조건 같은 객체에 다시 주지 마세요.

2. 끝난 객체에 꼬리를 다시 넣지 않기

zlib 래퍼 테스트는 평문1024바이트와5바이트 접미사 extra를 포함했습니다. 호출당100바이트로 제한하고 not d.eof and d.unconsumed_tail인 동안만 계속하자 정확한 전체 페이로드를 얻었습니다. 이 설치 버전에서 완료 시 두 꼬리 속성 모두 extra를 담았습니다. 서로 독립적인 두 조각으로 더하면 안 됩니다.

그 뒤 남은 꼬리로 정확히 세 번만 추가 호출했습니다. 새 출력은 모두0이고 unused_data는5에서20바이트로 늘었으며 unconsumed_tail은5바이트 그대로였습니다. 무한 루프를 실행하지 않았습니다. 상위 이슈는 이미 eof 조건을 제시하고 수정 링크와 함께 닫혔습니다. 모든 최신 Python에 남은 새 결함이라고 하지 말고 실제 설치 버전을 시험하세요.

3. 멤버 종료를 전체 입력 성공과 구분하기

49바이트 gzip 테스트는 alpha를 담은25바이트 멤버와 beta를 담은24바이트 멤버를 연결했습니다. zlib.decompressobj(31) 하나는 alpha만 반환하고 eof=True였으며 unused_data에 정확한24바이트 다음 멤버를 남겼습니다. 이 무제한 출력 호출에서 unconsumed_tail은 비었습니다.

unused_data를 새 객체에 주면 beta를 얻고 eof에 도달하며 접미사가 남지 않았습니다. 작은 완전 테스트에 gzip.decompress를 쓰면 문서의 다중 멤버 동작대로 alphabeta가 나왔습니다. 이 API와 커뮤니티의 새 객체 방법은 이미 알려져 있습니다. 큰 외부 입력은 필요한 정책·제한을 갖춘 유지관리되는 리더를 선택하고 이 메모리 비교를 운영 접수 설계로 간주하지 마세요.

4. 빈 출력이나 예상 평문을 완료 증거로 삼지 않기

유효한 빈 gzip 멤버는 평문을 내지 않아도 eof에 도달하고 다음 멤버를 남깁니다. 빈 출력 블록을 전체 입력 끝으로 취급하는 루프는 유효한 후속 데이터를 놓칠 수 있습니다. 최근 출력 유무뿐 아니라 멤버 완료와 보류 입력을 추적하세요.

두 번째 멤버 트레일러4바이트를 제거해도 두 객체는 예상한 alphabeta9바이트를 모두 냈지만 두 번째 eof=False였습니다. flush()도 누락된 트레일러의 완료를 입증하지 못했습니다. 다른 대조에서 두 번째 CRC를 손상시켜도 첫 객체는 alpha를 반환했고 두 번째 멤버를 처리할 때 비로소 오류가 났습니다.

5. 허용할 멤버와 후행 데이터 정책 정하기

프로토콜은 완전한 단일 멤버만 허용하고 모든 후행 바이트를 거부할 수도, 연결 멤버를 허용하고 각 멤버에 새 디코더를 요구할 수도 있습니다. unused_data를 버리거나 임의 접미사를 유효한 새 멤버로 가정하거나 겹칠 수 있는 unconsumed_tail과 더하지 마세요. garbage 접미사는 새 gzip 멤버 처리에 실패했습니다.

출력 예산은 허용한 전체 멤버에 걸쳐 누적하고 실제 리더에 맞춰 입력, 멤버 수, 시간, 상위 버퍼를 제어하세요. max_length는 호출당 제한이고0은 무제한입니다. flush(length)는 초기 버퍼 크기이며 상한이 아닙니다. 테스트는 max_length=0에서1024바이트,1에서1바이트를 확인했습니다. 프로세스 메모리, CPU 제한, 네트워크 클라이언트는 측정하지 않았습니다.

6. 완료 검사가 뒷받침하는 결과만 확정하기

작은 알려진 입력으로 회귀 진단을 만드세요. 유효한 멤버 하나·둘, 빈 첫 멤버, 잘린 두 번째 트레일러, 잘못된 두 번째 CRC, 비멤버 접미사를 검사합니다. 정확한 출력, 허용한 각 멤버의 완료, 최종 후행 데이터 정책을 확인하고 유용한 접두부를 완전한 결과로 확정하지 마세요.

28개 단언이 모두 명시한 환경에서 통과했습니다. 보고서는 버전, 플랫폼, 스크립트 SHA, 실제 상태를 보존합니다. 일반 스트리밍 리더 구현, 수정 릴리스 실행, HTTP 완결성·진위·자원 안전성 검증은 아닙니다. 올바른 멤버 전환은 필요조건이며 애플리케이션은 전체 허용 입력의 완료를 따로 정의해야 합니다.

자체 제작 설명 일러스트: 첫 멤버 완료, 정상 두 번째, 잘린 두 번째의 평문과 eof를 비교합니다.
자체 제작 설명 일러스트: alphabeta 출력만으로 두 번째 트레일러 검증 완료를 증명할 수 없어 멤버별 상태를 확인합니다. Watch at Ease