From 5a52f0da25376e339650a0c7fd84891ffd04cd8d Mon Sep 17 00:00:00 2001 From: Josh Miller Date: Tue, 27 Feb 2024 13:18:23 +0000 Subject: [PATCH 1/4] Add process termination handling --- electron/main.js | 48 +++++++++++++++++++++++++++---------------- electron/processes.js | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 electron/processes.js diff --git a/electron/main.js b/electron/main.js index caca6dacc..5a8496126 100644 --- a/electron/main.js +++ b/electron/main.js @@ -24,6 +24,7 @@ const { OperateCmd, OperateDirectory, } = require('./install'); +const { killProcessAndChildren } = require('./processes'); // Attempt to acquire the single instance lock const singleInstanceLock = app.requestSingleInstanceLock(); @@ -53,30 +54,22 @@ let tray, operateDaemonPid, nextAppProcess, nextAppProcessPid; -let beforeQuitOnceCheck = false; async function beforeQuit() { - if (beforeQuitOnceCheck) return; - beforeQuitOnceCheck = true; - - await new Promise(function (resolve, reject) { + if (operateDaemonPid) { try { - process.kill(operateDaemonPid, 'SIGKILL'); - resolve(true); - } catch (error) { - resolve(false); + await killProcessAndChildren(operateDaemonPid); + } catch (e) { + console.error(e); } - }); + } if (nextAppProcessPid) { - await new Promise(function (resolve, reject) { - try { - process.kill(nextAppProcessPid, 'SIGKILL'); - resolve(true); - } catch (error) { - resolve(false); - } - }); + try { + await killProcessAndChildren(nextAppProcessPid); + } catch (e) { + console.error(e); + } } tray && tray.destroy(); @@ -322,3 +315,22 @@ app.on('window-all-closed', () => { app.on('before-quit', () => { beforeQuit(); }); + +// PROCESS SPECIFIC EVENTS (HANDLES NON-GRACEFUL TERMINATION) + +process.on('uncaughtException', (error) => { + console.error('Uncaught Exception:', error); + // Clean up your child processes here + beforeQuit().then(() => { + process.exit(1); // Exit with a failure code + }); +}); + +['SIGINT', 'SIGTERM'].forEach((signal) => { + process.on(signal, () => { + console.log(`Received ${signal}. Cleaning up...`); + beforeQuit().then(() => { + process.exit(0); + }); + }); +}); diff --git a/electron/processes.js b/electron/processes.js new file mode 100644 index 000000000..c61ab1dd8 --- /dev/null +++ b/electron/processes.js @@ -0,0 +1,36 @@ +const psTree = require('ps-tree'); +const { exec } = require('child_process'); + +const unixKillCommand = 'kill -9'; +const windowsKillCommand = 'taskkill /F /PID'; + +function killProcessAndChildren(pid, isWindows = false) { + return new Promise((resolve, reject) => { + psTree(pid, (err, children) => { + if (err) { + reject(err); + return; + } + + // Array of PIDs to kill, starting with the children + const pidsToKill = children.map((p) => p.PID); + pidsToKill.push(pid); // Also kill the main process + + const killCommand = isWindows ? windowsKillCommand : unixKillCommand; + const joinedCommand = pidsToKill + .map((pid) => `${killCommand} ${pid}`) + .join('; '); // Separate commands with a semicolon, so they run in sequence even if one fails. Also works on Windows. + + exec(joinedCommand, (err) => { + if (err?.message?.includes('No such process')) { + return; // Ignore errors for processes that are already dead + } + reject(err); + }); + + resolve(); + }); + }); +} + +module.exports = { killProcessAndChildren }; From f42601ace23cfeb70355c366a387b2a385686a57 Mon Sep 17 00:00:00 2001 From: Josh Miller Date: Tue, 27 Feb 2024 13:24:23 +0000 Subject: [PATCH 2/4] Refactor killProcessAndChildren function to determine isWindows internally --- electron/processes.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/electron/processes.js b/electron/processes.js index c61ab1dd8..6b3d44ff5 100644 --- a/electron/processes.js +++ b/electron/processes.js @@ -4,7 +4,9 @@ const { exec } = require('child_process'); const unixKillCommand = 'kill -9'; const windowsKillCommand = 'taskkill /F /PID'; -function killProcessAndChildren(pid, isWindows = false) { +const isWindows = process.platform === 'win32'; + +function killProcessAndChildren(pid) { return new Promise((resolve, reject) => { psTree(pid, (err, children) => { if (err) { From 5521d73b46c3220a6f44a04529e996a3bf107515 Mon Sep 17 00:00:00 2001 From: Josh Miller Date: Tue, 27 Feb 2024 13:29:28 +0000 Subject: [PATCH 3/4] Fix error handling in killProcessAndChildren function --- electron/processes.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/electron/processes.js b/electron/processes.js index 6b3d44ff5..5f5c01b7f 100644 --- a/electron/processes.js +++ b/electron/processes.js @@ -24,7 +24,9 @@ function killProcessAndChildren(pid) { .join('; '); // Separate commands with a semicolon, so they run in sequence even if one fails. Also works on Windows. exec(joinedCommand, (err) => { - if (err?.message?.includes('No such process')) { + if ( + err?.message?.includes(isWindows ? 'not found' : 'No such process') + ) { return; // Ignore errors for processes that are already dead } reject(err); From 875078e7a6112c87121fe1bcd241d61a01cc6d86 Mon Sep 17 00:00:00 2001 From: Josh Miller Date: Tue, 27 Feb 2024 14:41:19 +0000 Subject: [PATCH 4/4] Update killProcessAndChildren function to killProcesses --- electron/main.js | 6 +++--- electron/processes.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/electron/main.js b/electron/main.js index 5a8496126..1a8420e4a 100644 --- a/electron/main.js +++ b/electron/main.js @@ -24,7 +24,7 @@ const { OperateCmd, OperateDirectory, } = require('./install'); -const { killProcessAndChildren } = require('./processes'); +const { killProcesses } = require('./processes'); // Attempt to acquire the single instance lock const singleInstanceLock = app.requestSingleInstanceLock(); @@ -58,7 +58,7 @@ let tray, async function beforeQuit() { if (operateDaemonPid) { try { - await killProcessAndChildren(operateDaemonPid); + await killProcesses(operateDaemonPid); } catch (e) { console.error(e); } @@ -66,7 +66,7 @@ async function beforeQuit() { if (nextAppProcessPid) { try { - await killProcessAndChildren(nextAppProcessPid); + await killProcesses(nextAppProcessPid); } catch (e) { console.error(e); } diff --git a/electron/processes.js b/electron/processes.js index 5f5c01b7f..e67dcaac5 100644 --- a/electron/processes.js +++ b/electron/processes.js @@ -6,7 +6,7 @@ const windowsKillCommand = 'taskkill /F /PID'; const isWindows = process.platform === 'win32'; -function killProcessAndChildren(pid) { +function killProcesses(pid) { return new Promise((resolve, reject) => { psTree(pid, (err, children) => { if (err) { @@ -37,4 +37,4 @@ function killProcessAndChildren(pid) { }); } -module.exports = { killProcessAndChildren }; +module.exports = { killProcesses };