# Electron 客户端长期运行稳定性改进 > 日期: 2026-03-18 > 分支: feature/electron-client-0.9 ## 背景 QimingClaw 是长期运行的桌面应用(可能持续运行数天/数周)。经过全面代码审查,发现若干资源泄漏、定时器未清理、进程清理不完整等问题,以及网络不稳定时超时过于激进的问题。本次优化共 13 项改动,新增 21 个测试用例。 --- ## 第一部分:资源泄漏修复(6 项) ### 1. Event Forwarders 监听器泄漏修复 **文件**: `src/main/ipc/eventForwarders.ts` **问题**: 21 个监听器注册在 agentService 上,永不清理。关闭窗口后旧闭包仍引用已销毁的 window。 **修复**: - 新增模块级 `registeredHandlers[]` 数组,存储每个 `{ event, handler }` 对 - 导出 `unregisterEventForwarders()`,遍历调用 `agentService.off(event, fn)` 精确移除 - `registerEventForwarders()` 开头先调用 `unregisterEventForwarders()` 保证幂等 ### 2. 将 unregister 接入 cleanupAllProcesses **文件**: `src/main/main.ts` **修复**: 在 `cleanupAllProcesses()` 中、`agentService.destroy()` 之前调用 `unregisterEventForwarders()`,确保应用退出时所有事件监听器被清理。 ### 3. ProcessManager.stop() 补齐 removeAllListeners **文件**: `src/main/processManager.ts` **问题**: `kill()` 和 `stopAsync()` 都正确移除了 stdout/stderr/stdin 监听器,但 `stop()` 没有。Windows 上可能导致句柄无法释放。 **修复**: 在 `stop()` 中 `proc.kill()` 之前加上对 stdout/stderr/stdin/proc 的 `removeAllListeners()`。 ### 4. SSE heartbeat 增加 error 事件清理 **文件**: `src/main/services/computerServer.ts` **问题**: heartbeat interval 仅在 `req.on('close')` 时清理。若 response 流出错但 close 未触发,interval 泄漏。 **修复**: 添加 `res.on('error', () => { clearInterval(heartbeat); })`。 ### 5. MemoryFileSync destroy 竞态修复 **文件**: `src/main/services/memory/MemoryFileSync.ts` **问题**: `destroy()` 调用顺序可能导致异步 `processPendingSync` 访问已清理资源。 **修复**: - 调整顺序:先设 `this.initialized = false`,再 `clearAllTimers()`,最后 `stopWatcher()` - 在 `processPendingSync()` 开头增加 `if (!this.initialized) return;` 守卫 ### 6. PermissionsPage 定时器泄漏修复 **文件**: `src/renderer/components/pages/PermissionsPage.tsx` **问题**: click handler 内创建 `setInterval` + `setTimeout`,组件卸载前不会清理。快速点击会导致定时器叠加。 **修复**: 用 `useRef` 存储定时器引用,`useCallback` 封装清理函数,`useEffect` return 中清理。新定时器创建前先清除旧定时器。 --- ## 第二部分:超时与网络容忍度提升(7 项) 所有超时常量已集中到 `src/shared/constants.ts` 统一管理。 ### 7. 集中超时常量调整 **文件**: `src/shared/constants.ts` | 常量 | 旧值 | 新值 | 原因 | |------|------|------|------| | `DEFAULT_API_TIMEOUT` | 30s | 60s | 弱网环境下 30s 可能不够 | | `DEFAULT_SSE_RETRY_DELAY` | 3s | 5s | 初始重试间隔过短,弱网下频繁重连 | | `DEFAULT_SSE_MAX_RETRY_DELAY` | 30s | 60s | 持续断网时应更慢退避 | ### 8. 退出清理超时增加 | 常量 | 旧值 | 新值 | 原因 | |------|------|------|------| | `CLEANUP_TIMEOUT` | 5s | 15s | 多引擎 + 多 MCP 服务器 + memory extraction 可能超过 5 秒 | **文件**: `src/main/main.ts` — 引用 `CLEANUP_TIMEOUT` 常量替代内联硬编码。 ### 9. 进程 SIGTERM→SIGKILL 升级超时增加 | 常量 | 旧值 | 新值 | 原因 | |------|------|------|------| | `PROCESS_KILL_ESCALATION_TIMEOUT` | 3s | 5s | 进程若在做网络 I/O 清理可能不够 | **文件**: `src/main/processManager.ts` — 引用常量替代内联硬编码。 ### 10. ACP 会话取消超时增加 | 常量 | 旧值 | 新值 | 原因 | |------|------|------|------| | `ACP_ABORT_TIMEOUT` | 10s | 15s | 弱网时 ACP 二进制响应取消命令可能更慢 | **文件**: `src/main/services/engines/acp/acpEngine.ts` — 引用常量替代内联 `ABORT_TIMEOUT_MS`。 ### 11. 引擎销毁超时增加 | 常量 | 旧值 | 新值 | 原因 | |------|------|------|------| | `ENGINE_DESTROY_TIMEOUT` | 10s | 20s | 多个 MCP 服务器关闭 + 进程清理可能需要更多时间 | **文件**: `src/main/services/engines/unifiedAgent.ts` — 引用常量替代内联硬编码。 ### 12. SSE 心跳间隔放宽 | 常量 | 旧值 | 新值 | 原因 | |------|------|------|------| | `DEFAULT_SSE_HEARTBEAT_INTERVAL` | 15s | 30s | 弱网时可能在短暂中断期间丢失连接 | **文件**: `src/main/services/computerServer.ts` — 引用常量替代内联硬编码。 ### 13. 依赖同步添加超时 | 常量 | 新增值 | 原因 | |------|--------|------| | `DEPS_SYNC_TIMEOUT` | 120s | 网络异常时 `syncInitDependencies()` 可能永久挂起 | **文件**: `src/main/bootstrap/startup.ts` — 用 `Promise.race` 包装 120 秒超时,超时后设 `_depsSyncInProgress = false` 并通知 renderer。定时器通过 `.finally()` 正确清理。 --- ## 超时常量一览 所有超时常量集中在 `src/shared/constants.ts`: ```typescript // API & SSE DEFAULT_API_TIMEOUT = 60_000 // API 请求超时 DEFAULT_SSE_RETRY_DELAY = 5_000 // SSE 初始重试延迟 DEFAULT_SSE_MAX_RETRY_DELAY = 60_000 // SSE 最大重试延迟 DEFAULT_SSE_HEARTBEAT_INTERVAL = 30_000 // SSE 心跳间隔 // 进程生命周期 DEFAULT_STARTUP_DELAY = 3_000 // 进程启动延迟 CLEANUP_TIMEOUT = 15_000 // 应用退出清理超时 PROCESS_KILL_ESCALATION_TIMEOUT = 5_000 // SIGTERM→SIGKILL 升级超时 // 引擎 & ACP ACP_ABORT_TIMEOUT = 15_000 // ACP 会话取消超时 ENGINE_DESTROY_TIMEOUT = 20_000 // 引擎销毁超时 // 依赖管理 DEPS_SYNC_TIMEOUT = 120_000 // 依赖同步超时 ``` **约束关系**: - `PROCESS_KILL_ESCALATION_TIMEOUT < CLEANUP_TIMEOUT` - `ACP_ABORT_TIMEOUT ≤ ENGINE_DESTROY_TIMEOUT` - `DEPS_SYNC_TIMEOUT > ENGINE_DESTROY_TIMEOUT > CLEANUP_TIMEOUT` --- ## 测试覆盖 新增 3 个测试文件,共 21 个测试用例: | 文件 | 用例数 | 覆盖内容 | |------|--------|----------| | `src/main/ipc/eventForwarders.test.ts` | 8 | 监听器注册/注销、幂等性、事件转发、注销后静默 | | `src/main/processManager.test.ts` | 7 | stop() 清理、null-before-kill 顺序、SIGKILL 升级时序、定时器取消 | | `src/main/services/memory/MemoryFileSync.test.ts` | 3 | destroy 顺序、processPendingSync 守卫、destroy 后 debounce 不触发 | | `src/shared/constants.test.ts` | +5 | 超时约束关系断言 | 全量测试:**470/470 通过**,构建无错误。 --- ## 已有的良好实践(保持不变) - ProcessRegistry 孤儿进程检测 - 日志轮转(按天 + 按大小,7/30 天 TTL) - SSE 事件缓冲(max 50, 30s TTL) - SIGTERM→SIGKILL 升级 - SQLite 状态持久化 - ServiceManager 服务启动顺序编排 --- ## 变更文件清单 | 文件 | 类型 | |------|------| | `src/shared/constants.ts` | 修改 — 新增 6 个超时常量,调整 3 个现有值 | | `src/main/ipc/eventForwarders.ts` | 修改 — 监听器生命周期管理 | | `src/main/main.ts` | 修改 — 清理流程集成、引用常量 | | `src/main/processManager.ts` | 修改 — stop() 补齐监听器清理、引用常量 | | `src/main/services/computerServer.ts` | 修改 — SSE error 清理、引用常量 | | `src/main/services/memory/MemoryFileSync.ts` | 修改 — destroy 竞态修复 | | `src/renderer/components/pages/PermissionsPage.tsx` | 修改 — 定时器泄漏修复 | | `src/main/services/engines/acp/acpEngine.ts` | 修改 — 引用常量 | | `src/main/services/engines/unifiedAgent.ts` | 修改 — 引用常量 | | `src/main/bootstrap/startup.ts` | 修改 — 依赖同步超时 | | `src/main/ipc/eventForwarders.test.ts` | 新增 | | `src/main/processManager.test.ts` | 新增 | | `src/main/services/memory/MemoryFileSync.test.ts` | 新增 | | `src/shared/constants.test.ts` | 修改 — 新增断言 | | `src/main/services/engines/acp/acpEngine.test.ts` | 修改 — 适配新超时值 |