Return

Cesium Viewer Lifecycle Management and Crash Prevention

ARCHITECTURE

Situation

Cesium utility demo 페이지들을 빠르게 전환하면 어플리케이션이 에러 메시지와 함께 비정상적으로 종료됨: Cannot read properties of undefined (reading 'scene'), Cannot set properties of undefined (setting 'canAnimate').

원인 조사 결과, 이미 destroy 된 viewer 에 대해 requestAnimationFrame 에서 접근하려는 시도 및 terrain 이나 tileset 을 불러온 후의 이벤트 등의 접근 시도를 확인함.

Task

  • 비동기 작업들이나 애니메이션 작업이 이미 destroy 된 viewer 에 접근하는 것을 방지
  • 데모 페이지들 전반에 걸쳐 공유되는 viewer 의 상태를 확인하는 과정 제거
  • 추가적인 복잡한 wrapper 없이 Cesium 의 API 를 그대로 사용하는 방향 유지

Action

1. First Attempt - Coordinating Viewer Destruction Timing

viewer 컴포넌트의 life cycle 을 조절하여 비동기 작업들의 처리 시점을 조절하는 방법을 먼저 시도.

Cesium 의 API 중 일부, terrain 또는 tileset 등의 초기 설정에 비동기 작업이 필요했는데, 이 방법으로 증상을 해결하기 위해서는 모든 데모 페이지마다 if (!viewer || viewer.isDestroyed()) return; 와 같은 확인 절차 추가가 필요하다는 것을 발견함.

이는 Cesium API 사용에 있어 항상 viewer 의 상태를 항상 확인해야하는 커플링이 발생하여 적절하지 않다고 판단함.

2. Decided to Split Context Scope

모든 데모 페이지마다 ViewerProvider 를 별도로 갖는 apps/web/app/cesium-utils/[api]/layout.tsx 를 생성. cesium-utils 페이지 범위로 공유되던 ViewerProvider 를 제거하고, feature 선택 상태를 관리하는 CesiumUtilsProvider 만 유지. 각 데모 페이지는 저마다의 ViewerProvider 를 가짐.

각 경로는 이제 그 경로의 lifecycle 과 일치하는 viewer 인스턴스를 사용하게 됨. 앞서 언급한 문제점인 모든 페이지마다의 viewer 상태 확인 없이 안전하게 viewer 를 사용할 수 있게 됨.

3. Discovered Animation Destruction Race

경로별 viewer 할당 작업을 마친 후, 비동기 작업들에 대한 처리는 완료했으나 다른 문제점을 발견: viewer 에서 애니메이션이 동작 중일 때 destroy 하면 requestAnimationFrame 에서 해당 viewer 에 접근하려는 시도

4. Improved Viewer Cleanup Logic

viewer 컴포넌트에 cleanup 작업을 추가함. 기존의 초기화 effect 의 cleanup 부분에서 destroy() 를 부르기 전에 viewer.useDefaultRenderLoop = false 설정을 추가하여 렌더링 루프를 멈춘 뒤 viewer 인스턴스를 삭제하도록 개선.

viewer 의 카메라 시점 변경을 담당하는 flyTo 실행을 별도의 effect 로 분리하여 viewer 의 초기화 이슈 개선.

5. Additional Improvements

viewer 컴포넌트에서 마운트 상태를 결정하던 key={pathname} 삭제. 경로 별로 viewer 를 제공하는 환경에서는 불필요.

highlight 데모에서 인스턴스들을 useRef 로 대체하여 viewer 상태를 확인하던 조건절 간소화 및 삭제.

이벤트 리스너 기반으로 설정했던 terrain 과 tileset 들을 Promise 기반으로 변경하여 async/await 로 활용 및 viewer 상태 guard 추가로 비동기 작업 안정성 확보.

Result

아키텍쳐 구조적인 변경으로 번잡한 조건절 추가 없이, 빠른 전환 시 발생하던 비정상 종료 문제를 해결함.

viewer 의 삭제 시점을 조절로 해결하려던 시도는 viewer 를 사용하는 모든 페이지마다, Cesium API 사용마다 viewer 상태를 확인해야하는 구조적인 커플링 문제가 발생하여 번복됨.

경로 별 viewer 분리를 통해 route 와 viewer 의 lifecycle 을 맞춤으로써 문제를 해결. 각 데모 페이지는 페이지가 마운트된 상태라면 인스턴스 또한 존재하는 것이 확실한 저마다의 viewer 를 갖게 됨. 비동기 작업들은 viewer 와 별개로 동작하여 도중에 viewer 가 삭제되더라도 문제없이 작업이 완료될 수 있음.

분리된 effect 는 destroy 전에 렌더링 loop 를 멈추고, try-catch 를 통해 race condition 을 무시하여 cleanup 안정성을 확보함.

데모 페이지들에서는 비동기 작업들에 대해서만 viewer 상태를 확인하고, 나머지 Cesium API 들은 별도의 확인 없이 사용 가능함.

Trade-off: 경로 별 viewer 를 설정하면 경로 전환 시 잠시 동안 복수의 viewer 가 존재하게 되어, 일시적인 메모리 점유율과 유지보수 및 구조적 안정성을 교환한 셈.