跳转至

Tab 生命周期与看门狗

类型:wiki(知识沉淀) 描述:抓取 Tab 的创建/销毁完整流程 + v0.10.15 看门狗的兜底机制 最后更新:2026-05-26(v0.10.15) 相关源码src/utils/scrape-window.tssrc/utils/scrape-watchdog.tssrc/entrypoints/background/batch-controller.ts 相关 wikishared-queue-architecture.md

是什么

抓取过程会开大量浏览器 Tab: - 地图实开 → openHarvestTab(url) 在共享窗口里开(active: false) - 网站抓取 → openScrapeTab(url) 同一个共享窗口 - 人机验证 → openVerifyTab() 新建可见窗口(独立窗口,不在共享里)

每个 Tab 的正常生命周期 < 60 秒。但长跑时会有各种漏的场景,需要看门狗兜底。

Tab 生命周期(正常)

[ openTabForTask ]
activeTabs.set(tabId, ActiveTab{ createdAt, timer 60s }) + tabToTaskIdMap.set
[ 加载 → content script 发 batch-tab-ready ]
  ↓ onTabReady → state: 'running'
[ 抓数据 → 发 batch-done ]
  ↓ onTabDone → finalizeTab(tabId, 'done')
    ├─ clearTimeout(timer)
    ├─ activeTabs.delete
    ├─ closeHarvestTab(tabId) → browser.tabs.remove
    └─ maybeCloseSharedWindow(窗口 0 tab 时关)

异常路径 + 兜底层级

正常完成 ─→ onTabDone
60s 超时 ─→ finalizeTab('timeout')
用户手动关 ─→ tabs.onRemoved → finalizeTab('tab-removed')
异常崩溃 ─→ try/finally 里 closeScrapeTab(tabId)
─────────以上是 v0.10.0─v0.10.14 的兜底─────────
SW 重启 ─→ restoreBatch 恢复 + forceCloseSharedWindow(旧窗口杀掉)
─────────以上不够,长跑仍会卡死────────────────
v0.10.15+ 看门狗 ─→ 5 分钟周期巡检 + 3 次累积自动重启

v0.10.15 看门狗设计

解决的真实问题

用户反馈:「长时间运行后,电脑卡死,页面没正常关闭,浏览器打开一堆 Tab。」

根因分析(按概率排序): 1. SW 被 Chromium 强杀重启 → 内存 activeTabs Map 清零 → 浏览器里的真 Tab 还在 → 没人会关它 2. maybeCloseSharedWindow 只在「窗口 0 tab」才关engine_heartbeat 每分钟唤起新 worker → 永远不会 0 → 累积 3. 60s TAB_TIMEOUT 失效:Chromium 后台节流可能让 setTimeout 推迟甚至跳过 4. engine_heartbeat 只跑业务,没人巡检孤儿

设计

维度 设计
调度方式 chrome.alarms API(SW 友好),不用 setInterval
Alarm 名 scrape_watchdog
默认周期 5 分钟(保守,可调 2-30)
默认僵尸阈值 5 分钟(远超 60s 正常完成)
默认重启阈值 3 次(= 15 分钟连续异常)
关闭路径 引擎 OFF / 抓取失败 15 次自动停止 → 一并清 alarm
重启后行为 v0.10.23 起默认自动恢复(60s 冷却后自动 pumpTasks);连续 3 次自动恢复都失败才停下要求人工

巡检步骤

1. 读 settings.watchdogEnabled —— 关掉就直接 return
2. 读 is_engine_running —— 引擎没跑就 return
3. 列共享窗口里所有 tab
4. 对比 activeTabs Map:
   ├─ 窗口里 + 不在 Map + 是 http(s) URL → 【孤儿】browser.tabs.remove
   └─ 排除 about:blank / chrome:// 占位 tab
5. 调 getZombieTabIds(staleMs):
   └─ activeTabs 里 createdAt > 阈值 → forceFinalizeTab('watchdog-zombie')
6. 收尾:窗口里 0 http tab + 调度器空 → forceCloseSharedWindow
7. 累积计数(持久化到 local:watchdogConsecutive,跨 SW 重启):
   ├─ 本次清掉 ≥1 个 → consecutive++
   ├─ 本次干净 → consecutive = 0
   └─ consecutive ≥ limit → 核弹重启:
        ├─ forceCloseSharedWindow
        ├─ nukeAllSchedulerState(清 activeTabs / paging / tabToTaskIdMap,
        │   把 running 任务降级为 paused)
        ├─ forceResetLocks
        ├─ browser.notifications 系统通知
        └─ consecutive = 0
8. 写 page-log:type=watchdog,error 字段携带统计

关键代码位置

