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
+77 -12
View File
@@ -25,15 +25,17 @@ let mainWindow: BrowserWindow | null = null;
let loadingWindow: BrowserWindow | null = null;
let nodecgProcess: ChildProcess | null = null;
let lastNodeCGOutput = "";
let stopNodeCGPromise: Promise<void> | null = null;
let isQuitting = false;
function createMainWindow(): BrowserWindow {
const win = new BrowserWindow({
show: false,
title: APP_TITLE,
width: 1440,
height: 900,
minWidth: 960,
minHeight: 640,
width: 1280,
height: 800,
minWidth: 800,
minHeight: 500,
backgroundColor: "#0f0f0f",
webPreferences: {
contextIsolation: true,
@@ -73,8 +75,8 @@ function createLoadingWindow(): BrowserWindow {
show: false,
frame: false,
title: APP_TITLE,
width: 420,
height: 280,
width: 300,
height: 300,
resizable: false,
movable: true,
minimizable: false,
@@ -180,6 +182,7 @@ function startNodeCG(): ChildProcess {
...(USE_SYSTEM_NODE ? {} : { ELECTRON_RUN_AS_NODE: "1" }),
},
stdio: ["ignore", "pipe", "pipe"],
detached: 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) {
return;
return Promise.resolve();
}
const processToStop = nodecgProcess;
const pid = processToStop.pid;
if (typeof pid !== "number") {
log("NodeCG pid unavailable, skipping graceful stop");
return Promise.resolve();
}
log(`Stopping NodeCG pid=${pid}`);
processToStop.kill("SIGTERM");
killNodeCGProcessTree(pid, "SIGTERM");
stopNodeCGPromise = new Promise((resolve) => {
const complete = () => {
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}`);
processToStop.kill("SIGKILL");
killNodeCGProcessTree(pid, "SIGKILL");
}
}, Math.max(0, NODECG_KILL_TIMEOUT_MS));
});
return stopNodeCGPromise;
}
function log(...args: unknown[]): void {
@@ -331,8 +387,17 @@ app.on("window-all-closed", () => {
}
});
app.on("before-quit", () => {
stopNodeCG();
app.on("before-quit", (event) => {
if (isQuitting) {
return;
}
event.preventDefault();
isQuitting = true;
stopNodeCG().finally(() => {
app.quit();
});
});
app.on("will-quit", () => {