mirror of https://github.com/openclaw/openclaw.git
fix: harden windows gateway stop cleanup
This commit is contained in:
parent
51fe0bf663
commit
6cb8729952
|
|
@ -121,6 +121,52 @@ describe("Scheduled Task stop/restart cleanup", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("force-kills remaining busy port listeners when the first stop pass does not free the port", async () => {
|
||||||
|
await withWindowsEnv(async ({ env }) => {
|
||||||
|
await writeGatewayScript(env);
|
||||||
|
schtasksResponses.push(
|
||||||
|
{ code: 0, stdout: "", stderr: "" },
|
||||||
|
{ code: 0, stdout: "", stderr: "" },
|
||||||
|
{ code: 0, stdout: "", stderr: "" },
|
||||||
|
);
|
||||||
|
findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([4242]);
|
||||||
|
inspectPortUsage.mockResolvedValueOnce({
|
||||||
|
port: 18789,
|
||||||
|
status: "busy",
|
||||||
|
listeners: [{ pid: 4242, command: "node.exe" }],
|
||||||
|
hints: [],
|
||||||
|
});
|
||||||
|
for (let i = 0; i < 20; i += 1) {
|
||||||
|
inspectPortUsage.mockResolvedValueOnce({
|
||||||
|
port: 18789,
|
||||||
|
status: "busy",
|
||||||
|
listeners: [{ pid: 4242, command: "node.exe" }],
|
||||||
|
hints: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
inspectPortUsage
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
port: 18789,
|
||||||
|
status: "busy",
|
||||||
|
listeners: [{ pid: 5252, command: "node.exe" }],
|
||||||
|
hints: [],
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
port: 18789,
|
||||||
|
status: "free",
|
||||||
|
listeners: [],
|
||||||
|
hints: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const stdout = new PassThrough();
|
||||||
|
await stopScheduledTask({ env, stdout });
|
||||||
|
|
||||||
|
expect(killProcessTree).toHaveBeenNthCalledWith(1, 4242, { graceMs: 300 });
|
||||||
|
expect(killProcessTree).toHaveBeenNthCalledWith(2, expect.any(Number), { graceMs: 300 });
|
||||||
|
expect(inspectPortUsage.mock.calls.length).toBeGreaterThanOrEqual(22);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("falls back to inspected gateway listeners when sync verification misses on Windows", async () => {
|
it("falls back to inspected gateway listeners when sync verification misses on Windows", async () => {
|
||||||
await withWindowsEnv(async ({ env }) => {
|
await withWindowsEnv(async ({ env }) => {
|
||||||
await writeGatewayScript(env);
|
await writeGatewayScript(env);
|
||||||
|
|
|
||||||
|
|
@ -463,6 +463,24 @@ async function waitForGatewayPortRelease(port: number, timeoutMs = 5_000): Promi
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function terminateBusyPortListeners(port: number): Promise<number[]> {
|
||||||
|
const diagnostics = await inspectPortUsage(port).catch(() => null);
|
||||||
|
if (diagnostics?.status !== "busy") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const pids = Array.from(
|
||||||
|
new Set(
|
||||||
|
diagnostics.listeners
|
||||||
|
.map((listener) => listener.pid)
|
||||||
|
.filter((pid): pid is number => Number.isFinite(pid) && pid > 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for (const pid of pids) {
|
||||||
|
await terminateGatewayProcessTree(pid, 300);
|
||||||
|
}
|
||||||
|
return pids;
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveFallbackRuntime(env: GatewayServiceEnv): Promise<GatewayServiceRuntime> {
|
async function resolveFallbackRuntime(env: GatewayServiceEnv): Promise<GatewayServiceRuntime> {
|
||||||
const port = resolveConfiguredGatewayPort(env);
|
const port = resolveConfiguredGatewayPort(env);
|
||||||
if (!port) {
|
if (!port) {
|
||||||
|
|
@ -655,7 +673,14 @@ export async function stopScheduledTask({ stdout, env }: GatewayServiceControlAr
|
||||||
await terminateScheduledTaskGatewayListeners(effectiveEnv);
|
await terminateScheduledTaskGatewayListeners(effectiveEnv);
|
||||||
await terminateInstalledStartupRuntime(effectiveEnv);
|
await terminateInstalledStartupRuntime(effectiveEnv);
|
||||||
if (stopPort) {
|
if (stopPort) {
|
||||||
await waitForGatewayPortRelease(stopPort);
|
const released = await waitForGatewayPortRelease(stopPort);
|
||||||
|
if (!released) {
|
||||||
|
await terminateBusyPortListeners(stopPort);
|
||||||
|
const releasedAfterForce = await waitForGatewayPortRelease(stopPort, 2_000);
|
||||||
|
if (!releasedAfterForce) {
|
||||||
|
throw new Error(`gateway port ${stopPort} is still busy after stop`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
stdout.write(`${formatLine("Stopped Scheduled Task", taskName)}\n`);
|
stdout.write(`${formatLine("Stopped Scheduled Task", taskName)}\n`);
|
||||||
}
|
}
|
||||||
|
|
@ -684,7 +709,14 @@ export async function restartScheduledTask({
|
||||||
await terminateScheduledTaskGatewayListeners(effectiveEnv);
|
await terminateScheduledTaskGatewayListeners(effectiveEnv);
|
||||||
await terminateInstalledStartupRuntime(effectiveEnv);
|
await terminateInstalledStartupRuntime(effectiveEnv);
|
||||||
if (restartPort) {
|
if (restartPort) {
|
||||||
await waitForGatewayPortRelease(restartPort);
|
const released = await waitForGatewayPortRelease(restartPort);
|
||||||
|
if (!released) {
|
||||||
|
await terminateBusyPortListeners(restartPort);
|
||||||
|
const releasedAfterForce = await waitForGatewayPortRelease(restartPort, 2_000);
|
||||||
|
if (!releasedAfterForce) {
|
||||||
|
throw new Error(`gateway port ${restartPort} is still busy before restart`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const res = await execSchtasks(["/Run", "/TN", taskName]);
|
const res = await execSchtasks(["/Run", "/TN", taskName]);
|
||||||
if (res.code !== 0) {
|
if (res.code !== 0) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue