主题
Service Worker 生命周期管理
本文引用的源码文件
本文引用的文件
目录
引言
本文面向 Manifest V3 扩展的 Service Worker(SW)生命周期管理,围绕本仓库中“保活端口连接策略、SW 重启检测与自动恢复、checkpoint 持久化与重试、终止竞态防护”等主题展开。该扩展采用双进程架构:Chrome 扩展端(MV3,Service Worker 入口为 src/background/index.js)与独立本地代理端 Agent(Node.js)。文档聚焦扩展端 SW 的生命周期机制,不深入 Agent 内部实现。
项目结构
从 MV3 清单和背景脚本入口可以看出,扩展使用 Service Worker 作为后台进程,并通过侧边栏面板发起长任务;SW 通过 keepalive 端口保持活跃,同时借助闹钟实现跨 SW 回收的自动续跑。
graph TB A["manifest.json<br/>声明 service_worker"] --> B["src/background/index.js<br/>SW 入口与消息路由"] B --> C["src/background/state.js<br/>keepalive 端口/身份注册表"] B --> D["src/background/auto-resume.js<br/>自动续跑闹钟与决策"] E["src/side_panel/chat-manager.js<br/>侧边栏调用与 keepalive 端口"] --> B F["test/unit/sw-heartbeat.unit.test.js<br/>心跳保活回归测试"] --> B
图表来源
- manifest.json#L47-L49
- src/background/index.js#L1-L20
- src/background/state.js#L227-L234
- src/background/auto-resume.js#L1-L24
- src/side_panel/chat-manager.js#L3236-L3239
- test/unit/sw-heartbeat.unit.test.js#L1-L18
章节来源
核心组件
- Service Worker 入口与消息路由:负责 keepalive 端口接入、SW 重启检测、面板可见性通知、消息分发、checkpoint 查询与恢复。
- Keepalive 状态注册表:维护按 sessionId 的 keepalive 端口映射、实例身份(窗口与宿主 tab)、派生会话祖先链解析。
- 自动续跑模块:基于
chrome.alarms创建周期闹钟,扫描 checkpoint 并决定是否自动续跑,限制最大尝试次数。 - 侧边栏调用流程:建立 keepalive 端口、上报实例身份、监听 SW_RESTARTED 通知、统一清理资源。
- 心跳保活测试:验证任务级保活心跳在长时间空闲间隙仍有效。
章节来源
- src/background/index.js#L121-L218
- src/background/state.js#L227-L305
- src/background/auto-resume.js#L1-L119
- src/side_panel/chat-manager.js#L3236-L3318
- test/unit/sw-heartbeat.unit.test.js#L1-L18
架构总览
下图展示 MV3 Service Worker 生命周期关键交互:侧边栏发起 API 时建立 keepalive 端口,SW 接收连接并记录身份;若 SW 被系统回收,闹钟唤醒后扫描 checkpoint 自动续跑;SW 重启后通过端口向侧边栏发送 SW_RESTARTED,前端拒绝 Promise 并提示用户继续执行。
sequenceDiagram
participant Panel as "侧边栏"
participant SW as "Service Worker"
participant State as "状态注册表"
participant Alarm as "闹钟调度"
participant DB as "Checkpoint 存储"
Panel->>SW : "connect('keepalive-<sessionId>')"
SW->>State : "registerKeepalivePort(sessionId, port)"
Panel->>SW : "KEEPALIVE_IDENTITY(windowId)"
SW->>State : "setKeepaliveIdentity(sessionId, identity)"
Note over SW : "API 调用期间保持 SW 活跃"
alt "SW 被系统回收"
Alarm->>SW : "onAlarm(AUTO_RESUME_ALARM_NAME)"
SW->>DB : "getAllReactCheckpoints()"
SW->>SW : "decideAutoResume(checkpoints, activeSessions)"
loop "可续跑目标"
SW->>DB : "saveReactCheckpoint(nextAttempts)"
SW->>SW : "resumeReactLoopFromCheckpoint(sessionId)"
end
end
alt "SW 静默重启"
Panel->>SW : "重连 keepalive-<sessionId>"
SW->>Panel : "SW_RESTARTED(checkpointMeta)"
Panel->>Panel : "reject(callApi promise)"
end
图表来源
- src/side_panel/chat-manager.js#L3236-L3260
- src/background/index.js#L140-L218
- src/background/auto-resume.js#L61-L89
- src/background/index.js#L65-L81
详细组件分析
Keepalive 端口注册与注销
- 注册:侧边栏在发起 API 调用时通过
chrome.runtime.connect({ name: 'keepalive-' + mySessionId })建立长连接;SW 在chrome.runtime.onConnect中识别以keepalive-开头的端口名,提取 sessionId 并调用registerKeepalivePort注册到状态表。 - 身份上报:侧边栏通过端口发送
KEEPALIVE_IDENTITY,携带windowId;SW 解析权威活跃 tab 镜像后回传HOST_TAB_RESOLVED,覆盖侧边栏乐观值。 - 注销:端口断开时调用
unregisterKeepalivePort,清理端口映射与身份信息;注销逻辑带端口校验,防止旧连接迟到误删新连接。
flowchart TD
Start(["侧边栏发起 API"]) --> Connect["建立 keepalive 端口"]
Connect --> Register["SW 注册端口到状态表"]
Register --> Identity["上报实例身份 windowId"]
Identity --> Resolve["SW 解析宿主 tab 并回传"]
Resolve --> Active["API 调用期间保持 SW 活跃"]
Active --> Disconnect{"端口断开?"}
Disconnect --> |是| Unregister["注销端口与身份"]
Unregister --> End(["结束"])
Disconnect --> |否| Active
图表来源
- src/side_panel/chat-manager.js#L3236-L3260
- src/background/index.js#L140-L170
- src/background/state.js#L236-L305
章节来源
- src/side_panel/chat-manager.js#L3236-L3260
- src/background/index.js#L140-L170
- src/background/state.js#L236-L305
SW 重启检测与自动恢复
- 重启检测:当侧边栏重连 keepalive 端口且状态表中已有该 sessionId,SW 判定为重连;若当前无对应 ReAct 循环,则读取 checkpoint 并发送
SW_RESTARTED通知。 - 前端处理:侧边栏收到
SW_RESTARTED后清理定时器与监听器,reject 当前 callApi Promise,并携带 checkpoint 元数据供 UI 展示“继续执行”。 - 自动恢复:SW 启动时注册自动续跑闹钟回调;闹钟触发后扫描所有 checkpoint,过滤终态与会话已运行项,对可续跑项递增
autoResumeAttempts并调用resumeReactLoopFromCheckpoint。
sequenceDiagram participant Panel as "侧边栏" participant SW as "Service Worker" participant State as "状态表" participant DB as "Checkpoint 存储" Panel->>SW : "重连 keepalive-<sessionId>" SW->>State : "hasKeepalivePort(sessionId)" alt "检测到重连且无活动循环" SW->>DB : "getReactCheckpoint(sessionId)" SW->>Panel : "SW_RESTARTED(checkpointMeta)" Panel->>Panel : "reject(callApi)" else "首次连接或仍有活动" SW->>Panel : "正常消息路由" end
图表来源
章节来源
Checkpoint 持久化与重试策略
- 持久化:ReAct 循环每轮推理后保存 checkpoint 到 IndexedDB,包含 sessionId、iteration、interruptedReason、currentMessages、subtaskPlan 等字段。
- 自动续跑决策:
decideAutoResume函数过滤终态原因(user_cancelled/error)、跳过已在 activeReactLoops 中的会话,限制最大尝试次数(默认 3),并对刚耗尽的会话仅通知一次。 - 重试执行:对每个目标 checkpoint,先回写递增后的
autoResumeAttempts,再调用resumeReactLoopFromCheckpoint;失败时记录警告日志但不中断其他会话续跑。
flowchart TD
Start(["闹钟触发"]) --> Load["加载所有 checkpoint"]
Load --> Filter["过滤终态与会话已运行"]
Filter --> Decide{"是否超过最大尝试次数?"}
Decide --> |是| Notify["通知人工介入仅一次"]
Decide --> |否| Update["递增 autoResumeAttempts"]
Update --> Resume["resumeReactLoopFromCheckpoint"]
Resume --> Next{"下一个 checkpoint?"}
Next --> |是| Load
Next --> |否| End(["结束"])
图表来源
章节来源
终止竞态防护方案
- 可选链保护:所有顶层 chrome API 访问均使用可选链(如
chrome.runtime?.onConnect?.addListener?.),避免扩展重载瞬间 API 被剥离导致 TypeError。 - 状态同步:keepalive 端口注册与注销使用原子操作,注销时校验当前端口是否与传入端口一致,防止旧连接迟到误删新连接。
- 降级处理:storage 读取失败、tabs.query 不可用等场景均静默降级,不影响核心功能;面板可见性判定保守按“不可见”处理,宁可多弹不漏弹。
flowchart TD
Start(["SW 启动"]) --> Optional["chrome API 可选链访问"]
Optional --> Register["注册 keepalive 端口"]
Register --> Sync["状态同步:端口与身份"]
Sync --> Graceful{"API 可用?"}
Graceful --> |是| Normal["正常流程"]
Graceful --> |否| Fallback["静默降级"]
Fallback --> End(["安全退出"])
Normal --> End
图表来源
章节来源
依赖关系分析
- 模块耦合:
index.js依赖state.js进行 keepalive 状态管理,依赖auto-resume.js进行自动续跑,依赖notifier.js和panel-visibility.js进行通知与可见性判定。 - 外部依赖:
chrome.runtime、chrome.alarms、chrome.storage、chrome.sidePanel等浏览器 API;IndexedDB 用于 checkpoint 持久化。 - 潜在循环依赖:
auto-resume.js仅依赖shared/logger,避免反向依赖业务逻辑;index.js通过回调注入方式解耦自动续跑执行逻辑。
graph LR Index["background/index.js"] --> State["background/state.js"] Index --> AutoResume["background/auto-resume.js"] Index --> Notifier["background/notifier.js"] Index --> Visibility["background/panel-visibility.js"] Panel["side_panel/chat-manager.js"] --> Index Test["sw-heartbeat.unit.test.js"] --> Index
图表来源
- src/background/index.js#L1-L20
- src/background/auto-resume.js#L18-L18
- src/side_panel/chat-manager.js#L3236-L3239
章节来源
- src/background/index.js#L1-L20
- src/background/auto-resume.js#L18-L18
- src/side_panel/chat-manager.js#L3236-L3239
性能与内存优化
- 内存泄漏防护:
- keepalive 端口在 onDisconnect 时及时注销,避免 Map 无限增长。
- 流式输出状态在 cleanupCallApi 中清理,包括元素引用、累积内容、定时器与监听器。
- AbortController 按会话隔离,取消时立即释放网络请求。
- 资源清理最佳实践:
- 统一清理函数 cleanupCallApi 集中处理端口断开、定时器清除、状态删除。
- 工具级 AbortController 单独管理,不干扰 ReAct 循环取消。
- 性能优化策略:
- MCP 工具查询缓存避免重复网络请求。
- 自动续跑闹钟周期设为 0.5 分钟(30s),平衡响应速度与系统开销。
- 心跳保活通过轻量 API 调用重置闲置计时器,避免频繁保活开销。
章节来源
- src/side_panel/chat-manager.js#L3309-L3318
- src/background/state.js#L313-L342
- src/background/index.js#L115-L119
- src/background/auto-resume.js#L21-L24
- test/unit/sw-heartbeat.unit.test.js#L1-L18
故障排查指南
- SW 未启动或 No SW 错误:检查顶层 chrome API 是否被可选链保护,确认扩展重载瞬间未抛出 TypeError。
- keepalive 端口未生效:确认侧边栏是否正确建立
keepalive-<sessionId>端口,SW 是否成功注册到状态表。 - SW 重启后任务丢失:检查 checkpoint 是否存在,自动续跑是否因终态原因被跳过,最大尝试次数是否已耗尽。
- 面板通知误判:确认实例身份上报是否成功,权威活跃 tab 镜像是否更新,可见性判定是否保守处理缺失值。
- 内存泄漏:检查 keepalive 端口是否及时注销,流式输出状态是否清理,AbortController 是否释放。
章节来源
- src/background/index.js#L136-L140
- src/background/index.js#L172-L197
- src/background/auto-resume.js#L39-L55
- src/side_panel/chat-manager.js#L3309-L3318
结论
本仓库通过 keepalive 端口、闹钟自动续跑、checkpoint 持久化与可选链保护,构建了健壮的 MV3 Service Worker 生命周期管理机制。侧边栏与 SW 之间通过端口通信保持活跃,SW 被系统回收后可自动恢复任务,同时提供用户友好的错误提示与继续执行选项。整体设计注重内存安全、资源清理与性能优化,适合长时间运行的扩展任务场景。