Skip to content

Service Worker 生命周期管理 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与调试建议
  8. 故障排查指南
  9. 结论

引言 ​

本文围绕 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

图表来源

章节来源

核心组件 ​

  • 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 取消。

章节来源

架构总览 ​

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 + 提示用户继续执行

图表来源

详细组件分析 ​

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(["等待任务完成或断开"])

图表来源

章节来源

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)

图表来源

章节来源

保活心跳机制(方案 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 自检失败)给出警告但不崩溃。

章节来源

自动续跑机制(方案 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()"]

图表来源

章节来源

代码示例与最佳实践 ​

  • 正确处理 Service Worker 生命周期事件:
    • 在 keepalive 端口 onDisconnect 中延迟复核面板是否真关闭,避免误判;
    • 在 SW 重启时拒绝 Promise 并携带 checkpoint 元数据供前端恢复;
    • 在任务结束时清理 keepalive 端口、定时器、AbortController 等。
  • 避免内存泄漏:
    • 及时删除 pendingCallApiSessionIds、pendingCancelApiMap;
    • 清理流式元素引用(deleteStreamingState);
    • 注销 keepalive 端口与身份映射。

章节来源

依赖关系分析 ​

  • 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

图表来源

章节来源

性能与调试建议 ​

  • 减少不必要的异步操作:
    • 优先使用同步镜像(如 _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。

章节来源

结论 ​

本仓库通过 keepalive 端口连接、任务级心跳、checkpoint 持久化与闹钟续跑的多层机制,有效应对 MV3 Service Worker 的生命周期硬约束。在实际开发中,应重点关注:

  • keepalive 端口的正确建立与清理;
  • SW 重启检测与前端 Promise 的正确拒绝;
  • checkpoint 的幂等持久化与自动续跑的安全阀;
  • 可选链保护与存储访问降级的竞态防护。

这些实践确保了长时间任务在 SW 被系统回收后仍能可靠恢复,同时避免了内存泄漏与资源泄露问题。