Spaces:
Runtime error
Runtime error
File size: 2,361 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | import net from "node:net";
// #4425: bumped from 30s β the old window reset the crash counter too quickly, so during
// an EADDRINUSE cascade the supervisor kept "recovering" then crashing within the window
// and exhausted its restart budget. A longer window keeps the counter meaningful.
export const RESTART_RESET_MS = 60_000;
// #4425: bumped from 2 β more recovery headroom before the supervisor gives up.
export const DEFAULT_MAX_RESTARTS = 3;
/**
* #4425: a clean child exit (code 0) is only intentional when the supervisor itself is
* shutting down. A spontaneous code-0 exit is anomalous β e.g. a systemd `MemoryMax`
* cgroup kill reports the process exited with code 0 β and MUST be restarted, not treated
* as a graceful stop (which left the gateway dead with `Restart=on-failure`).
*/
export function shouldExitInsteadOfRestart(isShuttingDown) {
return isShuttingDown === true;
}
/** Exponential backoff (1s, 2s, 4s, β¦) capped at 10s, matching the prior inline formula. */
export function computeRestartDelayMs(restartCount) {
return Math.min(1000 * 2 ** (Math.max(1, restartCount) - 1), 10_000);
}
/** Resolve true when nothing is listening on `port` (so a restart won't hit EADDRINUSE). */
export function isPortFree(port, host = "127.0.0.1") {
return new Promise((resolve) => {
const tester = net.createServer();
tester.once("error", (err) => {
// EADDRINUSE = something is bound β not free. Any other error β treat as free.
resolve(!(err && err.code === "EADDRINUSE"));
});
tester.once("listening", () => {
tester.close(() => resolve(true));
});
tester.listen(port, host);
});
}
/**
* #4425: wait until `port` is free before respawning. After a crash the OS may not have
* released the listen socket yet; restarting immediately produced the EADDRINUSE cascade
* that exhausted the restart budget. Polls up to `timeoutMs`, then proceeds anyway so a
* stuck port never blocks recovery forever.
*/
export async function waitUntilPortFree(port, timeoutMs = 10_000, intervalMs = 250) {
const p = Number(port);
if (!Number.isFinite(p) || p <= 0) return true;
const deadline = Date.now() + timeoutMs;
for (;;) {
if (await isPortFree(p)) return true;
if (Date.now() >= deadline) return false;
await new Promise((r) => setTimeout(r, intervalMs));
}
}
|