mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 03:11:35 +00:00
CLI: retry --force until gateway port is free
This commit is contained in:
@@ -2,6 +2,16 @@ import { execFileSync } from "node:child_process";
|
||||
|
||||
export type PortProcess = { pid: number; command?: string };
|
||||
|
||||
export type ForceFreePortResult = {
|
||||
killed: PortProcess[];
|
||||
waitedMs: number;
|
||||
escalatedToSigkill: boolean;
|
||||
};
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function parseLsofOutput(output: string): PortProcess[] {
|
||||
const lines = output.split(/\r?\n/).filter(Boolean);
|
||||
const results: PortProcess[] = [];
|
||||
@@ -50,3 +60,77 @@ export function forceFreePort(port: number): PortProcess[] {
|
||||
}
|
||||
return listeners;
|
||||
}
|
||||
|
||||
function killPids(listeners: PortProcess[], signal: NodeJS.Signals) {
|
||||
for (const proc of listeners) {
|
||||
try {
|
||||
process.kill(proc.pid, signal);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`failed to kill pid ${proc.pid}${proc.command ? ` (${proc.command})` : ""}: ${String(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function forceFreePortAndWait(
|
||||
port: number,
|
||||
opts: {
|
||||
/** Total wait budget across signals. */
|
||||
timeoutMs?: number;
|
||||
/** Poll interval for checking whether lsof reports listeners. */
|
||||
intervalMs?: number;
|
||||
/** How long to wait after SIGTERM before escalating to SIGKILL. */
|
||||
sigtermTimeoutMs?: number;
|
||||
} = {},
|
||||
): Promise<ForceFreePortResult> {
|
||||
const timeoutMs = Math.max(opts.timeoutMs ?? 1500, 0);
|
||||
const intervalMs = Math.max(opts.intervalMs ?? 100, 1);
|
||||
const sigtermTimeoutMs = Math.min(
|
||||
Math.max(opts.sigtermTimeoutMs ?? 600, 0),
|
||||
timeoutMs,
|
||||
);
|
||||
|
||||
const killed = forceFreePort(port);
|
||||
if (killed.length === 0) {
|
||||
return { killed, waitedMs: 0, escalatedToSigkill: false };
|
||||
}
|
||||
|
||||
let waitedMs = 0;
|
||||
const triesSigterm =
|
||||
intervalMs > 0 ? Math.ceil(sigtermTimeoutMs / intervalMs) : 0;
|
||||
for (let i = 0; i < triesSigterm; i++) {
|
||||
if (listPortListeners(port).length === 0) {
|
||||
return { killed, waitedMs, escalatedToSigkill: false };
|
||||
}
|
||||
await sleep(intervalMs);
|
||||
waitedMs += intervalMs;
|
||||
}
|
||||
|
||||
if (listPortListeners(port).length === 0) {
|
||||
return { killed, waitedMs, escalatedToSigkill: false };
|
||||
}
|
||||
|
||||
const remaining = listPortListeners(port);
|
||||
killPids(remaining, "SIGKILL");
|
||||
|
||||
const remainingBudget = Math.max(timeoutMs - waitedMs, 0);
|
||||
const triesSigkill =
|
||||
intervalMs > 0 ? Math.ceil(remainingBudget / intervalMs) : 0;
|
||||
for (let i = 0; i < triesSigkill; i++) {
|
||||
if (listPortListeners(port).length === 0) {
|
||||
return { killed, waitedMs, escalatedToSigkill: true };
|
||||
}
|
||||
await sleep(intervalMs);
|
||||
waitedMs += intervalMs;
|
||||
}
|
||||
|
||||
const still = listPortListeners(port);
|
||||
if (still.length === 0) {
|
||||
return { killed, waitedMs, escalatedToSigkill: true };
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`port ${port} still has listeners after --force: ${still.map((p) => p.pid).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user