`ThreadSafeWeakPtr` torn dual-pointer replaced with lock-protected storage
Component: WTF | a90ff51
ThreadSafeWeakPtr는 WTF가 제공하는 cross-thread-safe weak reference입니다. 내부적으로는 strong/weak refcount와 대상에 대한 raw pointer를 함께 추적하는 공유 control block이 뒷받침합니다. 그런데 C++ multiple inheritance에서는 base class pointer와 most-derived object pointer의 주소가 서로 다를 수 있습니다. 그래서 기존 구현은 control block RefPtr 옆에 interior object pointer를 두 번째 포인터로 따로 저장했습니다. 두 필드가 각각 독립적으로 쓰기 가능하면서도 atomicity 보장은 없는 형태였습니다.
이 commit은 interior pointer를 control block이 추적하는 객체 기준의 16비트 offset으로 변경했습니다. 그렇게 확보한 공간에 Lock을 추가하고, 두 필드를 모두 이 lock이 보호하는 ThreadSafeWeakPtrStorage 안으로 옮겼습니다. 함께 RemoteAudioVideoRendererProxyManager도 수정되었습니다. 이 클래스는 GPUConnectionToWebProcess의 control block을 그대로 재사용하면서, 서로 무관한 두 객체 사이의 offset을 계산하고 있었습니다.
Significance
함께 추가된 테스트 — ControlBlockFreedUnderReader, DoubleWeakDeref, TornPairOnAssignment — 는 기존 코드가 reader가 아직 사용 중인 control block을 해제하거나, 동시성 상황에서 weak reference를 두 번 deref할 수 있었음을 보여줍니다. writer thread가 weak pointer를 재할당하는 시점과 reader가 get()을 호출하는 시점이 겹치면, reader는 새로운 object pointer와 stale한 무관계 control block이 짝지어진 상태를 관찰하게 됩니다. 그 결과 refcount와 deref 로직이 엉뚱한 객체의 관리 정보를 대상으로 동작합니다. 다만 대가는 분명합니다. 매우 뜨겁고 광범위하게 쓰이는 primitive의 비용이 대략 두 배가 되는데, 이 primitive에 크게 의존하는 GPU process와 media 코드 경로에서는 무시하기 어려운 부담입니다.
Audit directions
재사용 가능한 패턴은, invariant가 독립적으로 쓰기 가능한 두 필드에 걸쳐 있는 smart pointer나 handle입니다. invariant가 성립하려면 두 필드가 atomic하게 갱신되어야 하는데, 정작 타입 자체는 그것을 강제할 수단을 제공하지 않습니다. WTF의 다른 multi-field handle 타입들과, control block과 interior pointer를 따로 캐시하는 WebKit 코드를 함께 점검해 볼 만합니다. 한편 RemoteAudioVideoRendererProxyManager 쪽 버그는 그 자체로 별도의 사냥감입니다. 어떤 객체가 다른 객체의 control block을 빌려 쓰면 저장된 offset은 의미를 잃습니다. ThreadSafeWeakPtr 생성 지점 중 control block의 출처와 pointee가 동일한 객체가 아닌 경우를 검색해 보십시오. 코드 리뷰에서의 판별 기준은, 한 클래스에서 *this로 만든 weak pointer가 정작 다른 클래스의 멤버나 peer를 가리키고 있는 경우입니다.