[Final] PEP 442 - Safe object finalization
원문 링크: PEP 442 - Safe object finalization
상태: Final 유형: Standards Track 작성일: 18-May-2013
파이썬 개발자를 위한 PEP 442 – 안전한 객체 마무리 (Safe Object Finalization)
요약 (Abstract)
이 PEP(Python Enhancement Proposal)는 현재 Python의 객체 마무리(finalization) 기능이 가진 한계를 다루는 것을 목표로 합니다. 그 목적은 객체 그래프(object graph) 내에서의 위치에 상관없이, 모든 객체에 대해 최종 처리기(finalizer)를 정의하고 실행할 수 있도록 하는 것입니다.
이 PEP는 Python 코드에 어떠한 변경도 요구하지 않습니다. 기존 최종 처리기를 가진 객체들은 자동으로 이 개선의 혜택을 받게 됩니다.
정의 (Definitions)
- 참조(Reference): 한 객체에서 다른 객체로 향하는 방향성 링크입니다. 참조 대상 객체는 참조를 가진 객체가 살아있는 동안, 그리고 참조가 해제되지 않는 한 메모리에 유지됩니다. 이 PEP는 약한 참조(weak reference)가 아닌 일반 참조에 중점을 둡니다.
- 약한 참조(Weak reference): 한 객체에서 다른 객체로 향하는 방향성 링크이지만, 참조 대상 객체를 메모리에 유지시키지 않습니다.
- 참조 순환(Reference cycle): 객체들 간의 방향성 링크로 이루어진 순환 부분 그래프(cyclic subgraph)로, 순수한 참조 카운트(reference-counting) 방식으로는 이 객체들이 회수되지 않도록 만듭니다.
- 순환 고립 객체(Cyclic isolate, CI): 외부로부터 어떤 객체도 참조되지 않으며, 하나 이상의 참조 순환을 포함하고, 객체들이 여전히 사용 가능한(non-broken) 상태인 객체들의 독립적인 부분 그래프입니다. 이 객체들은 각자의 최종 처리기에서 서로에게 접근할 수 있습니다.
- 순환 가비지 컬렉터(Cyclic garbage collector, GC): 순환 고립 객체를 감지하고 이를 순환 쓰레기(cyclic trash)로 만드는 장치입니다. 순환 쓰레기 내의 객체들은 결국 참조가 해제되고 참조 카운트가 0으로 떨어지면서 자연스럽게 소멸됩니다.
- 순환 쓰레기(Cyclic trash, CT): GC에 의해 객체들이 해제되기 시작한 이전 순환 고립 객체입니다. 순환 쓰레기 내의 객체들은 잠재적인 좀비(zombies) 상태입니다. 만약 Python 코드에서 접근될 경우, 이상한
AttributeError
부터 크래시까지 다양한 증상이 나타날 수 있습니다. - 좀비/손상된 객체(Zombie / broken object): 순환 쓰레기의 일부인 객체입니다. 이 용어는 객체가 안전하지 않다는 것을 강조합니다. 객체의 외부 참조가 해제되었을 수 있거나, 객체가 참조하는 다른 객체가 좀비 상태일 수 있습니다. 따라서 최종 처리기와 같은 임의의 코드에서 접근해서는 안 됩니다.
- 최종 처리기(Finalizer): 객체가 소멸될 때 호출되는 함수 또는 메서드입니다. 최종 처리기는 객체에 접근하여 객체가 보유하고 있던 자원(예: 뮤텍스 또는 파일 디스크립터)을 해제할 수 있습니다.
__del__
메서드가 대표적인 예입니다. - 부활(Resurrection): 최종 처리기가 순환 고립 객체 내의 객체에 대한 새로운 참조를 생성하는 과정입니다. 이는
__del__
메서드의 특이하지만 지원되는 부수 효과(side-effect)로 발생할 수 있습니다.
영향 (Impact)
이 PEP는 CPython 구현 세부 사항을 논의하지만, 마무리(finalization) 의미론의 변경은 전체 Python 생태계에 영향을 미칠 것으로 예상됩니다. 특히, 이 PEP는 “__del__
메서드를 가진 객체는 참조 순환의 일부가 되어서는 안 된다”는 현재의 지침을 더 이상 유효하지 않게 만듭니다.
이점 (Benefits)
이 PEP의 주요 이점은 __del__
메서드를 가진 객체나 finally
블록을 가진 제너레이터(generator)와 같은 최종 처리기를 가진 객체에 관한 것입니다. 이러한 객체들은 이제 참조 순환의 일부일지라도 회수될 수 있습니다.
이 PEP는 또한 다음과 같은 추가적인 이점을 위한 길을 닦습니다:
- 모듈 종료(module shutdown) 절차가 더 이상 전역 변수를
None
으로 설정할 필요가 없을 수도 있습니다. 이는 잘 알려진 짜증나는 문제들을 해결할 수 있습니다.
이 PEP는 다음의 의미론은 변경하지 않습니다:
- 참조 순환에 갇힌 약한 참조(weak references).
- 사용자 정의
tp_dealloc
함수를 가진 C 확장 타입(C extension types).
설명 (Description)
참조 카운트 방식 소멸 (Reference-counted disposal)
일반적인 참조 카운트 방식 소멸에서는 객체의 최종 처리기가 객체가 할당 해제되기 직전에 호출됩니다. 만약 최종 처리기가 객체를 부활(resurrect)시키면, 할당 해제는 중단됩니다.
그러나 객체가 이미 최종 처리된 경우, 최종 처리기는 호출되지 않습니다. 이는 좀비(zombies) 객체를 최종 처리하는 것을 방지합니다.
순환 고립 객체 소멸 (Disposal of cyclic isolates)
순환 고립 객체(CI)는 가비지 컬렉터(GC)에 의해 먼저 감지된 다음 소멸됩니다. 감지 단계는 변경되지 않으므로 여기서는 설명하지 않습니다. 전통적으로 CI의 소멸은 다음 순서로 작동했습니다:
- CI 객체에 대한 약한 참조가 해제되고, 콜백이 호출됩니다. 이 시점에서 객체는 여전히 안전하게 사용할 수 있습니다.
- GC가 CI 내부의 알려진 모든 참조를 체계적으로 끊으면서 (
tp_clear
함수 사용) CI는 CT(Cyclic Trash)가 됩니다. - 아무것도 하지 않습니다. 모든 CT 객체는 2단계에서 (참조 해제의 부수 효과로) 소멸되었어야 합니다. 이 컬렉션은 완료됩니다.
이 PEP는 CI 소멸을 다음 순서로 변경할 것을 제안합니다 (새로운 단계는 굵게 표시):
- CI 객체에 대한 약한 참조가 해제되고, 콜백이 호출됩니다. 이 시점에서 객체는 여전히 안전하게 사용할 수 있습니다.
- 모든 CI 객체의 최종 처리기(finalizer)가 호출됩니다.
- CI는 다시 한 번 순회되어 여전히 고립되어 있는지 확인합니다. 만약 CI 내의 최소 하나의 객체가 CI 외부에서 도달 가능하다고 판단되면, 이 컬렉션은 중단되고 전체 CI가 부활됩니다. 그렇지 않으면 다음으로 진행합니다.
- GC가 CI 내부의 알려진 모든 참조를 체계적으로 끊으면서 (
tp_clear
함수 사용) CI는 CT가 됩니다. - 아무것도 하지 않습니다. 모든 CT 객체는 4단계에서 (참조 해제의 부수 효과로) 소멸되었어야 합니다. 이 컬렉션은 완료됩니다.
참고: GC는 위 2단계 이후에 CI를 재계산하지 않으므로, 전체 부분 그래프(subgraph)가 여전히 고립되어 있는지 확인하는 3단계가 필요합니다.
C-레벨 변경 (C-level changes)
타입 객체는 새로운 tp_finalize
슬롯을 얻게 되며, __del__
메서드는 이 슬롯에 매핑됩니다 (그리고 역으로도). 제너레이터는 tp_del
대신 이 슬롯을 사용하도록 수정됩니다. tp_finalize
함수는 유효하고 살아있는 PyObject
를 유일한 인수로 받아 호출되는 일반적인 C 함수입니다. 호출자가 객체의 참조 카운트를 조작할 것이므로, tp_finalize
함수는 이를 조작할 필요가 없습니다. 그러나 호출자에게 반환하기 전에 원래의 예외 상태(exception state)가 복원되었는지 확인해야 합니다.
호환성을 위해 tp_del
은 타입 구조에 유지됩니다. tp_del
이 NULL
이 아닌 객체의 처리는 변경되지 않습니다. 즉, CI의 일부인 경우 최종 처리되지 않고 gc.garbage
에 남게 됩니다. 그러나 CPython 소스 트리에서는 (테스트 목적을 제외하고) 더 이상 NULL
이 아닌 tp_del
이 발견되지 않습니다.
특히 사용자 정의 할당 해제자(custom deallocator)에서 tp_finalize
를 쉽게 호출할 수 있도록 두 개의 새로운 C API 함수가 제공됩니다.
내부적으로, GC 관리 객체의 GC 헤더에 하나의 비트가 예약되어 객체가 최종 처리되었음을 알립니다. 이는 객체를 두 번 최종 처리하는 것을 방지하는 데 도움이 됩니다 (특히 GC에 의해 손상된 CT 객체를 최종 처리하는 것을 방지합니다).
참고: GC가 활성화되지 않은 객체도 tp_finalize
슬롯을 가질 수 있습니다. 이러한 객체는 추가 비트가 필요하지 않습니다. tp_finalize
함수는 할당 해제자에서만 호출될 수 있으므로, 부활되는 경우를 제외하고는 두 번 호출될 수 없기 때문입니다.
논의 (Discussion)
예측 가능성 (Predictability)
이 방식을 따르면, 객체의 최종 처리기는 나중에 부활하더라도 항상 정확히 한 번 호출됩니다.
CI 객체의 경우, 최종 처리기가 호출되는 순서 (위의 2단계)는 정의되어 있지 않습니다.
안전성 (Safety)
제안된 변경이 안전한 이유를 설명하는 것이 중요합니다. 두 가지 측면을 논의해야 합니다:
- 최종 처리기가 좀비 객체(최종 처리되고 있는 객체 포함)에 접근할 수 있는가?
- 최종 처리기가 객체 그래프를 변경하여 CI에 영향을 미치면 어떻게 되는가?
첫 번째 문제를 논의해 봅시다. 가능한 경우를 두 가지 범주로 나눌 수 있습니다:
- 최종 처리되는 객체가 CI의 일부인 경우: 구성상 CI 내의 객체는 아직 좀비가 아닙니다. CI 최종 처리기는 어떤 참조도 끊기기 전에 호출되기 때문입니다. 따라서 최종 처리기는 존재하지 않는 좀비 객체에 접근할 수 없습니다.
- 최종 처리되는 객체가 CI/CT의 일부가 아닌 경우: 정의상 CI/CT 내의 객체는 CI/CT 외부에서 자신을 가리키는 참조를 가지고 있지 않습니다. 따라서 최종 처리기는 어떤 좀비 객체에도 도달할 수 없습니다 (최종 처리되는 객체 자체가 좀비 객체로부터 참조되었다 하더라도).
이제 두 번째 문제입니다. 세 가지 잠재적인 경우가 있습니다:
- 최종 처리기가 CI 객체에 대한 기존 참조를 해제하는 경우: CI 객체는 GC가 이를 끊으려고 시도하기 전에 소멸될 수 있으며, 이는 문제가 없습니다 (GC는 이 가능성을 인지하고 있기만 하면 됩니다).
- 최종 처리기가 CI 객체에 대한 새로운 참조를 생성하는 경우: 이는 CI 객체의 최종 처리기에서만 발생할 수 있습니다 (위에서 이유를 설명했습니다). 따라서 새로운 참조는 모든 CI 최종 처리기가 호출된 후 (위의 3단계) GC에 의해 감지될 것이며, 어떤 객체도 손상되지 않고 컬렉션이 중단될 것입니다.
- 최종 처리기가 비-CI 객체에 대한 참조를 해제하거나 생성하는 경우: 구성상 이것은 문제가 되지 않습니다.
구현 (Implementation)
구현은 http://hg.python.org/features/finalize/
저장소의 finalize
브랜치에서 확인할 수 있습니다.
검증 (Validation)
일반적인 Python 테스트 스위트 실행 외에도, 이 구현은 참조 순환, 객체 부활, 그리고 레거시 tp_del
슬롯을 포함한 다양한 마무리 가능성에 대한 테스트 케이스를 추가했습니다.
또한 이 구현은 다음 테스트 스위트에서 어떠한 회귀(regression)도 발생시키지 않는다는 것이 확인되었습니다:
- Tulip (제너레이터를 광범위하게 사용)
- Tornado
- SQLAlchemy
- Django
- zope.interface
참고 자료 (References)
- 참조 순환 컬렉션 및 약한 참조 콜백에 대한 참고 사항:
http://hg.python.org/cpython/file/4e687d53b645/Modules/gc_weakref.txt
- 제너레이터 메모리 누수:
http://bugs.python.org/issue17468
- 객체가 GC에 의해 수집될 수 있는지 여부를 결정하도록 허용:
http://bugs.python.org/issue9141
- GC 기반 모듈 종료 절차:
http://bugs.python.org/issue812369
저작권 (Copyright)
이 문서는 퍼블릭 도메인에 공개되었습니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments