기본 콘텐츠로 건너뛰기

Propagate -X to child processes

`multiprocessing` 자식 프로세스에서만 -X 옵션이 사라진다면: PR #146005가 정리한 “인터프리터 플래그 전달” meta_description: CPython PR #146005는 subprocess/multiprocessing이 자식 파이썬 프로세스를 띄울 때 -X 옵션을 누락 없이 전달하도록 수정한다. dev/tracemalloc/importtime 등 런타임 디버그 옵션이 부모에서는 켜졌는데 자식에서만 꺼지는 문제를 줄인다. 운영/디버깅 관점에서 어떤 증상이 사라지는지, 어떤 -X 옵션이 특히 중요하며, 호환성/주의점은 무엇인지 정리한다. meta_keywords: python, multiprocessing, subprocess, -X, xoptions, dev mode, tracemalloc, importtime, faulthandler, debugging, child process, CI, production, practical meta_robots: index,follow 운영 디버깅에서 제일 짜증나는 버그 유형이 있다. 부모 프로세스에서는 디버그 옵션이 켜져 있는데 자식 프로세스(특히 multiprocessing 으로 띄운)에서는 꺼져 있다 그래서 로그가 갈라지고, 재현이 흐려진다. -X tracemalloc 은 켰는데, 자식에서 스냅샷이 없다 -X importtime 켰는데, 자식 프로세스의 import 시간은 안 찍힌다 -X dev 켰는데, 자식에서만 경고가 안 뜬다 PR #146005(gh-146004)는 이 ‘갈라짐’을 줄이는 방향으로 subprocess 쪽 플래그 전달을 정리한다. 핵심은 간단하다. 부모 인터프리터에 설정된 -X 옵션(xoptions)을, 자식 파이썬 프로세스 실행 인자에 일관되게 전달한다. 1) PR #146005가 바꾼 것: “일부만 전달” → “전부 전달(정렬해서)” diff의 핵심은 Lib/subprocess.py ...
최근 글

OrderedDict popitem leak fix

`OrderedDict.popitem()`을 돌렸는데 메모리가 내려오지 않는다: PR #146537이 손본 ‘가능한 누수’의 실전 증상 meta_description: CPython PR #146537은 OrderedDict의 popitem() 경로에서 발생할 수 있는 메모리 누수 가능성을 수정한다. 반복 popitem을 쓰는 캐시/큐/스케줄러에서 “요소는 지웠는데 RSS가 안 내려가는” 현상이 왜 나올 수 있는지, 어떤 버전에서 조심해야 하는지, 그리고 운영 코드에서 진단(tracemalloc/heap snapshot)과 완화(업그레이드, 패턴 변경)를 실무적으로 정리한다. meta_keywords: python, OrderedDict, popitem, memory leak, RSS, tracemalloc, CPython, cache, queue, eviction, dict, reference, practical, 운영, 진단 meta_robots: index,follow 운영에서 메모리 이슈는 늘 불쾌하다. 특히 더 불쾌한 건 이런 형태다. 캐시에서 항목을 “계속 지운다” 모니터링 그래프에서 객체 개수도 줄어든다 그런데 RSS가 안 내려온다(혹은 아주 천천히만) 이때 팀은 서로 다른 결론으로 갈라진다. “파이썬은 원래 메모리 반환을 안 해” “어딘가 참조가 남았어(진짜 leak)” “컨테이너가 찢어져서 조각난 거야(fragmentation)” 정답은 케이스 바이 케이스인데, 이런 논쟁의 바닥엔 늘 같은 질문이 있다. “내 코드가 지운 게 진짜로 지워졌나?” CPython PR #146537은 이 질문에 직접 연결되는 작은 수정이다. [3.14] Fix possible memory leak in OrderedDict popitem 이 글은 PR을 곧이곧대로 요약하기보다, “운영에서 popitem이 등장하는 상황”에 맞춰서 설명한다. 1) 왜 popitem() 이 실무에서 중요하...

Empty WHEEL_PKG_DIR: ensurepip CWD trap

