fix: 巡检报告快照 3 层防重复锁
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
修复巡检报告快照可能多次生成的竞态: 1. reportScheduler._inProgress —— 快照执行 >60s 时, 下一个 tick 进来直接跳过,防止定时 tick 重入 2. index.cjs._isSnapshotRunning 双向互斥 —— reportScheduler.tick 和 IPC 手动触发路径互相感知, 谁先进来谁锁,另一条路径跳过 3. IPC inspection:runReportSnapshot 入口拦截 —— 用户连点"立即巡检"按钮被拒绝 之前已修:renderer scheduler:start 不再触发 scanAllNow。
This commit is contained in:
@@ -1462,6 +1462,11 @@ function registerInspectionHandlers(ipcMain, options) {
|
||||
|
||||
ipcMain.handle("inspection:runReportSnapshot", async (_e, payload) => {
|
||||
const { deviceTargets = [], trigger = "manual" } = payload || {};
|
||||
// 防重入:快照正在执行中就拒绝(用户连点按钮或定时 tick 并发)
|
||||
if (_isSnapshotRunning) {
|
||||
console.warn("[Inspection:runReportSnapshot] 快照正在执行中,拒绝并发请求");
|
||||
return { ok: false, error: "快照正在执行中,请稍候" };
|
||||
}
|
||||
_isSnapshotRunning = true;
|
||||
// 广播 running 状态(页面切换后回来的 renderer 也能收到)
|
||||
try {
|
||||
@@ -2075,6 +2080,11 @@ function registerInspectionHandlers(ipcMain, options) {
|
||||
// ─── Electron 启动时自动启动 reportScheduler ───
|
||||
// 负责:per-device 定时快照 tick(主进程自身 SSH 执行) + dailyReport HH:MM 定时聚合
|
||||
async function _runScheduledSnapshot(deviceIds, trigger = "scheduled") {
|
||||
// 与 IPC 手动触发路径互斥:防止定时 tick 和用户手动按钮并发跑快照
|
||||
if (_isSnapshotRunning) {
|
||||
console.warn("[reportScheduler] _isSnapshotRunning=true(可能是手动触发在执行),跳过本次定时快照");
|
||||
return;
|
||||
}
|
||||
if (!_hostConfigs || Object.keys(_hostConfigs).length === 0) {
|
||||
console.warn("[reportScheduler] _hostConfigs 未初始化,定时快照跳过(需先打开监控页面让 renderer 同步 SSH 凭据到主进程)");
|
||||
return;
|
||||
@@ -2109,10 +2119,13 @@ function registerInspectionHandlers(ipcMain, options) {
|
||||
return;
|
||||
}
|
||||
console.log(`[reportScheduler] 主进程执行定时快照,目标 ${targets.length} 台`);
|
||||
_isSnapshotRunning = true;
|
||||
try {
|
||||
await _executeSnapshot(targets, trigger);
|
||||
} catch (e) {
|
||||
console.error("[reportScheduler] 定时快照执行失败:", e?.message || String(e));
|
||||
} finally {
|
||||
_isSnapshotRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ const dailyReportStore = require("./dailyReportStore.cjs");
|
||||
let _tickTimer = null;
|
||||
let _dailyTimer = null;
|
||||
let _lastDailyTriggerKey = null; // 用于 HH:MM 日报告每日只触发一次
|
||||
let _inProgress = false; // 快照执行中锁 —— 防止执行时间 >60s 时被下一个 tick 重入
|
||||
|
||||
/**
|
||||
* 启动 reportScheduler
|
||||
@@ -40,6 +41,12 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g
|
||||
const cfg = reportScheduleStore.getSchedule(readFn);
|
||||
if (!cfg.enabled) return;
|
||||
|
||||
// 防重入:快照正在执行中就跳过(执行时间 >60s 时下一个 tick 会撞到)
|
||||
if (_inProgress) {
|
||||
console.log("[reportScheduler] tick: 上一个快照还在执行,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const intervalMs = (cfg.defaultIntervalHours || 1) * 3600 * 1000;
|
||||
const lastRun = cfg.lastRunAt || 0;
|
||||
@@ -78,6 +85,7 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g
|
||||
}
|
||||
|
||||
console.log(`[reportScheduler] tick: 到点,${enabledDeviceIds.length} 台设备,interval=${cfg.defaultIntervalHours}h,距上次 ${(sinceLast/60000).toFixed(1)} 分钟`);
|
||||
_inProgress = true;
|
||||
try {
|
||||
await runSnapshotFn(enabledDeviceIds, "scheduled");
|
||||
// 持久化 lastRunAt —— 重启 Electron 也知道上次什么时候跑的
|
||||
@@ -86,6 +94,8 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g
|
||||
console.log("[reportScheduler] ✅ 快照完成,lastRunAt 已持久化");
|
||||
} catch (e) {
|
||||
console.error("[reportScheduler] runSnapshot 失败:", e?.message || String(e));
|
||||
} finally {
|
||||
_inProgress = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user