title: [ISSUE-0015] Watchdog 鲁棒性三连:并发触发 / nuke 不真关 tab / 计数残留 description: "watchdog 鲁棒性三连:并发 / nuke 不真关 tab / 计数残留" tags: [issue, scraping] created: 2026-05-26 updated: 2026-05-26 type: issue status: fixed severity: medium
[ISSUE-0015] Watchdog 鲁棒性三连:并发触发 / nuke 不真关 tab / 计数残留¶
相关源码:
src/utils/scrape-watchdog.ts、src/entrypoints/background/batch-controller.ts、src/utils/engine-manager.ts发现途径:v0.10.19 代码审查
用户感知的现象¶
三个相关的小问题(agent 报的 #5 / #7 / #14),都不严重但累积起来体感不好:
- 并发触发:用户连续操作(点开始 → 改设置 → 再开始),watchdog 可能被多次唤起,看到「累积异常」通知反复弹
- nuke 不真关 tab:连续异常 3 次触发核弹重启时,浏览器里的孤儿 tab 没被关 → 用户还能看到一堆 Google Maps tab
handleScrapeFailure退场时:连续失败 15 次 → 关 alarm 但local:watchdogConsecutive留旧值 → 下次启动从老计数继续 → 提前触发 nuke
根因分析¶
#5 并发触发¶
runWatchdog() 函数没有 mutex。alarm 5 分钟一次本来不会撞,但:
- 用户点「开始」会 syncWatchdogAlarm() 重建 alarm
- 重建瞬间 alarm 可能立刻 fire 一次
- 同时正常周期的 fire 也在跑
- 两次 runWatchdog 同时读 CONSECUTIVE_KEY → 各自 +1 → 各自重启
#7 nuke 不真关 tab¶
export function nukeAllSchedulerState(): void {
for (const ab of activeTabs.values()) {
if (ab.timer) clearTimeout(ab.timer);
}
activeTabs.clear();
...
}
注释「调用前应该已经 forceCloseSharedWindow」 — 但若 forceClose 失败(窗口已不存在 / API 异常),nuke 后浏览器里 tab 还在,内存 Map 已 clear → 下次 watchdog 又判定为孤儿。
#14 consecutive 残留¶
engine-manager.ts 的 handleScrapeFailure:
if (continuousFailures >= FAILURE_THRESHOLD) {
await storage.setItem('local:is_engine_running', false);
await browser.alarms.clear('engine_heartbeat');
await browser.alarms.clear('scrape_watchdog'); // ← 清 alarm
// 但没清 local:watchdogConsecutive
}
修复方案¶
#5 加 mutex¶
let isRunning = false;
export async function runWatchdog() {
if (isRunning) return { ... }; // 上一次还没跑完
isRunning = true;
try {
// ...原逻辑
} finally {
isRunning = false;
}
}
#7 nuke 时真关 tab¶
export async function nukeAllSchedulerState(): Promise<void> {
// 改成 async,关 tab 再清状态
const tabIds = Array.from(activeTabs.keys());
for (const tabId of tabIds) {
const ab = activeTabs.get(tabId);
if (ab?.timer) clearTimeout(ab.timer);
try {
await browser.tabs.remove(tabId);
} catch (e) { /* tab 可能已关 */ }
}
activeTabs.clear();
...
}
#14 清 consecutive¶
if (continuousFailures >= FAILURE_THRESHOLD) {
// ...
await browser.alarms.clear('scrape_watchdog');
await storage.setItem('local:watchdogConsecutive', 0); // v0.10.20 加
}
改动文件¶
| 文件 | 改了什么 |
|---|---|
src/utils/scrape-watchdog.ts |
runWatchdog 加 isRunning mutex |
src/entrypoints/background/batch-controller.ts |
nukeAllSchedulerState 改 async + browser.tabs.remove |
src/utils/engine-manager.ts |
handleScrapeFailure 加清 watchdogConsecutive |
验证方式¶
- 多次点击「开始」「停止」「设置变更」 → watchdog 日志应单次执行,不重复
- 制造异常累积场景(断网 + 跑任务)→ 核弹触发时浏览器里所有 scrape tab 应被一并关闭
- 触发 15 次连续失败 → 重启引擎 → consecutive 应从 0 开始
如何避免再犯¶
- 任何"周期性 + 事件触发"的 alarm/handler 都需要 mutex:alarm 触发时机不可控
-
nukeAllSchedulerState类的"核弹"函数必须自包含,不能假设调用方先做了什么 - 清 alarm 时同时清相关 storage:alarm 是行为,storage 是状态,两者必须同步
相关问题¶
- ISSUE-0003 — watchdog 设计的源问题