`WHEEL_PKG_DIR=''`가 CWD를 훑는 순간: PR #146357이 막아준 ensurepip의 ‘빈 문자열 함정’ meta_description: CPython PR #146357은 ensurepip에서 WHEEL_PKG_DIR 이 빈 문자열일 때 Path('') 가 현재 작업 디렉터리(CWD)로 해석되어, 의도치 않게 CWD에서 wheel 파일을 찾는 문제를 수정한다. 운영/빌드 환경에서 설정 값이 빈 문자열로 들어가는 흔한 케이스와, 이를 방어하는 패턴(환경변수/빌드 플래그 검증, 경로 truthiness 체크)을 실무 관점으로 정리한다. meta_keywords: python, ensurepip, WHEEL_PKG_DIR, sysconfig, wheel, pip, virtualenv, build, CWD, Path(’’), environment variable, packaging, security, CI, practical, 운영 meta_robots: index,follow ensurepip 은 평소엔 존재감이 없다가, 한 번 문제를 만나면 굉장히 현실적인 방식으로 사람을 괴롭힌다. “왜 이 환경에선 pip 부트스트랩이 이상하지?” “왜 현재 폴더에서 wheel을 찾지?” “왜 CI에서만 재현되지?” PR #146357(gh-146310)은 그 중에서도 아주 작은 조건 하나 가 얼마나 큰 행동 변화를 만드는지를 보여준다. 핵심은 이거다. WHEEL_PKG_DIR 가 빈 문자열( '' )이면, Path('') 가 CWD로 해석되어 ensurepip이 현재 디렉터리 에서 wheel 파일을 찾게 될 수 있다. 이건 “버그 같다”로 끝나는 문제가 아니다. 빌드/패키징 파이프라인이 예기치 않게 CWD에 의존하게 되고 특정 폴더에서만 동작이 달라지고 보안/무결성 관점에서도 찝찝한 동작이 된다 1) PR #146357이 ...

JSON Array Hook: Control Lists

`json.loads()`가 리스트를 그냥 리스트로만 만들던 시대가 끝난다: PR #146441의 `array_hook` 실전 용도 meta_description: CPython PR #146441은 JSON 디코더에 array_hook 파라미터를 추가해, JSON 배열을 파싱할 때 리스트 대신 원하는 타입으로 변환할 수 있게 했다. tuple/커스텀 리스트/검증 래퍼 등으로 일괄 변환하는 방법과, object_hook/object_pairs_hook과의 차이, 운영 코드에서 실수하기 쉬운 포인트(성능/메모리/예외 처리)를 정리한다. meta_keywords: python, json, loads, JSONDecoder, array_hook, object_hook, object_pairs_hook, decoder, parsing, tuple, list, validation, performance, CPython, practical, 운영, 변환 meta_robots: index,follow JSON을 오래 만져본 사람은 다 안다. object_hook 는 있다(딕셔너리 후처리) object_pairs_hook 도 있다(순서 보존/중복 키 처리) 근데 이상하게도 “배열”에는 훅이 없었다. 그래서 우리는 늘 한 번 더 손을 댔다. loads() 로 일단 파싱하고 리스트를 전부 걷어내며(재귀) tuple로 바꾸거나, 커스텀 컨테이너로 감싸거나, 값 검증을 한다 PR #146441(gh-146440)은 그 빈 칸을 메운다. JSON 디코더에 array_hook 가 들어간다. 이제 “배열이 파싱되는 순간” 리스트를 다른 타입으로 바꾸는 길이 열린다. 1) 뭐가 추가됐나: load/loads 와 JSONDecoder 에 array_hook diff를 보면 핵심은 단순하다. json.load() / json.loads() 에 array_hook= 인자가 추가된다 json.JSONDeco...

Audit Hooks Can Leak: Socket Fix

감사(audit) 훅이 소켓을 느리게 죽인다: PR #146248이 보여준 관측 코드의 메모리 누수 패턴 meta_description: CPython PR #146248은 socket 모듈에서 audit hook을 통해 참조(reference)와 버퍼(buffer)가 새는 문제를 수정했다. 보안/관측을 위해 sys.addaudithook를 붙였는데, 의도치 않게 객체 수명을 늘려 메모리 누수처럼 보일 수 있다. 운영 코드에서 audit hook를 안전하게 쓰는 패턴(최소 정보만 복사, 객체 저장 금지, 버퍼 처리, 샘플링/비동기화)을 정리한다. meta_keywords: python, socket, audit hook, sys.addaudithook, CPython, memory leak, reference leak, buffer leak, observability, security, GC, tracemalloc, weakref, logging, sampling, asyncio, 운영, 메모리 meta_robots: index,follow 운영에서 메모리가 서서히 오르는 서비스를 잡다 보면, 진짜 원인이 비즈니스 로직이 아닌 경우가 꽤 많다. 디버그 로그 트레이싱/계측(telemetry) 보안/감사(audit) 즉, 관측 코드 가 시스템을 갉아먹는 케이스. CPython PR #146248(gh-146245)은 이걸 아주 교과서적으로 보여준다. socket 모듈이 audit hook과 엮이는 과정에서 reference/buffer leak 이 생길 수 있었고, 그걸 고쳤다. 이 글은 PR을 번역하려는 글이 아니다. 실무에서 바로 쓰는 관점으로 정리한다. audit hook가 왜 메모리 누수처럼 보일 수 있는지 sys.addaudithook를 붙일 때 뭘 저장하면 위험한지 로그를 남기면서도 객체 수명을 늘리지 않는 방법 1) PR #146248 한 줄 요약: 감사 훅이 데이터를 잡고 있었다 ...

