主题
Service Worker 生命周期管理
本文引用的源码文件
本文引用的文件
目录
引言
本文围绕 Manifest V3 扩展的 Service Worker(SW)生命周期管理展开,结合本仓库实现,系统性解释以下主题:
- SW 启动、保活和终止机制;
- keepalive 端口连接如何防止 SW 被系统杀死;
- SW 重启检测与自动恢复流程;
- 保活心跳与闹钟续跑的双保险设计;
- SW 终止竞态防护(可选链保护、存储访问降级、错误处理);
- 自动续跑机制(checkpoint 持久化、闹钟唤醒后的任务恢复、重试限制);
- 常见内存泄漏与资源清理问题;
- 调试技巧与性能优化建议。
项目结构
本仓库采用双进程架构:Chrome 扩展端(Manifest V3,Service Worker 入口在 src/background/index.js)与独立本地代理端 Agent。本文聚焦扩展端 MV3 Service Worker 的生命周期相关代码。
graph TB A["manifest.json<br/>声明 background.service_worker"] --> B["src/background/index.js<br/>SW 入口"] B --> C["src/background/state.js<br/>keepalive 端口注册表/身份映射"] B --> D["src/background/react-loop.js<br/>ReAct 循环/保活心跳/自动续跑接入点"] B --> E["src/background/auto-resume.js<br/>闹钟续跑 watch"] F["src/side_panel/chat-manager.js<br/>侧边栏发起 CALL_API"] --> B
图表来源
- manifest.json#L47-L49
- src/background/index.js#L1-L20
- src/background/state.js#L227-L305
- src/background/react-loop.js#L1-L20
- src/background/auto-resume.js#L1-L24
- src/side_panel/chat-manager.js#L3236-L3240
章节来源
核心组件
- SW 入口与事件总线:
src/background/index.js负责监听 keepalive 端口、处理 SW 重启检测、面板可见性、定时任务等。 - 状态与 keepalive 注册表:
src/background/state.js维护 keepalive 端口映射、派生会话祖先链、实例身份(窗口/宿主 tab)。 - ReAct 执行循环:
src/background/react-loop.js是任务执行主循环,集成保活心跳、checkpoint 持久化、自动续跑接入点。 - 自动续跑闹钟:
src/background/auto-resume.js提供基于chrome.alarms的任务级保活与自动恢复决策。 - 侧边栏调用方:
src/side_panel/chat-manager.js建立 keepalive 长连接、上报实例身份、处理 SW_RESTARTED 通知并桥接 Promise 取消。
章节来源
- src/background/index.js#L115-L218
- src/background/state.js#L227-L305
- src/background/react-loop.js#L110-L113
- src/background/auto-resume.js#L20-L24
- src/side_panel/chat-manager.js#L3236-L3240
架构总览
MV3 下 SW 可能被系统回收,因此需要“保活 + 可恢复”的双重保障:
- 保活层:通过 keepalive 端口连接 + 任务级心跳(轻量 API 调用)维持 SW 活跃;
- 恢复层:通过 checkpoint 持久化 + alarms 闹钟唤醒后自动续跑,保证长时间任务不因 SW 回收而丢失进度。
sequenceDiagram
participant Panel as "侧边栏 chat-manager"
participant SW as "background/index.js"
participant Loop as "react-loop.js"
participant Alarm as "auto-resume.js"
participant DB as "storage/db.js"
Panel->>SW : chrome.runtime.connect("keepalive-<sessionId>")
SW->>SW : registerKeepalivePort(sessionId, port)
Panel->>SW : KEEPALIVE_IDENTITY(windowId)
SW-->>Panel : HOST_TAB_RESOLVED(hostTabId)
Note over Panel,SW : 长连接保持 SW 存活
Panel->>Loop : callApi(...)
Loop->>DB : saveReactCheckpoint(checkpoint)
Loop->>Alarm : startAutoResumeWatch()
alt SW 被系统回收
Alarm-->>SW : onAlarm(AUTO_RESUME_ALARM_NAME)
SW->>DB : getAllReactCheckpoints()
SW->>Loop : resumeReactLoopFromCheckpoint(sessionId)
end
SW-->>Panel : SW_RESTARTED(checkpointMeta)
Panel->>Panel : reject Promise + 提示用户继续执行
图表来源
- src/side_panel/chat-manager.js#L3236-L3260
- src/background/index.js#L140-L197
- src/background/react-loop.js#L540-L580
- src/background/auto-resume.js#L61-L89
- src/background/index.js#L65-L81
详细组件分析
keepalive 端口连接与保活原理
- 侧边栏在发起 API 调用时建立
keepalive-<sessionId>长连接,用于:- 防止 Chrome 判定 SW 空闲而将其杀死;
- 作为定向消息通道(澄清弹框、敏感工具确认等),避免广播到所有 Tab 的侧边栏。
- SW 入口监听
chrome.runtime.onConnect,按 sessionId 注册端口,并在重连时检测 SW 是否静默重启。 - 实例身份(windowId/hostTabId)由侧边栏上报,SW 用权威活跃 tab 镜像解析后回传 HOST_TAB_RESOLVED,覆盖侧边栏乐观值。
flowchart TD
Start(["侧边栏发起 CALL_API"]) --> Connect["chrome.runtime.connect('keepalive-<sessionId>')"]
Connect --> Register["SW 注册 keepalive 端口"]
Register --> Identity["侧边栏上报 KEEPALIVE_IDENTITY"]
Identity --> Resolve["SW 解析权威 hostTabId 并回传 HOST_TAB_RESOLVED"]
Resolve --> KeepAlive["长连接保持 SW 存活"]
KeepAlive --> End(["等待任务完成或断开"])
图表来源
- src/side_panel/chat-manager.js#L3236-L3260
- src/background/index.js#L140-L169
- src/background/state.js#L236-L252
章节来源
- src/side_panel/chat-manager.js#L3236-L3260
- src/background/index.js#L140-L169
- src/background/state.js#L227-L305
SW 重启检测与自动恢复流程
- SW 重启检测:当 keepalive 端口重连且 activeReactLoops 中不存在该 sessionId,说明 SW 已重启,当前 API 调用丢失。
- 恢复策略:
- 读取 checkpoint,若存在则携带元数据(iteration、interruptedReason、updatedAt、messageCount、subtaskPlan)通知侧边栏;
- 侧边栏收到 SW_RESTARTED 后拒绝当前 Promise,并提示用户“继续执行”,前端根据 checkpoint 决定是否恢复。
- 自动续跑:
- react-loop 在执行期间注册 30s 周期闹钟(AUTO_RESUME_ALARM_NAME);
- SW 被闹钟唤醒后扫描残留 checkpoint,过滤终态与会话仍在执行的项,对可续跑项递增 autoResumeAttempts 并调用 resumeReactLoopFromCheckpoint;
- 达到最大尝试次数后停止自动续跑并通知人工介入。
sequenceDiagram participant SW as "background/index.js" participant Port as "keepalive port" participant Loop as "react-loop.js" participant Alarm as "auto-resume.js" participant DB as "storage/db.js" Port->>SW : onConnect(keepalive-<sessionId>) SW->>SW : hasKeepalivePort(sessionId)? alt 重连且不在 activeReactLoops SW->>DB : getReactCheckpoint(sessionId) DB-->>SW : checkpoint? SW-->>Port : SW_RESTARTED(checkpointMeta) end Alarm-->>SW : onAlarm(AUTO_RESUME_ALARM_NAME) SW->>DB : getAllReactCheckpoints() SW->>SW : decideAutoResume() SW->>DB : saveReactCheckpoint(autoResumeAttempts++) SW->>Loop : resumeReactLoopFromCheckpoint(sessionId)
图表来源
- src/background/index.js#L172-L197
- src/background/index.js#L65-L81
- src/background/auto-resume.js#L39-L55
- src/background/auto-resume.js#L61-L89
章节来源
- src/background/index.js#L172-L197
- src/background/index.js#L65-L81
- src/background/auto-resume.js#L39-L55
- src/background/auto-resume.js#L61-L89
保活心跳机制(方案 A)
- 仅靠 keepalive 端口在不同 Chrome 版本上保活语义松紧不一;
- 任务执行期间每 20s 主动调用一次轻量扩展 API(如
chrome.runtime.getPlatformInfo)重置闲置计时器; - 使用引用计数确保多任务并发时只有最外层 stop 才真正停跳。
flowchart TD HStart["startSwHeartbeat()"] --> Pulse["立即调用一次轻量 API"] Pulse --> Timer["设置定时器间隔 20s"] Timer --> Pulse Pulse --> Stop["stopSwHeartbeat() 引用计数归零后清除定时器"]
图表来源
章节来源
SW 终止竞态防护
- 顶层 chrome API 访问统一使用可选链保护,避免扩展重载/终止瞬间 API 被剥离为 undefined 导致 TypeError 中断 SW 启动;
- storage 访问失败时静默降级(例如恢复脱离窗口状态、侧边栏作用域模式读取);
- 错误处理策略:
- 捕获异常并记录日志;
- 不阻断关键路径(如面板开关、分组管理);
- 对不可恢复错误(如 DB 自检失败)给出警告但不崩溃。
章节来源
- src/background/index.js#L136-L140
- src/background/index.js#L130-L134
- src/background/index.js#L283-L291
- src/background/index.js#L86-L95
自动续跑机制(方案 B)
- 主任务执行期注册 30s 周期闹钟(AUTO_RESUME_INTERVAL_MIN = 0.5 分钟);
- 闹钟回调扫描所有 checkpoint,过滤:
- 终态(user_cancelled/error)不自动续;
- 会话仍在 activeReactLoops 中不重复续;
- 连续续跑超过 AUTO_RESUME_MAX_ATTEMPTS(默认 3)后停止并通知人工介入;
- 对可续跑项先回写递增计数再执行 resumeReactLoopFromCheckpoint,避免无限重试风暴。
flowchart TD
CP["getAllReactCheckpoints()"] --> Filter{"过滤条件"}
Filter --> |终态| Skip["跳过"]
Filter --> |已在执行| Skip
Filter --> |超限| Exhaust["标记 justExhausted"]
Filter --> |可续跑| Target["加入 targets(nextAttempts++)"]
Target --> Save["saveReactCheckpoint(autoResumeAttempts++)"]
Save --> Resume["resumeReactLoopFromCheckpoint(sessionId)"]
Exhaust --> Notify["notifyAutoResumeExhausted()"]
图表来源
章节来源
- src/background/auto-resume.js#L20-L24
- src/background/auto-resume.js#L39-L55
- src/background/index.js#L65-L81
代码示例与最佳实践
- 正确处理 Service Worker 生命周期事件:
- 在 keepalive 端口 onDisconnect 中延迟复核面板是否真关闭,避免误判;
- 在 SW 重启时拒绝 Promise 并携带 checkpoint 元数据供前端恢复;
- 在任务结束时清理 keepalive 端口、定时器、AbortController 等。
- 避免内存泄漏:
- 及时删除 pendingCallApiSessionIds、pendingCancelApiMap;
- 清理流式元素引用(deleteStreamingState);
- 注销 keepalive 端口与身份映射。
章节来源
- src/background/index.js#L199-L218
- src/side_panel/chat-manager.js#L3309-L3318
- src/background/state.js#L300-L305
依赖关系分析
- background/index.js 依赖 state.js(keepalive 端口注册)、react-loop.js(ReAct 循环与恢复)、auto-resume.js(闹钟续跑)、notifier.js(通知)、panel-visibility.js(面板可见性);
- side_panel/chat-manager.js 依赖 background/index.js(通过 runtime.connect 与 sendMessage);
- react-loop.js 依赖 storage/db.js(checkpoint 持久化)、sw-heartbeat.js(保活心跳)、auto-resume.js(闹钟续跑)。
graph LR Index["background/index.js"] --> State["state.js"] Index --> Loop["react-loop.js"] Index --> Auto["auto-resume.js"] Index --> Notifier["notifier.js"] Index --> Visibility["panel-visibility.js"] Chat["side_panel/chat-manager.js"] --> Index Loop --> DB["storage/db.js"] Loop --> Heartbeat["sw-heartbeat.js"] Loop --> Auto
图表来源
- src/background/index.js#L1-L20
- src/side_panel/chat-manager.js#L3236-L3240
- src/background/react-loop.js#L1-L20
章节来源
- src/background/index.js#L1-L20
- src/side_panel/chat-manager.js#L3236-L3240
- src/background/react-loop.js#L1-L20
性能与调试建议
- 减少不必要的异步操作:
- 优先使用同步镜像(如 _activeTabByWindow、_openPanelsByWindow)避免多次 tabs.query;
- 在 keepalive 端口 onMessage 中只做必要的数据更新,避免阻塞。
- 合理使用缓存:
- MCP 工具查询缓存、Skill Prompts 缓存避免重复网络请求;
- checkpoint 持久化应幂等,避免重复写入。
- 调试技巧:
- 搜索日志关键字 "checkpoint"、"SW_RESTARTED"、"keepalive" 定位问题;
- 使用浏览器开发者工具的 Service Worker 控制台观察 SW 生命周期;
- 在自动续跑耗尽时检查桌面通知是否触发。
[本节为通用指导,不直接分析具体文件]
故障排查指南
- SW 被杀死但任务未恢复:
- 检查 auto-resume 闹钟是否注册成功;
- 检查 checkpoint 是否持久化成功;
- 查看 decideAutoResume 过滤逻辑是否因终态或超限跳过。
- keepalive 端口断开但未清理:
- 检查 unregisterKeepalivePort 是否被调用;
- 确认 onDisconnect 延迟复核逻辑是否误判。
- SW 重启后前端未收到 SW_RESTARTED:
- 检查 isReconnection 判断是否正确;
- 确认 activeReactLoops 中是否确实不存在该 sessionId。
章节来源
- src/background/auto-resume.js#L39-L55
- src/background/index.js#L172-L197
- src/background/state.js#L300-L305
结论
本仓库通过 keepalive 端口连接、任务级心跳、checkpoint 持久化与闹钟续跑的多层机制,有效应对 MV3 Service Worker 的生命周期硬约束。在实际开发中,应重点关注:
- keepalive 端口的正确建立与清理;
- SW 重启检测与前端 Promise 的正确拒绝;
- checkpoint 的幂等持久化与自动续跑的安全阀;
- 可选链保护与存储访问降级的竞态防护。
这些实践确保了长时间任务在 SW 被系统回收后仍能可靠恢复,同时避免了内存泄漏与资源泄露问题。