chore(windows): instrument detached daemon launch

This commit is contained in:
Mohamed Boudra
2026-03-29 14:38:18 +07:00
parent 094864d779
commit 831e3289e7
4 changed files with 247 additions and 3 deletions
+47 -1
View File
@@ -1,5 +1,5 @@
import { fileURLToPath } from "url";
import { existsSync } from "node:fs";
import { appendFileSync, existsSync } from "node:fs";
import path from "node:path";
import { loadConfig } from "../src/server/config.js";
import { acquirePidLock, PidLockError, releasePidLock } from "../src/server/pid-lock.js";
@@ -68,6 +68,28 @@ function resolvePackagedNodeEntrypointRunnerPath(currentScriptPath: string): str
return existsSync(runnerPath) ? runnerPath : null;
}
function appendLaunchLog(
launchLogPath: string,
message: string,
details?: Record<string, unknown>,
): void {
const line = [
new Date().toISOString(),
"[DaemonRunner]",
`pid=${process.pid}`,
message,
details ? JSON.stringify(details) : "",
]
.filter((part) => part.length > 0)
.join(" ");
try {
appendFileSync(launchLogPath, `${line}\n`, "utf-8");
} catch {
// Keep launch debugging best-effort so daemon startup behavior is unchanged.
}
}
async function main(): Promise<void> {
const config = parseConfig(process.argv.slice(2));
const workerEntry = config.devMode ? resolveDevWorkerEntry() : resolveWorkerEntry();
@@ -84,14 +106,34 @@ async function main(): Promise<void> {
applySherpaLoaderEnv(workerEnv);
const paseoHome = resolvePaseoHome(workerEnv);
const launchLogPath = path.join(paseoHome, "daemon-launch.log");
const daemonConfig = loadConfig(paseoHome, { env: workerEnv });
appendLaunchLog(launchLogPath, "daemon runner starting", {
argv: process.argv.slice(2),
execPath: process.execPath,
execArgv: process.execArgv,
workerEntry,
workerExecArgv,
devMode: config.devMode,
listen: daemonConfig.listen,
packagedNodeEntrypointRunner,
});
try {
await acquirePidLock(paseoHome, daemonConfig.listen, {
ownerPid: process.pid,
});
appendLaunchLog(launchLogPath, "pid lock acquired", {
ownerPid: process.pid,
listen: daemonConfig.listen,
});
} catch (error) {
if (error instanceof PidLockError) {
appendLaunchLog(launchLogPath, "pid lock acquisition failed", {
message: error.message,
existingLock: error.existingLock,
});
process.stderr.write(`${error.message}\n`);
process.exit(1);
return;
@@ -105,6 +147,9 @@ async function main(): Promise<void> {
return;
}
lockReleased = true;
appendLaunchLog(launchLogPath, "releasing pid lock", {
ownerPid: process.pid,
});
await releasePidLock(paseoHome, {
ownerPid: process.pid,
});
@@ -112,6 +157,7 @@ async function main(): Promise<void> {
runSupervisor({
name: "DaemonRunner",
launchLogPath,
startupMessage: config.devMode
? "Starting daemon worker (dev mode, crash restarts enabled)"
: "Starting daemon worker (IPC restart enabled)",
+89
View File
@@ -1,4 +1,5 @@
import { fork, spawn, type ChildProcess } from "child_process";
import { appendFileSync } from "node:fs";
type WorkerLifecycleMessage =
| {
@@ -12,6 +13,7 @@ type WorkerLifecycleMessage =
type SupervisorOptions = {
name: string;
startupMessage: string;
launchLogPath?: string;
resolveWorkerEntry: () => string;
workerArgs?: string[];
workerEnv?: NodeJS.ProcessEnv;
@@ -63,6 +65,28 @@ export function runSupervisor(options: SupervisorOptions): void {
process.stderr.write(`[${options.name}] ${message}\n`);
};
const logLaunch = (message: string, details?: Record<string, unknown>): void => {
if (!options.launchLogPath) {
return;
}
const line = [
new Date().toISOString(),
`[${options.name}]`,
`pid=${process.pid}`,
message,
details ? JSON.stringify(details) : "",
]
.filter((part) => part.length > 0)
.join(" ");
try {
appendFileSync(options.launchLogPath, `${line}\n`, "utf-8");
} catch {
// Keep launch debugging best-effort so daemon startup behavior is unchanged.
}
};
const exitSupervisor = (code: number): void => {
if (exiting) {
return;
@@ -92,11 +116,22 @@ export function runSupervisor(options: SupervisorOptions): void {
const spawnSpec = resolveWorkerSpawnSpec?.(workerEntry) ?? null;
if (spawnSpec) {
logLaunch("spawning worker via explicit spawn spec", {
workerEntry,
command: spawnSpec.command,
args: spawnSpec.args,
workerArgs,
});
child = spawn(spawnSpec.command, spawnSpec.args, {
stdio: ["inherit", "inherit", "inherit", "ipc"],
env: spawnSpec.env ?? workerEnv,
});
} else {
logLaunch("spawning worker via fork", {
workerEntry,
workerArgs,
workerExecArgv,
});
child = fork(workerEntry, workerArgs, {
stdio: "inherit",
env: workerEnv,
@@ -104,8 +139,35 @@ export function runSupervisor(options: SupervisorOptions): void {
});
}
logLaunch("worker spawn returned", {
childPid: child.pid ?? null,
connected: child.connected,
spawnfile: child.spawnfile,
spawnargs: child.spawnargs,
});
child.on("spawn", () => {
logLaunch("worker emitted spawn", {
childPid: child?.pid ?? null,
});
});
child.on("error", (error) => {
logLaunch("worker emitted error", {
childPid: child?.pid ?? null,
message: error.message,
});
});
child.on("message", (msg: unknown) => {
const lifecycleMessage = parseLifecycleMessage(msg);
logLaunch("worker emitted ipc message", {
childPid: child?.pid ?? null,
rawType:
typeof msg === "object" && msg !== null && "type" in msg
? (msg as { type?: unknown }).type
: typeof msg,
});
if (!lifecycleMessage) {
return;
}
@@ -119,6 +181,13 @@ export function runSupervisor(options: SupervisorOptions): void {
});
child.on("exit", (code, signal) => {
logLaunch("worker emitted exit", {
childPid: child?.pid ?? null,
code,
signal,
restarting,
shuttingDown,
});
const exitDescriptor = describeExit(code, signal);
if (shuttingDown) {
@@ -137,6 +206,20 @@ export function runSupervisor(options: SupervisorOptions): void {
log(`Worker exited (${exitDescriptor}). Supervisor exiting.`);
exitSupervisor(typeof code === "number" ? code : 0);
});
child.on("close", (code, signal) => {
logLaunch("worker emitted close", {
childPid: child?.pid ?? null,
code,
signal,
});
});
child.on("disconnect", () => {
logLaunch("worker emitted disconnect", {
childPid: child?.pid ?? null,
});
});
};
const requestRestart = (reason: string) => {
@@ -169,6 +252,12 @@ export function runSupervisor(options: SupervisorOptions): void {
process.on("SIGINT", () => forwardSignal("SIGINT"));
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
logLaunch("supervisor starting", {
startupMessage: options.startupMessage,
restartOnCrash,
workerArgs,
workerExecArgv,
});
process.stdout.write(`[${options.name}] ${options.startupMessage}\n`);
spawnWorker();
}