※ 이 글은 아래 영문 질문의 한국어 버전입니다. 9월 3일에 등록하고 재현 방법까지 보충했으나 아직 회신을 받지 못해, 한국어 지원 그룹으로 다시 문의드립니다.
[Content removed]
[환경 / 영향]
UE 5.5 라이선시 소스 빌드, Shipping, Android/iOS/Windows 라이브 RPG.
현재 저희 최대 크래시 버킷입니다. 누적 약 163만 건(안드로이드 하루 약 6,000대, 일일 안드로이드 크래시의 약 40%). 5.5 적용 이후 모든 제품 버전에서 일정한 발생률을 보이므로, 콘텐츠 리그레션이 아니라 엔진 레벨의 상시 레이스로 판단하고 있습니다.
(본문의 코드는 Release-5.5 기준이며, 5.6 의 CreateSparseClassData 구현도 동일한 구조로 보입니다.)
[저희 쪽 원인 분석]
UClass::CreateSparseClassData() (CoreUObject/Private/UObject/Class.cpp) 기준으로 세 가지가 겹친 것으로 보입니다.
(1) 초기화 완료 전에 포인터가 발행됨 — SparseClassData = FMemory::Malloc(…) 로 멤버에 먼저 대입한 뒤 InitializeStruct / CopyScriptStruct 가 그 멤버 위에서 수행됩니다. 락 없이 읽는 GetSparseClassData / GetOrCreateSparseClassData 는 초기화 중인 메모리를 가리키는 non-null 포인터를 관측할 수 있습니다.
(2) 생성자 간 동기화 부재 — 비동기 로딩 스레드(CDO 직렬화)와 게임 스레드가 같은 클래스에 대해, 또는 GetArchetypeForSparseClassData() 를 통한 부모/자식 체인에 대해 동시에 생성 경로에 진입할 수 있습니다.
(3) Shipping 에서는 check(SparseClassData == nullptr) 가 컴파일 제거되어 이중 진입이 조용히 진행됩니다. memcpy(src=null) 은 자식 SCD 생성이 “현재 생성 중인” 부모 아키타입 SCD 를 복사하는 상황과 정확히 일치합니다.
저희가 접근 가능한 최신 스트림을 확인했으나, 복사 지점 주변의 null 지연 처리(방어 코드)만 있고 발행/생성 레이스 자체에 대한 수정은 찾지 못했습니다.
[검토 중인 수정 — 단일 .cpp, ABI 변경 없음]
생성 경로를 static FCriticalSection 으로 직렬화하고(재진입 가능하므로 락 안에서 부모 체인 생성이 일어나도 안전), 로컬 버퍼에 완전히 구성한 뒤 release 배리어와 함께 마지막에 발행합니다. 읽기 경로는 수정하지 않습니다(포인터 의존 로드라 ARM64 에서도 안전하다고 보았습니다).
FScopeLock Lock(&GSparseClassDataCreationLock);
if (SparseClassData) { return SparseClassData; } // 레이스에서 밀렸으면 재사용
void* NewSCD = FMemory::Malloc(…);
SparseClassDataStruct->InitializeStruct(NewSCD);
/* 기존 아키타입 복사 로직 그대로, 대상만 NewSCD */
FPlatformMisc::MemoryBarrier(); // 완전히 만들어진 뒤에만 발행
SparseClassData = NewSCD;
FCoreUObjectDelegates::OnPostInitSparseClassData.Broadcast(…);
[문의사항]
(1) 이미 알려진 이슈인가요(UE-xxxxx)? 5.5 / 5.6 에 대한 공식 수정 계획이나, 저희가 백포트할 수 있는 기존 CL 이 있는지 알고 싶습니다.
(2) 위 접근에 문제가 있을까요? 특히 (a) 락을 잡은 채로 OnPostInitSparseClassData 를 브로드캐스트하게 되는데 리스너 쪽 데드락 위험이 있는지, (b) 전역 락 하나로 모든 클래스의 최초 생성이 직렬화되는 점이 문제가 될지 궁금합니다.
(3) 권장되는 공식 패턴이 따로 있는지요(atomic CAS 발행, 또는 비동기 로딩 중 SCD 생성을 게임 스레드로 제한하는 방식 등).
발생량이 커서 공식 수정이 없다면 다음 릴리스에 로컬 패치를 넣을 예정입니다. 가이드 부탁드립니다.
[Attachment Removed]