fix(preview): zombie process cleanup on anatomy load
This commit is contained in:
@@ -792,7 +792,60 @@ async function loadPreviews(projectId: string): Promise<Preview[]> {
|
||||
WHERE project_id = $1 AND state != 'stopped'`,
|
||||
[projectId],
|
||||
);
|
||||
return sortDevPreviewsFrontendFirst(rows).map((r) => ({
|
||||
|
||||
// Filter out zombies: if a server is marked 'running' but the URL returns a 50x
|
||||
// Gateway error or times out, the process died. We mark it stopped so the
|
||||
// UI can trigger an auto-restart.
|
||||
const activePreviews: typeof rows = [];
|
||||
|
||||
await Promise.all(
|
||||
rows.map(async (r) => {
|
||||
if (r.state !== "running") {
|
||||
activePreviews.push(r);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 2500); // Fast 2.5s timeout
|
||||
const ping = await fetch(r.preview_url, {
|
||||
method: "HEAD",
|
||||
signal: controller.signal,
|
||||
});
|
||||
clearTimeout(timeout);
|
||||
|
||||
// 502/503/504 means Traefik is up but the container isn't answering.
|
||||
// 404 means Traefik doesn't even know about the route.
|
||||
if (
|
||||
ping.status === 502 ||
|
||||
ping.status === 503 ||
|
||||
ping.status === 504 ||
|
||||
ping.status === 404
|
||||
) {
|
||||
console.warn(
|
||||
`[anatomy] Preview zombie detected for ${r.preview_url} (HTTP ${ping.status}). Marking stopped.`,
|
||||
);
|
||||
await query(
|
||||
`UPDATE fs_dev_servers SET state = 'stopped' WHERE id = $1`,
|
||||
[r.id],
|
||||
).catch(() => {});
|
||||
} else {
|
||||
activePreviews.push(r);
|
||||
}
|
||||
} catch (e: any) {
|
||||
// If the fetch completely fails (e.g. timeout, DNS failure), it's dead.
|
||||
console.warn(
|
||||
`[anatomy] Preview zombie detected for ${r.preview_url} (${e.message}). Marking stopped.`,
|
||||
);
|
||||
await query(
|
||||
`UPDATE fs_dev_servers SET state = 'stopped' WHERE id = $1`,
|
||||
[r.id],
|
||||
).catch(() => {});
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return sortDevPreviewsFrontendFirst(activePreviews).map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
command: r.command ?? undefined,
|
||||
|
||||
Reference in New Issue
Block a user