perf(web): deduplicate control plane gate state
This commit is contained in:
@@ -2,12 +2,14 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { recordFailure } from "./wbv2-failure-ledger.mjs";
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const abs = (file) => path.join(root, file);
|
||||
const controlRoot = "tests/golden/WBV2";
|
||||
const logPath = `${controlRoot}/continuous-guardian.log`;
|
||||
const checkpointPath = `${controlRoot}/continuous-guardian.json`;
|
||||
const supervisorCheckpointPath = `${controlRoot}/continuous-supervisor.json`;
|
||||
const lockPath = `${controlRoot}/continuous-guardian.lock`;
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
@@ -73,8 +75,15 @@ const releaseLock = () => {
|
||||
};
|
||||
const appendLog = (text) => fs.appendFileSync(abs(logPath), `[${new Date().toISOString()}] ${text}\n`);
|
||||
const writeCheckpoint = (payload) => {
|
||||
const next = { schemaVersion: 1, pid: process.pid, updatedAt: new Date().toISOString(), ...payload };
|
||||
const stable = (value) => { const { pid, updatedAt, ...rest } = value ?? {}; return rest; };
|
||||
try {
|
||||
const current = JSON.parse(fs.readFileSync(abs(checkpointPath), "utf8"));
|
||||
if (JSON.stringify(stable(current)) === JSON.stringify(stable(next))) return;
|
||||
}
|
||||
catch { /* The first checkpoint or an interrupted write is not reusable. */ }
|
||||
const temporaryPath = `${checkpointPath}.tmp-${process.pid}`;
|
||||
fs.writeFileSync(abs(temporaryPath), `${JSON.stringify({ schemaVersion: 1, pid: process.pid, updatedAt: new Date().toISOString(), ...payload }, null, 2)}\n`);
|
||||
fs.writeFileSync(abs(temporaryPath), `${JSON.stringify(next, null, 2)}\n`);
|
||||
fs.renameSync(abs(temporaryPath), abs(checkpointPath));
|
||||
};
|
||||
const readJson = (file) => {
|
||||
@@ -94,23 +103,6 @@ const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, mil
|
||||
let child;
|
||||
let stopRequested = false;
|
||||
let stopSignal;
|
||||
let heartbeatTimer;
|
||||
const writeHeartbeat = () => {
|
||||
if (stopRequested || released) return;
|
||||
try {
|
||||
writeCheckpoint({
|
||||
state: "running",
|
||||
restartCount,
|
||||
current: currentTask(),
|
||||
supervisorPid: child?.pid ?? null,
|
||||
heartbeatAt: new Date().toISOString(),
|
||||
supervisor: supervisorCommand,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
appendLog(`heartbeat-failed error=${error?.message ?? String(error)}`);
|
||||
}
|
||||
};
|
||||
const signalChild = (signal) => {
|
||||
if (!child?.pid) return;
|
||||
try { process.kill(-child.pid, signal); }
|
||||
@@ -158,7 +150,6 @@ let restartCount = 0;
|
||||
try {
|
||||
appendLog(`started pid=${process.pid} supervisor=${supervisorCommand} maxRestarts=${maxRestarts} restartDelayMs=${restartDelayMs}`);
|
||||
writeCheckpoint({ state: "started", restartCount, supervisor: supervisorCommand, current: currentTask() });
|
||||
heartbeatTimer = setInterval(writeHeartbeat, 2000);
|
||||
while (true) {
|
||||
if (stopRequested) {
|
||||
writeCheckpoint({ state: "stopped", restartCount, reason: stopSignal, current: currentTask() });
|
||||
@@ -168,7 +159,7 @@ try {
|
||||
const before = currentTask();
|
||||
writeCheckpoint({ state: "running", restartCount, before, supervisor: supervisorCommand });
|
||||
const result = await runSupervisor();
|
||||
const checkpoint = readJson(checkpointPath);
|
||||
const checkpoint = readJson(supervisorCheckpointPath);
|
||||
const after = currentTask();
|
||||
appendLog(`supervisor-exit code=${result.code} signal=${result.signal ?? "none"} task=${before.task} queue=${after.task} state=${checkpoint?.state ?? "missing"}`);
|
||||
if (stopRequested) {
|
||||
@@ -182,6 +173,7 @@ try {
|
||||
}
|
||||
const decision = safeRestart(checkpoint, after);
|
||||
if (!decision.ok) {
|
||||
recordFailure(root, { task: before.task, phase: "guardian", reason: decision.reason, childExit: result.code });
|
||||
writeCheckpoint({ state: "blocked", restartCount, before, after, reason: decision.reason, supervisorExit: result.code });
|
||||
appendLog(`blocked reason=${decision.reason}; no automatic replay`);
|
||||
guardianExit = 2;
|
||||
@@ -189,6 +181,7 @@ try {
|
||||
}
|
||||
restartCount += 1;
|
||||
if (maxRestarts > 0 && restartCount > maxRestarts) {
|
||||
recordFailure(root, { task: before.task, phase: "guardian", reason: "max_safe_restarts_exceeded", childExit: result.code });
|
||||
writeCheckpoint({ state: "blocked", restartCount, before, after, reason: "max_safe_restarts_exceeded", supervisorExit: result.code });
|
||||
appendLog("blocked reason=max_safe_restarts_exceeded");
|
||||
guardianExit = 3;
|
||||
@@ -201,11 +194,12 @@ try {
|
||||
}
|
||||
catch (error) {
|
||||
guardianExit = 4;
|
||||
try { recordFailure(root, { task: currentTask()?.task ?? "UNKNOWN", phase: "guardian", reason: "guardian_error", detail: error?.message ?? String(error) }); }
|
||||
catch { /* Preserve the original guardian failure. */ }
|
||||
writeCheckpoint({ state: "blocked", restartCount, reason: "guardian_error", error: error?.message ?? String(error) });
|
||||
appendLog(`blocked reason=guardian_error error=${error?.message ?? String(error)}`);
|
||||
}
|
||||
finally {
|
||||
if (heartbeatTimer !== undefined) clearInterval(heartbeatTimer);
|
||||
signalChild("SIGTERM");
|
||||
releaseLock();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user