authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-19 02:03:03-08:00
committergravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-19 02:03:03-08:00
log8cb2b3fcfe451932681e73e38adb25321fdcb4cb
tree29f2e87e6f19fe2ae7ac8d327813cbecb2fdd7cd
parentdaf8e5f53b80dd51913a058cf7012153e5e9cd09
signature Commit is signed but in an unrecognized format.

fix force closing


2 files changed, 63 insertions(+), 3 deletions(-)

apps/desktop/scripts/dev-electron.mjs+45-1
...@@ -1,3 +1,5 @@...@@ -1,3 +1,5 @@
1import { spawnSync } from "node:child_process";
2
1import electronmon from "electronmon";3import electronmon from "electronmon";
2import waitOn from "wait-on";4import waitOn from "wait-on";
35
...@@ -18,7 +20,7 @@ await waitOn({...@@ -18,7 +20,7 @@ await waitOn({
18const childEnv = { ...process.env };20const childEnv = { ...process.env };
19delete childEnv.ELECTRON_RUN_AS_NODE;21delete childEnv.ELECTRON_RUN_AS_NODE;
2022
21await electronmon({23const monitor = await electronmon({
22 cwd: desktopDir,24 cwd: desktopDir,
23 args: ["dist-electron/main.js"],25 args: ["dist-electron/main.js"],
24 env: {26 env: {
...@@ -27,3 +29,45 @@ await electronmon({...@@ -27,3 +29,45 @@ await electronmon({
27 },29 },
28 electronPath: resolveElectronPath(),30 electronPath: resolveElectronPath(),
29});31});
32
33let shuttingDown = false;
34
35function killChildTree(signal) {
36 if (process.platform === "win32") {
37 return;
38 }
39
40 // electronmon may leave Electron orphaned when parent shutdown races; kill direct children as fallback.
41 spawnSync("pkill", [`-${signal}`, "-P", String(process.pid)], { stdio: "ignore" });
42}
43
44async function shutdown(exitCode) {
45 if (shuttingDown) return;
46 shuttingDown = true;
47
48 try {
49 await Promise.race([
50 monitor.destroy(),
51 new Promise((resolve) => setTimeout(resolve, 1_500)),
52 ]);
53 } catch {
54 // Best effort only; fallback process kill below handles stubborn children.
55 }
56
57 killChildTree("TERM");
58 setTimeout(() => {
59 killChildTree("KILL");
60 }, 1_200).unref();
61
62 process.exit(exitCode);
63}
64
65process.once("SIGINT", () => {
66 void shutdown(130);
67});
68process.once("SIGTERM", () => {
69 void shutdown(143);
70});
71process.once("SIGHUP", () => {
72 void shutdown(129);
73});
apps/desktop/src/main.ts+18-2
...@@ -164,10 +164,10 @@ function stopBackend(): void {...@@ -164,10 +164,10 @@ function stopBackend(): void {
164 backendProcess = null;164 backendProcess = null;
165 if (!child) return;165 if (!child) return;
166166
167 if (!child.killed) {167 if (child.exitCode === null && child.signalCode === null) {
168 child.kill("SIGTERM");168 child.kill("SIGTERM");
169 setTimeout(() => {169 setTimeout(() => {
170 if (!child.killed) {170 if (child.exitCode === null && child.signalCode === null) {
171 child.kill("SIGKILL");171 child.kill("SIGKILL");
172 }172 }
173 }, 2_000).unref();173 }, 2_000).unref();
...@@ -336,3 +336,19 @@ app.on("window-all-closed", () => {...@@ -336,3 +336,19 @@ app.on("window-all-closed", () => {
336 app.quit();336 app.quit();
337 }337 }
338});338});
339
340if (process.platform !== "win32") {
341 process.on("SIGINT", () => {
342 if (isQuitting) return;
343 isQuitting = true;
344 stopBackend();
345 app.quit();
346 });
347
348 process.on("SIGTERM", () => {
349 if (isQuitting) return;
350 isQuitting = true;
351 stopBackend();
352 app.quit();
353 });
354}