mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
fix(gateway): preserve active work during recovered Node restarts (#153435)
* fix(gateway): preserve work during recovered Node restarts Let Unix Node recovery wrappers respect the existing Gateway stop budget. Share startup-safe command classification and deadline facts, and resolve managed restart intent against the live serving owner in its write transaction with current update authority. Preserve the existing public PID intent API, record format, and Windows behavior. Keep cold lifecycle test preparation outside timed hooks. Inspired by @ly85206559's wrapper-grace approach in #147054; this repair is independently authored from main. Related: #146956. Windows cooperative shutdown remains a follow-up. * refactor(gateway): stabilize restart callback and test setup Declare receiver-independent restart intent callbacks as arrows and retain typed signal spy handles for lint-safe assertions. Move cold lifecycle imports into test collection so worker preparation cannot consume timed setup hooks.
This commit is contained in:
parent
388d856006
commit
2ce0573b02
38 changed files with 1108 additions and 400 deletions
|
|
@ -3,47 +3,21 @@ import { spawn, spawnSync } from "node:child_process";
|
|||
import { lstatSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { consumeRootOptionToken as consumeLauncherRootOptionToken } from "./cli-root-options.mjs";
|
||||
import { isForegroundGatewayRunArgv } from "./gateway-run-argv.mjs";
|
||||
import {
|
||||
GATEWAY_SERVICE_STOP_TIMEOUT_MS,
|
||||
LAUNCH_AGENT_EXIT_TIMEOUT_SECONDS,
|
||||
} from "./gateway-shutdown-budget.mjs";
|
||||
import {
|
||||
detectCurrentSqliteCapabilities,
|
||||
nodeRuntimeFailure,
|
||||
SQLITE_CAPABILITY_PROBE,
|
||||
} from "./node-sqlite.mjs";
|
||||
|
||||
const LAUNCHER_ROOT_BOOLEAN_FLAGS = new Set(["--dev", "--no-color"]);
|
||||
const LAUNCHER_ROOT_VALUE_FLAGS = new Set(["--profile", "--log-level", "--container"]);
|
||||
export { consumeLauncherRootOptionToken };
|
||||
export const isNativeHookRelayInvocation = (argv) => argv[2] === "hooks" && argv[3] === "relay";
|
||||
|
||||
const isLauncherRootOptionValueToken = (arg) => {
|
||||
if (!arg || arg === "--") {
|
||||
return false;
|
||||
}
|
||||
if (!arg.startsWith("-")) {
|
||||
return true;
|
||||
}
|
||||
return /^-\d+(?:\.\d+)?$/.test(arg);
|
||||
};
|
||||
|
||||
export const consumeLauncherRootOptionToken = (args, index) => {
|
||||
const arg = args[index];
|
||||
if (!arg) {
|
||||
return 0;
|
||||
}
|
||||
if (LAUNCHER_ROOT_BOOLEAN_FLAGS.has(arg)) {
|
||||
return 1;
|
||||
}
|
||||
if (
|
||||
arg.startsWith("--profile=") ||
|
||||
arg.startsWith("--log-level=") ||
|
||||
arg.startsWith("--container=")
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
if (LAUNCHER_ROOT_VALUE_FLAGS.has(arg)) {
|
||||
return isLauncherRootOptionValueToken(args[index + 1]) ? 2 : 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Mirror the entry's foreground Gmail policy: a wrapper would kill that run before descendant cleanup finishes.
|
||||
export const isForegroundGmailRunInvocation = (argv) => {
|
||||
const args = argv.slice(2);
|
||||
|
|
@ -70,6 +44,17 @@ const respawnSignalForceKillGraceMs = 1_000;
|
|||
const respawnSignalHardExitGraceMs = 1_000;
|
||||
|
||||
export const runRespawnedChild = (command, args, env) => {
|
||||
const launchdService = env.OPENCLAW_LAUNCHD_LABEL?.trim();
|
||||
const serviceStopTimeoutMs =
|
||||
process.platform === "darwin" && launchdService && env.XPC_SERVICE_NAME === launchdService
|
||||
? LAUNCH_AGENT_EXIT_TIMEOUT_SECONDS * 1_000
|
||||
: GATEWAY_SERVICE_STOP_TIMEOUT_MS;
|
||||
// The serving Gateway owns drain and cleanup. Reap a stuck child only in the
|
||||
// supervisor's exit margin, after that owner has had its full shutdown budget.
|
||||
const signalExitGraceMs =
|
||||
process.platform !== "win32" && isForegroundGatewayRunArgv(process.argv)
|
||||
? serviceStopTimeoutMs - respawnSignalForceKillGraceMs - respawnSignalHardExitGraceMs
|
||||
: respawnSignalExitGraceMs;
|
||||
const stdioIsTerminal = process.stdin.isTTY || process.stdout.isTTY;
|
||||
const child = spawn(command, args, {
|
||||
stdio: "inherit",
|
||||
|
|
@ -131,7 +116,7 @@ export const runRespawnedChild = (command, args, env) => {
|
|||
}
|
||||
signalExitTimer = setTimeout(() => {
|
||||
requestChildTermination();
|
||||
}, respawnSignalExitGraceMs);
|
||||
}, signalExitGraceMs);
|
||||
signalExitTimer.unref?.();
|
||||
};
|
||||
for (const signal of respawnSignals) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue