fix(smartexit): use native OS methods to kill process trees; remove tree-kill dependency
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-03-04 - 2.0.3 - fix(smartexit)
|
||||||
|
use native OS methods to kill process trees; remove tree-kill dependency
|
||||||
|
|
||||||
|
- Replaced tree-kill usage with taskkill on Windows and process.kill(-pid) on POSIX.
|
||||||
|
- Removed tree-kill package from dependencies and removed its export from plugins.
|
||||||
|
- Handle ESRCH (no such process/group) as non-error when killing process groups.
|
||||||
|
- Ensure trackedPids is cleared when a child process is removed.
|
||||||
|
|
||||||
## 2026-03-03 - 1.1.1 - fix(shutdown)
|
## 2026-03-03 - 1.1.1 - fix(shutdown)
|
||||||
kill full child process trees and add synchronous exit handler to force-kill remaining child processes
|
kill full child process trees and add synchronous exit handler to force-kill remaining child processes
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/lik": "^6.2.2",
|
"@push.rocks/lik": "^6.2.2",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3"
|
||||||
"tree-kill": "^1.2.2"
|
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 1 chrome versions"
|
"last 1 chrome versions"
|
||||||
|
|||||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -11,15 +11,9 @@ importers:
|
|||||||
'@push.rocks/lik':
|
'@push.rocks/lik':
|
||||||
specifier: ^6.2.2
|
specifier: ^6.2.2
|
||||||
version: 6.2.2
|
version: 6.2.2
|
||||||
'@push.rocks/smartdelay':
|
|
||||||
specifier: ^3.0.5
|
|
||||||
version: 3.0.5
|
|
||||||
'@push.rocks/smartpromise':
|
'@push.rocks/smartpromise':
|
||||||
specifier: ^4.2.3
|
specifier: ^4.2.3
|
||||||
version: 4.2.3
|
version: 4.2.3
|
||||||
tree-kill:
|
|
||||||
specifier: ^1.2.2
|
|
||||||
version: 1.2.2
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@git.zone/tsbuild':
|
'@git.zone/tsbuild':
|
||||||
specifier: ^4.0.2
|
specifier: ^4.0.2
|
||||||
@@ -853,24 +847,28 @@ packages:
|
|||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.52':
|
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.52':
|
||||||
resolution: {integrity: sha512-ENLmSQCWqSA/+YN45V2FqTIemg7QspaiTjlm327eUAMeOLdqmSOVVyrQexJGNTQ5M8sDYCgVAig2Kk01Ggmqaw==}
|
resolution: {integrity: sha512-ENLmSQCWqSA/+YN45V2FqTIemg7QspaiTjlm327eUAMeOLdqmSOVVyrQexJGNTQ5M8sDYCgVAig2Kk01Ggmqaw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.52':
|
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.52':
|
||||||
resolution: {integrity: sha512-klahlb2EIFltSUubn/VLjuc3qxp1E7th8ukayPfdkcKvvYcQ5rJztgx8JsJSuAKVzKtNTqUGOhy4On71BuyV8g==}
|
resolution: {integrity: sha512-klahlb2EIFltSUubn/VLjuc3qxp1E7th8ukayPfdkcKvvYcQ5rJztgx8JsJSuAKVzKtNTqUGOhy4On71BuyV8g==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-musl@1.0.0-beta.52':
|
'@rolldown/binding-linux-x64-musl@1.0.0-beta.52':
|
||||||
resolution: {integrity: sha512-UuA+JqQIgqtkgGN2c/AQ5wi8M6mJHrahz/wciENPTeI6zEIbbLGoth5XN+sQe2pJDejEVofN9aOAp0kaazwnVg==}
|
resolution: {integrity: sha512-UuA+JqQIgqtkgGN2c/AQ5wi8M6mJHrahz/wciENPTeI6zEIbbLGoth5XN+sQe2pJDejEVofN9aOAp0kaazwnVg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rolldown/binding-openharmony-arm64@1.0.0-beta.52':
|
'@rolldown/binding-openharmony-arm64@1.0.0-beta.52':
|
||||||
resolution: {integrity: sha512-1BNQW8u4ro8bsN1+tgKENJiqmvc+WfuaUhXzMImOVSMw28pkBKdfZtX2qJPADV3terx+vNJtlsgSGeb3+W6Jiw==}
|
resolution: {integrity: sha512-1BNQW8u4ro8bsN1+tgKENJiqmvc+WfuaUhXzMImOVSMw28pkBKdfZtX2qJPADV3terx+vNJtlsgSGeb3+W6Jiw==}
|
||||||
@@ -918,21 +916,25 @@ packages:
|
|||||||
resolution: {integrity: sha512-211/XoBiooGGgUo/NxNpsrzGUXtH1d7g/4+UTtjYtfc8QHwu7ZMHcsqg0wss53fXzn/yyxd0DZ56vBHq52BiFw==}
|
resolution: {integrity: sha512-211/XoBiooGGgUo/NxNpsrzGUXtH1d7g/4+UTtjYtfc8QHwu7ZMHcsqg0wss53fXzn/yyxd0DZ56vBHq52BiFw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rspack/binding-linux-arm64-musl@1.6.7':
|
'@rspack/binding-linux-arm64-musl@1.6.7':
|
||||||
resolution: {integrity: sha512-0WnqAWz3WPDsXGvOOA++or7cHpoidVsH3FlqNaAfRu6ni6n7ig/s0/jKUB+C5FtXOgmGjAGkZHfFgNHsvZ0FWw==}
|
resolution: {integrity: sha512-0WnqAWz3WPDsXGvOOA++or7cHpoidVsH3FlqNaAfRu6ni6n7ig/s0/jKUB+C5FtXOgmGjAGkZHfFgNHsvZ0FWw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rspack/binding-linux-x64-gnu@1.6.7':
|
'@rspack/binding-linux-x64-gnu@1.6.7':
|
||||||
resolution: {integrity: sha512-iMrE0Q4IuYpkE0MjpaOVaUDYbQFiCRI9D3EPoXzlXJj4kJSdNheODpHTBVRlWt8Xp7UAoWuIFXCvKFKcSMm3aQ==}
|
resolution: {integrity: sha512-iMrE0Q4IuYpkE0MjpaOVaUDYbQFiCRI9D3EPoXzlXJj4kJSdNheODpHTBVRlWt8Xp7UAoWuIFXCvKFKcSMm3aQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rspack/binding-linux-x64-musl@1.6.7':
|
'@rspack/binding-linux-x64-musl@1.6.7':
|
||||||
resolution: {integrity: sha512-e7gKFxpdEQwYGk7lTC/hukTgNtaoAstBXehnZNk4k3kuU6+86WDrkn18Cd949iNqfIPtIG/wIsFNGbkHsH69hQ==}
|
resolution: {integrity: sha512-e7gKFxpdEQwYGk7lTC/hukTgNtaoAstBXehnZNk4k3kuU6+86WDrkn18Cd949iNqfIPtIG/wIsFNGbkHsH69hQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rspack/binding-wasm32-wasi@1.6.7':
|
'@rspack/binding-wasm32-wasi@1.6.7':
|
||||||
resolution: {integrity: sha512-yx88EFdE9RP3hh7VhjjW6uc6wGU0KcpOcZp8T8E/a+X8L98fX0aVrtM1IDbndhmdluIMqGbfJNap2+QqOCY9Mw==}
|
resolution: {integrity: sha512-yx88EFdE9RP3hh7VhjjW6uc6wGU0KcpOcZp8T8E/a+X8L98fX0aVrtM1IDbndhmdluIMqGbfJNap2+QqOCY9Mw==}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartexit',
|
name: '@push.rocks/smartexit',
|
||||||
version: '1.1.1',
|
version: '2.0.3',
|
||||||
description: 'A library for managing graceful shutdowns of Node.js processes by handling cleanup operations, including terminating child processes.'
|
description: 'A library for managing graceful shutdowns of Node.js processes by handling cleanup operations, including terminating child processes.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,21 +56,31 @@ export type TProcessSignal =
|
|||||||
export class SmartExit {
|
export class SmartExit {
|
||||||
/** Kill an entire process tree by PID. */
|
/** Kill an entire process tree by PID. */
|
||||||
public static async killTreeByPid(pidArg: number, signalArg: TProcessSignal = 'SIGKILL') {
|
public static async killTreeByPid(pidArg: number, signalArg: TProcessSignal = 'SIGKILL') {
|
||||||
const done = plugins.smartpromise.defer();
|
if (process.platform === 'win32') {
|
||||||
plugins.treeKill.default(pidArg, signalArg, (err) => {
|
// Windows: use native taskkill for tree kill
|
||||||
if (err) {
|
try {
|
||||||
done.reject(err);
|
plugins.childProcess.execSync(`taskkill /T /F /PID ${pidArg}`, { stdio: 'ignore' });
|
||||||
} else {
|
} catch {
|
||||||
done.resolve();
|
// Process already dead
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// POSIX: kill the entire process group via negative PID.
|
||||||
|
// Works even for grandchildren reparented to PID 1.
|
||||||
|
try {
|
||||||
|
process.kill(-pidArg, signalArg);
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.code !== 'ESRCH') {
|
||||||
|
// ESRCH = no such process/group, already dead — that's fine
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
await done.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instance state
|
// Instance state
|
||||||
public processesToEnd = new plugins.lik.ObjectMap<plugins.childProcess.ChildProcess>();
|
public processesToEnd = new plugins.lik.ObjectMap<plugins.childProcess.ChildProcess>();
|
||||||
public cleanupFunctions = new plugins.lik.ObjectMap<() => Promise<any>>();
|
public cleanupFunctions = new plugins.lik.ObjectMap<() => Promise<any>>();
|
||||||
/** PIDs tracked independently — survives removeProcess() so shutdown can still kill the tree. */
|
/** PIDs tracked independently for tree-killing during shutdown. */
|
||||||
public trackedPids = new Set<number>();
|
public trackedPids = new Set<number>();
|
||||||
private options: ISmartExitOptions;
|
private options: ISmartExitOptions;
|
||||||
|
|
||||||
@@ -108,6 +118,9 @@ export class SmartExit {
|
|||||||
/** Unregister a child process. */
|
/** Unregister a child process. */
|
||||||
public removeProcess(childProcessArg: plugins.childProcess.ChildProcess) {
|
public removeProcess(childProcessArg: plugins.childProcess.ChildProcess) {
|
||||||
this.processesToEnd.remove(childProcessArg);
|
this.processesToEnd.remove(childProcessArg);
|
||||||
|
if (childProcessArg.pid) {
|
||||||
|
this.trackedPids.delete(childProcessArg.pid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,9 +9,3 @@ import * as smartpromise from '@push.rocks/smartpromise';
|
|||||||
|
|
||||||
export { lik, smartpromise };
|
export { lik, smartpromise };
|
||||||
|
|
||||||
// third party scope
|
|
||||||
import * as treeKill from 'tree-kill';
|
|
||||||
|
|
||||||
export {
|
|
||||||
treeKill
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user