Interfacing with the XPCOM cycle collector

이 글은 Firefox 에서 XPCOM에 도입된 순환 수집기(cycle collector)에 대한 빠른 개요이며, XPCOM 순환 콜렉션에 참여하도록 기존 C++ 클래스를 수정하는데 필요한 절차를 포함합니다. 여러분이 생각하기에 순환 소유권(cyclical-ownership) 누출에 영향을 미치는 클래스가 있다면, 이 페이지는 여러분을 위한 것입니다.

대상 독자는 Mozilla C++ 개발자입니다.

순환 수집기가 하는 일

순환 수집기는 대부분의 시간을 가비지(garbage) 순환에 영향을 미칠 수도 있는 XPCOM 개체에 대한 포인터를 모으는데(그리고 버리는데) 보냅니다. 이는 수집기 작업의 유휴 단계로서 여기에서는 <tt>nsAutoRefCnt</tt>의 특별한 변형이 "의심스러운" refcount 이벤트(0이 아닌 N에 대하여 N+1부터 N까지)를 전달하면서 자신을 재빠르게 수집기에 등록하고 등록 해제하기를 반복합니다.

수집기는 주기적으로 깨어 나서 버퍼에 계속 있었던 의심스러운 포인터를 검사합니다. 이는 수집기 동작의 검사 단계입니다. 이 단계에서 수집기는 반복적으로 각 후보자에게 싱글톤(singleton) 순환 수집 도우미 클래스를 요청하고, 만약 도우미 클래스가 존재하면 수집기는 후보자의 (소유한) 자식에 대한 설명을 요청합니다. 이러한 방법으로 수집기는 의심스러운 개체로부터 도달 가능한 소유권 서브그래프(ownership subgraph)에 대한 그림을 작성합니다.

수집기가 모두 서로에게 다시 참조를 하는 개체의 그룹을 찾고 개체의 참조 카운트가 그룹 안에서 내부 포인터에 의한 것이라고 확인하면 수집기는 해당 그룹을 순환 가비지로 간주하여 해제하려고 시도합니다. 이는 수집기 동작의 연결 해제 단계입니다. 이 단계에서 수집기는 발견한 가비지 개체를 지나 다니며 다시 그들의 도우미 개체의 도움을 받아 도우미 개체에게 각 개체를 인접한 자식과 "연결 해제"하라고 요청합니다.

수집기는 JS 힙(heap)을 지나 다니는 방법도 알고 있으며 그것으로 들어오고 나가는 소유권 순환을 발견할 수 있다는 점을 참고하십시오.

수집기가 실패하는 경우

순환 수집기는 신중한 장치입니다. 그것이 가비지 순환을 수집하는데 실패하는 경우도 있습니다.

  1. 수집기는 기본 값으로 어떠한 포인터도 의심하지 않습니다. 개체는 보통 <tt>nsAutoRefCnt</tt>가 아닌 <tt>nsCycleCollectingAutoRefCnt</tt>를 사용하여 스스로 의심해야 합니다.
  2. 수집기는 <tt>nsICycleCollectionParticipant</tt>로 QI를 했을 때 도우미 개체를 반환하는 개체만 탐색합니다. 탐색 중에 알 수 없는 모서리를 만나면 그 모서리를 포기하는데, 이는 순환에 관여하는 모든 모서리가 참여해야 한다는 것을 의미하며 그렇지 않으면 순환을 발견할 수 없습니다.
  3. 도우미 개체에 대한 <tt>Traverse</tt>와 <tt>Unlink</tt> 메소드는 마법이 아닙니다. 그것을 프로그래머가 올바르게 제공해야 하며 그렇지 않으면 수집기는 실패하게 됩니다.
  4. 수집기는 스택(stack)에 존재하는 임시로 소유하는 포인터를 발견하는 방법을 모릅니다. 그래서 그것이 프로그램의 상단(top-loop) 근처에서만 실행한다는 사실이 중요합니다. 별도의 소유하는 포인터가 있는 경우에 고장나지는 않지만 소유되는 개체에서 발견한 참조 카운트를 설명할 수 없게 되어 순환을 수집하는데 실패하게 됩니다.

