chore(windows): instrument detached daemon launch
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { spawn, type ChildProcess } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { app, ipcMain } from "electron";
|
||||
import { loadConfig, resolvePaseoHome, getOrCreateServerId } from "@getpaseo/server";
|
||||
@@ -27,6 +27,7 @@ const STOP_TIMEOUT_MS = 15_000;
|
||||
const KILL_TIMEOUT_MS = 3_000;
|
||||
const DETACHED_STARTUP_GRACE_MS = 1200;
|
||||
const DEFAULT_ELECTRON_DEV_SERVER_URL = "http://localhost:8081";
|
||||
const DAEMON_LAUNCH_LOG_FILENAME = "daemon-launch.log";
|
||||
|
||||
type DesktopDaemonState = "starting" | "running" | "stopped" | "errored";
|
||||
|
||||
@@ -75,6 +76,10 @@ function logFilePath(): string {
|
||||
return path.join(getPaseoHome(), DAEMON_LOG_FILENAME);
|
||||
}
|
||||
|
||||
function launchLogFilePath(): string {
|
||||
return path.join(getPaseoHome(), DAEMON_LAUNCH_LOG_FILENAME);
|
||||
}
|
||||
|
||||
function isProcessRunning(pid: number): boolean {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
@@ -140,6 +145,24 @@ function tailFile(filePath: string, lines = 50): string {
|
||||
}
|
||||
}
|
||||
|
||||
function appendLaunchLog(message: string, details?: Record<string, unknown>): void {
|
||||
const line = [
|
||||
new Date().toISOString(),
|
||||
"[desktop daemon]",
|
||||
`pid=${process.pid}`,
|
||||
message,
|
||||
details ? JSON.stringify(details) : "",
|
||||
]
|
||||
.filter((part) => part.length > 0)
|
||||
.join(" ");
|
||||
|
||||
try {
|
||||
appendFileSync(launchLogFilePath(), `${line}\n`, "utf-8");
|
||||
} catch {
|
||||
// Keep launch debugging best-effort so desktop startup behavior is unchanged.
|
||||
}
|
||||
}
|
||||
|
||||
function buildDesktopDaemonCorsOriginsEnv(): string | undefined {
|
||||
const origins = new Set(
|
||||
(process.env.PASEO_CORS_ORIGINS ?? "")
|
||||
@@ -303,6 +326,16 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
|
||||
},
|
||||
});
|
||||
|
||||
appendLaunchLog("starting detached daemon", {
|
||||
appIsPackaged: app.isPackaged,
|
||||
daemonRunnerEntry: daemonRunner.entryPath,
|
||||
daemonRunnerExecArgv: daemonRunner.execArgv,
|
||||
command: invocation.command,
|
||||
args: invocation.args,
|
||||
listen: process.env.PASEO_LISTEN ?? null,
|
||||
corsOrigins: corsOrigins ?? null,
|
||||
});
|
||||
|
||||
const child: ChildProcess = spawn(
|
||||
invocation.command,
|
||||
invocation.args,
|
||||
@@ -313,6 +346,12 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
|
||||
},
|
||||
);
|
||||
|
||||
appendLaunchLog("detached spawn returned", {
|
||||
childPid: child.pid ?? null,
|
||||
spawnfile: child.spawnfile,
|
||||
spawnargs: child.spawnargs,
|
||||
});
|
||||
|
||||
child.unref();
|
||||
|
||||
// Wait for process to survive the grace period
|
||||
@@ -327,15 +366,26 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
|
||||
const timer = setTimeout(() => finish(false), DETACHED_STARTUP_GRACE_MS);
|
||||
|
||||
child.once("error", () => {
|
||||
appendLaunchLog("detached child emitted error during grace period", {
|
||||
childPid: child.pid ?? null,
|
||||
});
|
||||
clearTimeout(timer);
|
||||
finish(true);
|
||||
});
|
||||
child.once("exit", () => {
|
||||
appendLaunchLog("detached child emitted exit during grace period", {
|
||||
childPid: child.pid ?? null,
|
||||
});
|
||||
clearTimeout(timer);
|
||||
finish(true);
|
||||
});
|
||||
});
|
||||
|
||||
appendLaunchLog("detached startup grace period completed", {
|
||||
childPid: child.pid ?? null,
|
||||
exitedEarly,
|
||||
});
|
||||
|
||||
if (exitedEarly) {
|
||||
const logs = tailFile(logFilePath(), 15);
|
||||
throw new Error(`Daemon failed to start.${logs ? `\n\nRecent logs:\n${logs}` : ""}`);
|
||||
@@ -344,6 +394,15 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
|
||||
// Poll for PID file with server ID
|
||||
for (let attempt = 0; attempt < STARTUP_POLL_MAX_ATTEMPTS; attempt++) {
|
||||
const status = resolveStatus();
|
||||
if (attempt === 0 || attempt === STARTUP_POLL_MAX_ATTEMPTS - 1 || attempt % 10 === 9) {
|
||||
appendLaunchLog("polling daemon status after detached start", {
|
||||
attempt: attempt + 1,
|
||||
status: status.status,
|
||||
pid: status.pid,
|
||||
listen: status.listen,
|
||||
serverId: status.serverId || null,
|
||||
});
|
||||
}
|
||||
if (status.status === "running" && status.serverId) return status;
|
||||
await sleep(STARTUP_POLL_INTERVAL_MS);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user