Files
diegosouzapw--omniroute/bin/cli/utils/pid.mjs
T
2026-07-13 13:39:12 +08:00

111 lines
3.2 KiB
JavaScript

import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { resolveDataDir } from "../data-dir.mjs";
const SERVICES = ["server", "mitm", "tunnel/cloudflared", "tunnel/tailscale"];
function getServicePidPath(service) {
return join(resolveDataDir(), service, ".pid");
}
export function writePidFile(service, pid) {
try {
const dir = join(resolveDataDir(), service);
mkdirSync(dir, { recursive: true });
writeFileSync(getServicePidPath(service), String(pid), "utf8");
return true;
} catch {
return false;
}
}
export function readPidFile(service) {
try {
const file = getServicePidPath(service);
if (!existsSync(file)) return null;
const pid = parseInt(readFileSync(file, "utf8").trim(), 10);
return Number.isFinite(pid) ? pid : null;
} catch {
return null;
}
}
export function cleanupPidFile(service) {
try {
unlinkSync(getServicePidPath(service));
} catch {}
}
export function killAllSubprocesses() {
for (const service of SERVICES) {
const pid = readPidFile(service);
if (!pid) continue;
try {
process.kill(pid, "SIGTERM");
} catch {}
cleanupPidFile(service);
}
}
export function isPidRunning(pid) {
if (!pid) return false;
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
export function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// #2460: Default raised from 15s to 60s so Windows users (slower Next.js
// cold start due to filesystem watchers, antivirus, etc.) get a working
// "server ready" signal instead of a phantom timeout while the server is
// still booting. TCP fallback marks the server as ready when the port
// has been listening for >= 3s consecutively but /api/monitoring/health
// has not yet been mounted — common during dev cold start.
export async function waitForServer(port, timeout = 60000) {
const start = Date.now();
let tcpListeningSince = null;
while (Date.now() - start < timeout) {
try {
const res = await fetch(`http://localhost:${port}/api/monitoring/health`, {
signal: AbortSignal.timeout(2000),
});
if (res.ok) return true;
// Server responded but health endpoint is not ready yet — keep
// polling, but the fact that we got a response means TCP is open.
if (tcpListeningSince === null) tcpListeningSince = Date.now();
} catch {
const listening = await isPortListening(port).catch(() => false);
if (listening) {
if (tcpListeningSince === null) tcpListeningSince = Date.now();
if (Date.now() - tcpListeningSince >= 3000) return true;
} else {
tcpListeningSince = null;
}
}
await sleep(500);
}
return false;
}
async function isPortListening(port) {
const net = await import("node:net");
return new Promise((resolve) => {
const socket = net.connect({ host: "127.0.0.1", port, timeout: 1000 });
const finish = (ok) => {
try {
socket.destroy();
} catch {}
resolve(ok);
};
socket.once("connect", () => finish(true));
socket.once("error", () => finish(false));
socket.once("timeout", () => finish(false));
});
}