Shell Returncodes: -N vs 128+N

`shell=True`인데 returncode가 음수가 아니라서 당황했다면: PR #146255가 정리한 “셸의 책임” meta_description: POSIX에서 subprocess의 returncode는 보통 신호 종료면 -N이라고 배웠지만, shell=True 나 asyncio.create_subprocess_shell() 에서는 그 규칙이 깨질 수 있다. CPython PR #146255는 “returncode는 셸의 종료 상태를 반영하며, 신호를 128+N 같은 코드로 매핑할 수 있다”는 점을 문서로 명확히 했다. 운영 코드에서 재현/로깅/알람을 어떻게 설계해야 덜 흔들리는지 정리한다. meta_keywords: subprocess, asyncio, returncode, shell=True, create_subprocess_shell, create_subprocess_exec, POSIX, signal, 128+N, Bash, exit status, SIGTERM, SIGKILL, 프로세스, 종료코드, 운영, 재현, 로깅, 알람, 파이썬 meta_robots: index,follow 운영하다 보면 이 상황을 한 번은 만난다. 프로세스가 SIGTERM으로 죽었으니 returncode == -15 일 거라고 생각했는데 실제 로그에는 143 이 찍혀 있다(= 128 + 15) 어떤 경우는 또 -15 로 찍힌다 그래서 대시보드가 갈라지고, “이번 장애는 신호 종료냐? 정상 종료냐?” 분류가 흔들린다. CPython PR #146255는 이 혼란의 원인을 문서로 깔끔하게 정리한다. 핵심은 한 줄이다. shell=True 면 returncode는 ‘자식 프로세스’가 아니라 ‘셸(/bin/sh)의 종료 상태’를 반영한다. 셸은 신호를 그대로 “음수”로 내보내지 않을 수 있고, 대신 128+N 같은 규칙으로 매핑할 수 있다(참고자료). 1) PR #146255가 실제로 추가한 문장(요약) diff를...

When Exceptions Change: struct.Struct Boundaries

struct.Struct가 던지는 예외가 바뀌면, 내 장애 대응도 바뀐다: PR #145851 읽는 법 meta_description: CPython PR #145851은 struct.Struct의 내부 구현을 손보면서, 비ASCII 포맷 처리와 초기화되지 않은 객체의 속성 접근에서 던지는 예외 타입을 더 일관되게 정리했다. “예외 타입 하나쯤”이 테스트/에러 핸들링/호환성에서 어떤 차이를 만들 수 있는지, diff에서 어디를 보고 내 코드에 무엇을 점검할지 정리한다. meta_keywords: struct, Struct, format, ValueError, UnicodeEncodeError, UnicodeDecodeError, AttributeError, RuntimeError, CPython, PR145851, non-ASCII, surrogateescape, bytes, str, exception, 테스트, 호환성, 에러처리, 파이썬 meta_robots: index,follow 운영에서 제일 성가신 버그는 “같은 입력인데 어떤 환경에서만 터지는 것”이다. 그중에서도 예외 타입이 바뀌는 종류는 더 귀찮다. 코드는 except UnicodeEncodeError: 로 잡고 있었는데, 어느 날부터 ValueError 가 날아오고 테스트는 “정확한 예외 클래스”를 기대하고 있어서 갑자기 줄줄이 깨지고 로그/알람은 예외 이름을 키로 집계해서, 그래프가 한순간에 찢어진다 CPython PR #145851은 딱 그 종류의 변화다. 겉으로는 “구현 디테일 변경”인데, 실제로는 예외의 계약(contract) 을 조금 더 선명하게 만든다. PR이 말하는 변화 3가지(그리고 왜 실무에선 체감이 큰지) PR 페이지 요약과 diff를 보면 변화는 세 묶음으로 정리된다. 1) non-ASCII 문자열 포맷 을 넣을 때: UnicodeEncodeError 대신 ValueError 2) non-ASCII bytes 포맷 을...