mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-10 01:39:02 +00:00
Some checks failed
Publish Fork Image to GHCR / Build and Push Fork Image (push) Has been cancelled
CI / Change Classification (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Quality Gates (Extended) (push) Has been cancelled
CI / Docs Sync (Strict) (push) Has been cancelled
CI / Docs Lint (prose — advisory) (push) Has been cancelled
CI / i18n UI Coverage (push) Has been cancelled
CI / Build language matrix (push) Has been cancelled
CI / PR Test Policy (push) Has been cancelled
Publish to Docker Hub / Resolve Docker release metadata (push) Has been cancelled
OpenSSF Scorecard / Scorecard analysis (push) Has been cancelled
semgrep / semgrep (push) Has been cancelled
Wiki Sync / Sync wiki with docs (push) Has been cancelled
CI / E2E Tests (3/9) (push) Has been cancelled
CI / Quality Ratchet (push) Has been cancelled
CI / i18n Validation (push) Has been cancelled
CI / E2E Tests (4/9) (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Package Artifact (push) Has been cancelled
CI / Electron Package Smoke (push) Has been cancelled
CI / Unit Tests (1/8) (push) Has been cancelled
CI / Unit Tests (2/8) (push) Has been cancelled
CI / Unit Tests (3/8) (push) Has been cancelled
CI / Unit Tests (4/8) (push) Has been cancelled
CI / Unit Tests (5/8) (push) Has been cancelled
CI / E2E Tests (5/9) (push) Has been cancelled
CI / Unit Tests (6/8) (push) Has been cancelled
CI / Unit Tests (7/8) (push) Has been cancelled
CI / Unit Tests (8/8) (push) Has been cancelled
CI / Vitest (MCP / autoCombo / UI components) (push) Has been cancelled
CI / Node 24 Compatibility Tests (1/4) (push) Has been cancelled
CI / Node 24 Compatibility Tests (2/4) (push) Has been cancelled
CI / Node 24 Compatibility Tests (3/4) (push) Has been cancelled
CI / Node 24 Compatibility Tests (4/4) (push) Has been cancelled
CI / Node 26 Compatibility Build (push) Has been cancelled
CI / Node 26 Compatibility Tests (1/4) (push) Has been cancelled
CI / Node 26 Compatibility Tests (2/4) (push) Has been cancelled
CI / Node 26 Compatibility Tests (3/4) (push) Has been cancelled
CI / Node 26 Compatibility Tests (4/4) (push) Has been cancelled
CI / Coverage Shard (1/8) (push) Has been cancelled
CI / Coverage Shard (2/8) (push) Has been cancelled
CI / Coverage Shard (3/8) (push) Has been cancelled
CI / Coverage Shard (4/8) (push) Has been cancelled
CI / Coverage Shard (5/8) (push) Has been cancelled
CI / Coverage Shard (6/8) (push) Has been cancelled
CI / Coverage Shard (7/8) (push) Has been cancelled
CI / Coverage Shard (8/8) (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / SonarQube (push) Has been cancelled
CI / PR Coverage Comment (push) Has been cancelled
CI / E2E Tests (1/9) (push) Has been cancelled
CI / E2E Tests (2/9) (push) Has been cancelled
CI / E2E Tests (6/9) (push) Has been cancelled
CI / E2E Tests (7/9) (push) Has been cancelled
CI / E2E Tests (8/9) (push) Has been cancelled
CI / E2E Tests (9/9) (push) Has been cancelled
CI / Integration Tests (1/2) (push) Has been cancelled
CI / Integration Tests (2/2) (push) Has been cancelled
CI / Security Tests (push) Has been cancelled
CI / CI Dashboard (push) Has been cancelled
Publish to Docker Hub / Build Docker (linux/amd64) (push) Has been cancelled
Publish to Docker Hub / Build Docker (linux/arm64) (push) Has been cancelled
Publish to Docker Hub / Publish multi-arch manifests (push) Has been cancelled
Release v3.8.33 — full CHANGELOG in the PR body. Blocking gates green (Build, Lint, Unit Tests 8/8, Package Artifact, Quality Gates, Quality Ratchet, Docs Sync, PR Test Policy, test-vitest). Admin-merged over a Node 26 future-compat timer flake (1/4) + an E2E UI flake (3/9) — both verified non-deterministic; full test:unit validated locally (16936 pass).
56 lines
2.3 KiB
JavaScript
56 lines
2.3 KiB
JavaScript
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));
|
|
}
|
|
}
|