본문으로 건너뛰기

[Open WebUI] ChatControls 컴포넌트 메모리 누수 수정

PR #22112 - fix: fix memory leaking of ChatControls

들어가며

Open WebUI의 ChatControls.svelte 컴포넌트에서 메모리 누수가 발생하고 있었습니다. 핵심 원인은 onMount 내에서 비동기 작업(await tick()) 후에 이벤트 리스너를 등록하는 반면, onDestroy에서는 동기적으로 리스너를 해제하는 구조였습니다. 컴포넌트가 빠르게 마운트/언마운트되면 onDestroy가 먼저 실행되고, 이후 비동기 onMount가 완료되어 리스너가 등록되지만 해제할 방법이 없어지는 문제였습니다.

핵심 코드 분석

Before

onMount(async () => {
    mediaQuery = window.matchMedia('(min-width: 1024px)');
    mediaQuery.addEventListener('change', handleMediaQuery);

    await tick();
    // ResizeObserver 생성 및 이벤트 리스너 등록
    document.addEventListener('mousedown', onMouseDown);
    document.addEventListener('mouseup', onMouseUp);
});

onDestroy(() => {
    // 동기적 정리 - onMount의 await 이후 코드보다 먼저 실행될 수 있음
    mediaQuery.removeEventListener('change', handleMediaQuery);
    document.removeEventListener('mousedown', onMouseDown);
    document.removeEventListener('mouseup', onMouseUp);
});

After

onMount(() => {
    const mediaQuery = window.matchMedia('(min-width: 1024px)');
    mediaQuery.addEventListener('change', handleMediaQuery);

    let resizeObserver: ResizeObserver | null = null;
    let isDestroyed = false;

    const init = async () => {
        await tick();
        if (isDestroyed) return; // 이미 파괴되었으면 중단
        // ResizeObserver 생성 및 이벤트 리스너 등록
    };
    init();

    document.addEventListener('mousedown', onMouseDown);
    document.addEventListener('mouseup', onMouseUp);

    return () => {
        isDestroyed = true;
        resizeObserver?.disconnect();
        mediaQuery.removeEventListener('change', handleMediaQuery);
        document.removeEventListener('mousedown', onMouseDown);
        document.removeEventListener('mouseup', onMouseUp);
    };
});

왜 이게 좋은가

  1. Svelte의 onMount 반환값 활용: onMount에서 함수를 반환하면 컴포넌트 파괴 시 자동으로 호출됩니다. 이는 onDestroy보다 안전한 패턴입니다.
  2. isDestroyed 플래그: 비동기 초기화가 완료되기 전에 컴포넌트가 파괴된 경우를 감지하여, 불필요한 리스너 등록을 방지합니다.
  3. 지역 변수 활용: mediaQueryresizeObserveronMount 내부 지역 변수로 이동하여 클로저에서 안전하게 참조합니다.
  4. DOM 트리 메모리 누수 방지: 리스너가 정리되지 않으면 전체 컴포넌트 DOM 트리가 GC 대상에서 제외되어 페이지 크래시를 유발할 수 있었습니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글