본문으로 건너뛰기

[CPython] OrderedDict.popitem()의 메모리 누수 수정

PR 링크: python/cpython#146538 (3.13 backport), #146537 (3.14 backport) 상태: Merged | 변경: +3 / -1

들어가며

CPython의 C 확장 코드에서 참조 카운트(reference counting) 관리는 메모리 안전성의 핵심입니다. 이 PR은 OrderedDict.popitem()에서 에러 발생 시 key 객체의 참조를 해제하지 않아 메모리가 누수되던 버그를 수정합니다.

핵심 코드 분석

Before:

// Objects/odictobject.c
node = last ? _odict_LAST(self) : _odict_FIRST(self);
key = Py_NewRef(_odictnode_KEY(node));
value = _odict_popkey_hash((PyObject *)self, key, NULL, _odictnode_HASH(node));
if (value == NULL)
    return NULL;
item = PyTuple_Pack(2, key, value);
Py_DECREF(key);
Py_DECREF(value);

After:

node = last ? _odict_LAST(self) : _odict_FIRST(self);
key = Py_NewRef(_odictnode_KEY(node));
value = _odict_popkey_hash((PyObject *)self, key, NULL, _odictnode_HASH(node));
if (value == NULL) {
    Py_DECREF(key);
    return NULL;
}
item = PyTuple_Pack(2, key, value);
Py_DECREF(key);
Py_DECREF(value);

문제의 핵심은 Py_NewRef로 증가시킨 key의 참조 카운트입니다. 정상 경로에서는 함수 끝의 Py_DECREF(key)로 해제되지만, _odict_popkey_hash가 실패하여 value == NULL인 경우 return NULL로 즉시 반환하면서 Py_DECREF(key)를 건너뛰게 됩니다.

수정은 단순합니다: 에러 경로에서도 Py_DECREF(key)를 호출합니다.

왜 이게 좋은가

  • 3줄로 메모리 누수 해결: 중괄호 추가와 Py_DECREF(key) 한 줄이 전부입니다. 에러 경로의 리소스 정리는 항상 누락되기 쉬운 부분입니다.
  • 패턴 인식: Py_NewRef → 중간 연산 → Py_DECREF 패턴에서 중간 연산이 실패하면 DECREF에 도달하지 못합니다. C 확장 개발의 전형적인 함정입니다.
  • 동일 수정 2개 브랜치: 3.13과 3.14 모두에 backport되어 모든 지원 버전에서 수정됩니다.

정리

CPython의 참조 카운트 기반 메모리 관리에서 에러 경로의 Py_DECREF 누락은 가장 흔한 메모리 누수 패턴입니다. 이 PR은 OrderedDict.popitem()의 에러 경로에서 key 참조를 올바르게 해제하여 누수를 방지합니다.

참고 자료


이 포스트는 AI가 작성하였으며, 사실과 다를 수 있습니다. 정확한 정보는 원본 PR을 참고해 주세요.

댓글

관련 포스트

PR Analysis 의 다른글