Files
qiming/qimingclaw/crates/agent-electron-client/docs/long-running-stability-improvements.md

8.1 KiB
Raw Blame History

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

// 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 修改 — 适配新超时值