文件 位置 作用
scrape-watchdog.ts runWatchdog 巡检入口
scrape-watchdog.ts syncWatchdogAlarm 创建/重建 alarm
batch-controller.ts getActiveTabIds 给 watchdog 拿快照
batch-controller.ts getZombieTabIds(staleMs) 算僵尸列表
batch-controller.ts forceFinalizeTab watchdog 用的强制结束
batch-controller.ts nukeAllSchedulerState 核弹重置
background/index.ts onAlarm 监听器 触发 runWatchdog
background/index.ts start/stop-engine 联动 syncWatchdogAlarm
engine-manager.ts handleScrapeFailure 15 次失败时一并清 watchdog alarm

配置项(settings)

字段 默认 范围 含义
watchdogEnabled true bool 总开关
watchdogInterval 5 2-30 分钟,巡检周期
watchdogStaleThreshold 5 2-30 分钟,单 tab 寿命阈值
watchdogConsecutiveLimit 3 1-10 次,连续异常触发自动重启
watchdogAutoResume true bool v0.10.23 — 重启后是否自动 pumpTasks 继续抓取
watchdogAutoResumeCooldownSec 10 0-600 v0.10.24 — 重启后冷却秒数(让 SW / 网络喘口气)
watchdogAutoResumeMaxCycles 9999 1-99999 v0.10.24 — 默认无限次(除非用户点停止);保守用户可调小

自动恢复设计(v0.10.23 SPEC-002)

核弹重启后默认不要求手动

连续 N 次累积异常 → 触发核弹
读 settings.watchdogAutoResume + storage.watchdogAutoResumeCycles
shouldAutoResume = autoResume && cycles < maxCycles
shouldAutoResume = true:
   ├─ nukeAllSchedulerState({ keepTasksRunning: true })   ← 不降级 task
   ├─ forceCloseSharedWindow + forceResetLocks
   ├─ cycles++ 持久化到 storage
   ├─ 通知用户「自动恢复中(第 N/M 次)」(priority 1,温和)
   └─ setTimeout(cooldownMs) → pumpTasks() + manageQueue()
shouldAutoResume = false(达上限 / 用户关闭):
   ├─ nukeAllSchedulerState({ keepTasksRunning: false })  ← 降级 paused(老行为)
   ├─ cycles = 0
   └─ 通知用户「自动恢复达上限,请人工」(priority 2,强烈)

每次巡检干净(orphans=0, zombies=0)→ cycles = 0   ← 避免长期累积误触发

local:watchdogAutoResumeCycles 持久化跨 SW 重启(同 watchdogConsecutive)。

易踩坑

⚠️ 坑 1:不要在共享窗口外的 tab 上动手

watchdog 必须用 browser.tabs.query({ windowId: sharedWinId }) 严格限定窗口。如果换成 browser.tabs.query({}) 扫全部 tab → 可能误关用户的私人 tab,灾难。

⚠️ 坑 2:累积计数要持久化

local:watchdogConsecutive 必须存 storage,不能放模块内存。否则 SW 重启 → 内存清零 → 永远到不了 limit → 自动重启失效。

⚠️ 坑 3:核弹重启不要清 tasks Map

nukeAllSchedulerState 把 running 任务降级为 paused,不删除。这样用户能看到「哦它停了」、点恢复继续 —— 而不是「咦我的任务呢」。

⚠️ 坑 4:watchdog 不能挂

runWatchdog 整体 try/catch 吞所有异常 + 写日志。它如果自己挂了 = 兜底层失效。

⚠️ 坑 5:openVerifyTab 不在巡检范围

人机验证 tab 是独立窗口的可见 tab,不在 sharedWinId 内 → watchdog 不会动它。这是对的(用户可能正在做验证)。但如果用户忘了关 → 累积。当前不处理,看后续是否需要单独机制识别"验证完成但没关"。

修改这块时要 / 不要做什么

  • ✅ 加新巡检项 → 在 runWatchdog 中加步骤 + 单独统计 + 写日志
  • ✅ 调整默认值 → 改 storage-data.tsDefaultSettings 即可
  • ❌ 不要把 watchdog 周期降到 < 2 分钟(CPU 浪费)
  • ❌ 不要直接 browser.tabs.query({}) 扫全部(误伤用户 tab)
  • ❌ 不要让累积计数仅存内存(SW 重启会丢)

版本里程碑

版本 事件
v0.9.x 仅靠 60s TAB_TIMEOUT + try/finally 兜底,长跑会卡
v0.10.0 共享窗口 + maybeCloseSharedWindow,但仍有累积场景
v0.10.15 首次引入看门狗 — 5 分钟巡检 + 3 次累积自动重启 + UI 设置
v0.10.20 mutex 防并发 / nuke 真关 tab / 清 consecutive — ISSUE-0015
v0.10.23 核弹后自动恢复(默认 ON) — 用户不需要手动点开始 — SPEC-002
v0.10.24 自动恢复默认调激进:cooldown 60→10s / maxCycles 3→9999(用户:「无限次除非人工停止」)