본문으로 건너뛰기

[Open WebUI] Sidebar 컴포넌트 메모리 누수 수정: onDestroy에서 onMount return으로 전환

PR 링크: open-webui/open-webui#22082 상태: Merged | 변경: +30 / -39

들어가며

Open WebUI의 Sidebar 컴포넌트에서 메모리 누수가 발생하고 있었습니다. onMount에서 등록한 이벤트 리스너와 스토어 구독을 onDestroy에서 해제하는 패턴인데, Svelte의 라이프사이클 타이밍 문제로 unsubscribers 배열이 모듈 스코프에 선언되어 컴포넌트 재생성 시 이전 구독이 누수될 수 있었습니다. 이 PR은 Svelte의 권장 패턴인 onMount 반환 함수로 클린업을 통합합니다.

핵심 코드 분석

Before: onMount + onDestroy 분리

let unsubscribers = []; // 모듈 스코프

onMount(async () => {
    // 이벤트 리스너 등록
    window.addEventListener('keydown', onKeyDown);
    // ...
    dropZone?.addEventListener('dragover', onDragOver);

    $socket?.off('events', chatActiveEventHandler);
    $socket?.on('events', chatActiveEventHandler);

    unsubscribers = [
        mobile.subscribe(...),
        showSidebar.subscribe(...)
    ];
});

onDestroy(() => {
    unsubscribers.forEach(u => u?.());
    window.removeEventListener('keydown', onKeyDown);
    // ...
    $socket?.off('events', chatActiveEventHandler);
});

After: onMount 반환 함수로 통합

onMount(() => {
    // 이벤트 리스너 등록
    window.addEventListener('keydown', onKeyDown);

    const unsubscribers = [  // 로컬 스코프
        mobile.subscribe(...),
        showSidebar.subscribe(...)
    ];

    if (dropZone) {
        dropZone.addEventListener('dragover', onDragOver);
    }

    const socketInstance = $socket;
    socketInstance?.on('events', chatActiveEventHandler);

    return () => {  // 클린업 함수
        unsubscribers.forEach(u => u());
        window.removeEventListener('keydown', onKeyDown);
        if (dropZone) {
            dropZone.removeEventListener('dragover', onDragOver);
        }
        socketInstance?.off('events', chatActiveEventHandler);
    };
});

왜 이게 좋은가

1. 소켓 인스턴스 캡처

기존 코드에서 $socket?.off()onDestroy 시점의 $socket 값을 사용하는데, 이 시점에 소켓이 이미 변경되었거나 null이 되면 원래 등록한 리스너를 해제하지 못합니다. 새 코드는 const socketInstance = $socket으로 등록 시점의 소켓을 캡처하여 정확한 해제를 보장합니다.

2. 클로저를 통한 안전한 참조

unsubscribersonMount 함수 안의 로컬 변수로 선언하고 반환 함수의 클로저로 캡처합니다. 모듈 스코프 변수를 사용할 때 발생할 수 있는 경쟁 조건이 제거됩니다.

3. dropZone null 체크 강화

기존의 dropZone?.addEventListener()는 옵셔널 체이닝으로 null을 무시했지만, 해제 시에도 동일한 조건 분기를 사용하지 않으면 불일치가 생깁니다. 새 코드는 if (dropZone) 블록으로 등록과 해제를 대칭적으로 처리합니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글