여러분의 클래스를 참여시키는 방법

순환 수집기와 여러분의 클래스 사이의 인터페이스는 <tt>xpcom/base/nsCycleCollector.h</tt>의 내용을 사용하여 직접 접근할 수 있습니다. 하지만 <tt>xpcom/base/nsCycleCollectionParticipant.h</tt>에는 여러분의 클래스에 추가할 수 있는 편리한 매크로가 있으며 이는 훨씬 사용하기 쉽습니다. 일반적으로, 수정해야 할 <tt>nsFoo</tt> 클래스가 있고 두 개의 <tt>nsCOMPtr</tt> 모서리인 <tt>mBar</tt>와 <tt>mBaz</tt>가 있다고 가정하면, 처리 과정은 몇 가지 간단한 수정이 됩니다.

  1. <tt>nsCycleCollectionParticipant.h</tt> 헤더를 <tt>nsFoo.h</tt>와 <tt>nsFoo.cpp</tt>에 모두 포함합니다.
  2. <tt>nsFoo</tt>의 정의에서 <tt>NS_DECL_ISUPPORTS</tt>를 <tt>NS_DECL_CYCLE_COLLECTING_ISUPPORTS</tt>로 바꿉니다.
  3. <tt>nsFoo</tt> 정의의 public 부분에 <tt>NS_DECL_CYCLE_COLLECTION_CLASS(nsFoo)</tt>를 추가합니다.
  4. <tt>nsFoo.cpp</tt>에서 <tt>nsFoo</tt>의 인터페이스 정의에 <tt>NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsFoo)</tt>를 추가합니다.
  5. <tt>nsFoo.cpp</tt>에서 <tt>NS_IMPL_ADDREF(nsFoo)</tt>를 <tt>NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFoo)</tt>로 바꿉니다.
  6. <tt>nsFoo.cpp</tt>에서 <tt>NS_IMPL_RELEASE(nsFoo)</tt>를 <tt>NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFoo)</tt>로 바꿉니다.
  7. <tt>nsFoo.cpp</tt>에서 <tt>NS_IMPL_CYCLE_COLLECTION_CLASS_2(nsFoo, mBar, mBaz)</tt>를 추가합니다.

여러분의 클래스는 이 그림보다 훨씬 더 복잡한 구조를 가지고 있을 가능성이 있습니다. 예를 들어, 여러분의 클래스는 여러 개의 <tt>nsISupports</tt> 기반 클래스를 가질 수 있으며 이는 애매함을 제거하는 다운캐스트(disambiguating downcast)를 수행하는 <tt>*_AMBIGUOUS</tt> 매크로 사용을 필요로 합니다. 또한 여러분의 클래스는 간단한 <tt>NS_IMPL_CYCLE_COLLECTION_CLASS_N</tt> 매크로는 충분하지 않은 복잡한 소유 구조를 가질 수 있습니다. 이 경우에 여러분은 도우미 클래스의 TraverseUnlink 메소드를 직접 구현해야 합니다. 이러한 경우에도 <tt>NS_IMPL_CYCLE_COLLECTION_TRAVERSE_{BEGIN,END}</tt>와 <tt>NS_IMPL_CYCLE_COLLECTION_UNLINK_{BEGIN,END}</tt> 매크로를 사용하는 것이 도움이 됩니다. 그것을 사용하는 예제는 <tt>content/base/src/nsGenericElement.cpp</tt>와 같은 더 복잡한 클래스에서 볼 수 있습니다. 클래스가 티어오프(tearoff)를 가지고 있거나 다른 클래스에 결합(aggregate)된 경우에는 티어오프 클래스나 외부 클래스도 순환 수집에 참여시키는 것이 중요합니다. 그렇게 하지 않으면 순환 수집기가 개체를 너무 빨리 수집하도록 만듭니다.


Document Tags and Contributors

태그:
Contributors to this page: Jeongkyu
Last updated by: Jeongkyu,