[Open WebUI] MentionList 컴포넌트 메모리 누수 수정
PR 링크: open-webui/open-webui#21965 상태: Merged | 변경: +13 / -12
들어가며
Open WebUI의 MentionList 컴포넌트에서 onMount가 async로 선언되어 있고, 이벤트 리스너 해제는 별도의 onDestroy에서 수행되고 있었다. 비동기 onMount 내에서 이벤트 리스너를 등록하면, onDestroy가 먼저 호출된 후 비동기 onMount가 실행되는 경우가 발생할 수 있다. 이때 등록된 리스너는 영원히 해제되지 않아 전체 컴포넌트 DOM 트리가 메모리에 유지되는 심각한 누수가 발생했다.
핵심 코드 분석
Before: async onMount + 별도 onDestroy
const keydownListener = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
select(selectedIndex);
}
};
onMount(async () => {
window.addEventListener('keydown', keydownListener);
// ...
if (userSuggestions) {
await getUserList();
}
// ...
});
onDestroy(() => {
window.removeEventListener('keydown', keydownListener);
});
After: 동기 onMount + cleanup 반환
onMount(() => {
const keydownListener = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
select(selectedIndex);
}
};
window.addEventListener('keydown', keydownListener);
if (userSuggestions) {
getUserList(); // await 제거
}
if (modelSuggestions) {
_models = [...$models.map((m) => ({ type: 'model', id: m.id, label: m.name, data: m }))];
}
return () => {
window.removeEventListener('keydown', keydownListener);
};
});
왜 이게 좋은가
- 타이밍 문제 해결:
onMount를 동기로 변경하여onDestroy보다 먼저 실행되지 않는 경우를 원천 차단한다. - 클로저 활용:
keydownListener를onMount내부에서 정의하여 cleanup 함수에서 정확히 같은 참조를 제거할 수 있다. - Svelte 라이프사이클 패턴:
onMount의 반환 함수를 cleanup으로 사용하면onDestroy를 별도로 관리할 필요가 없어 코드가 단순해진다. - getUserList의 await 제거:
getUserList()를 fire-and-forget으로 변경하여onMount자체의 비동기 문제를 해결했다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [Open WebUI] 스트리밍 중 메시지 리스트 재구성을 프레임당 1회로 제한
- 현재글 : [Open WebUI] MentionList 컴포넌트 메모리 누수 수정
- 다음글 [Open WebUI] Sidebar 컴포넌트 메모리 누수 수정: onDestroy에서 onMount return으로 전환
댓글