Fix Electron shutdown by terminating NodeCG process tree (#13)

* Fix Electron shutdown by terminating NodeCG process tree

* fix: adjust window dimensions for main and loading screens
This commit is contained in:
Pandipipas
2026-02-10 22:20:33 +01:00
committed by GitHub
parent da3ca23c68
commit db81138af1
+83 -18
View File
@@ -25,15 +25,17 @@ let mainWindow: BrowserWindow | null = null;
let loadingWindow: BrowserWindow | null = null; let loadingWindow: BrowserWindow | null = null;
let nodecgProcess: ChildProcess | null = null; let nodecgProcess: ChildProcess | null = null;
let lastNodeCGOutput = ""; let lastNodeCGOutput = "";
let stopNodeCGPromise: Promise<void> | null = null;
let isQuitting = false;
function createMainWindow(): BrowserWindow { function createMainWindow(): BrowserWindow {
const win = new BrowserWindow({ const win = new BrowserWindow({
show: false, show: false,
title: APP_TITLE, title: APP_TITLE,
width: 1440, width: 1280,
height: 900, height: 800,
minWidth: 960, minWidth: 800,
minHeight: 640, minHeight: 500,
backgroundColor: "#0f0f0f", backgroundColor: "#0f0f0f",
webPreferences: { webPreferences: {
contextIsolation: true, contextIsolation: true,
@@ -73,8 +75,8 @@ function createLoadingWindow(): BrowserWindow {
show: false, show: false,
frame: false, frame: false,
title: APP_TITLE, title: APP_TITLE,
width: 420, width: 300,
height: 280, height: 300,
resizable: false, resizable: false,
movable: true, movable: true,
minimizable: false, minimizable: false,
@@ -180,6 +182,7 @@ function startNodeCG(): ChildProcess {
...(USE_SYSTEM_NODE ? {} : { ELECTRON_RUN_AS_NODE: "1" }), ...(USE_SYSTEM_NODE ? {} : { ELECTRON_RUN_AS_NODE: "1" }),
}, },
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],
detached: process.platform !== "win32",
shell: process.platform === "win32", shell: process.platform === "win32",
}); });
@@ -277,23 +280,76 @@ async function launch(): Promise<void> {
} }
} }
function stopNodeCG(): void { function killNodeCGProcessTree(pid: number, signal: NodeJS.Signals): boolean {
if (process.platform === "win32") {
const force = signal === "SIGKILL" ? "/F" : "";
const killer = spawn("taskkill", ["/pid", String(pid), "/T", ...(force ? [force] : [])], {
stdio: "ignore",
shell: true,
});
killer.on("error", (error) => {
log(`taskkill error for pid=${pid}`, error);
});
return true;
}
try {
process.kill(-pid, signal);
return true;
} catch {
try {
process.kill(pid, signal);
return true;
} catch {
return false;
}
}
}
function stopNodeCG(): Promise<void> {
if (stopNodeCGPromise) {
return stopNodeCGPromise;
}
if (!nodecgProcess || nodecgProcess.killed) { if (!nodecgProcess || nodecgProcess.killed) {
return; return Promise.resolve();
} }
const processToStop = nodecgProcess; const processToStop = nodecgProcess;
const pid = processToStop.pid; const pid = processToStop.pid;
log(`Stopping NodeCG pid=${pid}`); if (typeof pid !== "number") {
processToStop.kill("SIGTERM"); log("NodeCG pid unavailable, skipping graceful stop");
return Promise.resolve();
}
setTimeout(() => { log(`Stopping NodeCG pid=${pid}`);
if (processToStop.exitCode === null && processToStop.signalCode === null) { killNodeCGProcessTree(pid, "SIGTERM");
log(`NodeCG did not exit after SIGTERM, forcing SIGKILL pid=${pid}`);
processToStop.kill("SIGKILL"); stopNodeCGPromise = new Promise((resolve) => {
} const complete = () => {
}, Math.max(0, NODECG_KILL_TIMEOUT_MS)); if (nodecgProcess === processToStop) {
nodecgProcess = null;
}
stopNodeCGPromise = null;
resolve();
};
processToStop.once("exit", () => {
complete();
});
setTimeout(() => {
if (processToStop.exitCode === null && processToStop.signalCode === null) {
log(`NodeCG did not exit after SIGTERM, forcing SIGKILL pid=${pid}`);
killNodeCGProcessTree(pid, "SIGKILL");
}
}, Math.max(0, NODECG_KILL_TIMEOUT_MS));
});
return stopNodeCGPromise;
} }
function log(...args: unknown[]): void { function log(...args: unknown[]): void {
@@ -331,8 +387,17 @@ app.on("window-all-closed", () => {
} }
}); });
app.on("before-quit", () => { app.on("before-quit", (event) => {
stopNodeCG(); if (isQuitting) {
return;
}
event.preventDefault();
isQuitting = true;
stopNodeCG().finally(() => {
app.quit();
});
}); });
app.on("will-quit", () => { app.on("will-quit", () => {