From ef2d033ad6c0492e79749893f2d9985cc04a1d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Mon, 15 Jun 2026 08:21:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20CR-24=20AiChat=20listener?= =?UTF-8?q?=20=E6=B3=84=E6=BC=8F(stopListener=20=E6=8E=A5=E5=85=A5=20onBef?= =?UTF-8?q?oreUnmount+=E8=A1=A5=20clearStreamWatchdog)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useAiEvents stopListener 补 clearStreamWatchdog()(原漏清 _streamWatchdog 计时器,卸载后 130s onStreamTimeout 仍写 state);AiChat onBeforeUnmount 补 store.stopListener()(原全仓零调用 listener+watchdog 永久泄漏)。R1 双注册经核查 App.vue:94 v-if 互斥(detach 卸载主窗口 AiChat)同一时刻仅一窗口持活 listener,无真实双消费,不加去重标志避免过度防御(留后续:互斥逻辑改变则需去重或后端 emit_to 定向)。批8 wsyxw19ai,vue-tsc 0 --- src/components/AiChat.vue | 4 ++++ src/composables/ai/useAiEvents.ts | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue index 45f52c9..1954d4c 100644 --- a/src/components/AiChat.vue +++ b/src/components/AiChat.vue @@ -746,6 +746,10 @@ watch(() => store.state.streaming, (s) => { }) onBeforeUnmount(() => { if (rafId !== null) cancelAnimationFrame(rafId) + // CR-260615-24: 释放后端 listener(ai-chat-event/ai-conversation-changed)+清流式看门狗。 + // 之前 stopListener 定义但全仓零调用 → listener+watchdog 永久泄漏;分离窗口主窗口 AiChat + // 卸载(走 App.vue v-if detach)时 listener 仍挂后端,watchdog 130s 后写已卸载 state。 + store.stopListener() }) // ── 工具卡片自动收起:新内容追加(新消息/toolCall 状态变化/文本增长)时, diff --git a/src/composables/ai/useAiEvents.ts b/src/composables/ai/useAiEvents.ts index 753eec4..88abe9c 100644 --- a/src/composables/ai/useAiEvents.ts +++ b/src/composables/ai/useAiEvents.ts @@ -254,12 +254,15 @@ export async function startListener() { } } -/** 停止事件监听(卸载时调用,释放后端 listener) */ +/** 停止事件监听(卸载时调用,释放后端 listener + 清看门狗) + * 清看门狗:卸载时若仍在生成,_streamWatchdog 计时器未释放,130s 后 onStreamTimeout + * 仍写 state(messages.push/置 streaming)——已卸载组件不应再被触发。故同步清除。 */ function stopListener() { _unlistenAiEvent?.() _unlistenConvChanged?.() _unlistenAiEvent = null _unlistenConvChanged = null + clearStreamWatchdog() } export function useAiEvents() {