chore(telemetry): implement universal path normalizer and omni-reaper to prevent preview sprawl
This commit is contained in:
@@ -827,31 +827,40 @@ export async function startDevServer(
|
||||
throw new PortOutOfRangeError(opts.port);
|
||||
}
|
||||
|
||||
// 2. Stop ALL tracked rows for this project on the target port.
|
||||
// Previous runs may have crashed or exited without being marked
|
||||
// stopped, causing stale rows to accumulate. We reap them
|
||||
// unconditionally before starting anything new — the AI's intent
|
||||
// is "I want THIS command on THIS port", so most-recent-write-wins.
|
||||
const existingRows = await query<{ id: string; pid: number | null }>(
|
||||
`SELECT id, pid FROM fs_dev_servers
|
||||
WHERE project_id = $1 AND port = $2 AND state IN ('starting','running','failed')`,
|
||||
[opts.projectId, opts.port],
|
||||
// 2. Stop ALL tracked rows for this project on ALL preview ports.
|
||||
// Because our socket reaper is infallible, the AI never needs to
|
||||
// sprawl across multiple ports. We unconditionally reap and stop
|
||||
// every active preview server for this project before starting a new one
|
||||
// to keep the dashboard clean and prevent memory leaks.
|
||||
const existingRows = await query<{
|
||||
id: string;
|
||||
pid: number | null;
|
||||
port: number;
|
||||
}>(
|
||||
`SELECT id, pid, port FROM fs_dev_servers
|
||||
WHERE project_id = $1 AND state IN ('starting','running','failed')`,
|
||||
[opts.projectId],
|
||||
);
|
||||
|
||||
const killPortNodeCmd =
|
||||
`node -e '` +
|
||||
`const fs = require("fs"); ` +
|
||||
`const port = ${opts.port}; ` +
|
||||
`const portsToKill = [${existingRows
|
||||
.map((r) => r.port)
|
||||
.concat(opts.port)
|
||||
.join(",")}]; ` +
|
||||
`try { ` +
|
||||
`const hexPort = port.toString(16).toUpperCase().padStart(4, "0"); ` +
|
||||
`const tcp = fs.readFileSync("/proc/net/tcp", "utf8"); ` +
|
||||
`const inodes = []; ` +
|
||||
`tcp.split("\\n").forEach(line => { ` +
|
||||
`const parts = line.trim().split(/\\s+/); ` +
|
||||
`if (parts.length > 9) { ` +
|
||||
`const local = parts[1]; ` +
|
||||
`for (const p of portsToKill) { ` +
|
||||
`const hexPort = p.toString(16).toUpperCase().padStart(4, "0"); ` +
|
||||
`if (local.endsWith(":" + hexPort)) { inodes.push(parts[9]); } ` +
|
||||
`} ` +
|
||||
`} ` +
|
||||
`}); ` +
|
||||
`if (inodes.length > 0) { ` +
|
||||
`fs.readdirSync("/proc").forEach(file => { ` +
|
||||
@@ -872,7 +881,7 @@ export async function startDevServer(
|
||||
`}); ` +
|
||||
`} ` +
|
||||
`} catch (e) { ` +
|
||||
`try { require("child_process").execSync("fuser -k -9 ${opts.port}/tcp 2>/dev/null || true"); } catch (err) {} ` +
|
||||
`try { require("child_process").execSync("fuser -k -9 " + portsToKill.join(",") + "/tcp 2>/dev/null || true"); } catch (err) {} ` +
|
||||
`}'`;
|
||||
|
||||
for (const row of existingRows) {
|
||||
|
||||
Reference in New Issue
Block a user