fix(smartexit): use native OS methods to kill process trees; remove tree-kill dependency

This commit is contained in:
2026-03-04 17:55:47 +00:00
parent 76225c6b9f
commit 28a09f63b2
6 changed files with 40 additions and 24 deletions

View File

@@ -1,5 +1,13 @@
# 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)
kill full child process trees and add synchronous exit handler to force-kill remaining child processes

View File

@@ -20,8 +20,7 @@
},
"dependencies": {
"@push.rocks/lik": "^6.2.2",
"@push.rocks/smartpromise": "^4.2.3",
"tree-kill": "^1.2.2"
"@push.rocks/smartpromise": "^4.2.3"
},
"browserslist": [
"last 1 chrome versions"

14
pnpm-lock.yaml generated
View File

@@ -11,15 +11,9 @@ importers:
'@push.rocks/lik':
specifier: ^6.2.2
version: 6.2.2
'@push.rocks/smartdelay':
specifier: ^3.0.5
version: 3.0.5
'@push.rocks/smartpromise':
specifier: ^4.2.3
version: 4.2.3
tree-kill:
specifier: ^1.2.2
version: 1.2.2
devDependencies:
'@git.zone/tsbuild':
specifier: ^4.0.2
@@ -853,24 +847,28 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.52':
resolution: {integrity: sha512-ENLmSQCWqSA/+YN45V2FqTIemg7QspaiTjlm327eUAMeOLdqmSOVVyrQexJGNTQ5M8sDYCgVAig2Kk01Ggmqaw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.52':
resolution: {integrity: sha512-klahlb2EIFltSUubn/VLjuc3qxp1E7th8ukayPfdkcKvvYcQ5rJztgx8JsJSuAKVzKtNTqUGOhy4On71BuyV8g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.0.0-beta.52':
resolution: {integrity: sha512-UuA+JqQIgqtkgGN2c/AQ5wi8M6mJHrahz/wciENPTeI6zEIbbLGoth5XN+sQe2pJDejEVofN9aOAp0kaazwnVg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.0.0-beta.52':
resolution: {integrity: sha512-1BNQW8u4ro8bsN1+tgKENJiqmvc+WfuaUhXzMImOVSMw28pkBKdfZtX2qJPADV3terx+vNJtlsgSGeb3+W6Jiw==}
@@ -918,21 +916,25 @@ packages:
resolution: {integrity: sha512-211/XoBiooGGgUo/NxNpsrzGUXtH1d7g/4+UTtjYtfc8QHwu7ZMHcsqg0wss53fXzn/yyxd0DZ56vBHq52BiFw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rspack/binding-linux-arm64-musl@1.6.7':
resolution: {integrity: sha512-0WnqAWz3WPDsXGvOOA++or7cHpoidVsH3FlqNaAfRu6ni6n7ig/s0/jKUB+C5FtXOgmGjAGkZHfFgNHsvZ0FWw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rspack/binding-linux-x64-gnu@1.6.7':
resolution: {integrity: sha512-iMrE0Q4IuYpkE0MjpaOVaUDYbQFiCRI9D3EPoXzlXJj4kJSdNheODpHTBVRlWt8Xp7UAoWuIFXCvKFKcSMm3aQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rspack/binding-linux-x64-musl@1.6.7':
resolution: {integrity: sha512-e7gKFxpdEQwYGk7lTC/hukTgNtaoAstBXehnZNk4k3kuU6+86WDrkn18Cd949iNqfIPtIG/wIsFNGbkHsH69hQ==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rspack/binding-wasm32-wasi@1.6.7':
resolution: {integrity: sha512-yx88EFdE9RP3hh7VhjjW6uc6wGU0KcpOcZp8T8E/a+X8L98fX0aVrtM1IDbndhmdluIMqGbfJNap2+QqOCY9Mw==}

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
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.'
}

View File

@@ -56,21 +56,31 @@ export type TProcessSignal =
export class SmartExit {
/** Kill an entire process tree by PID. */
public static async killTreeByPid(pidArg: number, signalArg: TProcessSignal = 'SIGKILL') {
const done = plugins.smartpromise.defer();
plugins.treeKill.default(pidArg, signalArg, (err) => {
if (err) {
done.reject(err);
} else {
done.resolve();
if (process.platform === 'win32') {
// Windows: use native taskkill for tree kill
try {
plugins.childProcess.execSync(`taskkill /T /F /PID ${pidArg}`, { stdio: 'ignore' });
} catch {
// 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
public processesToEnd = new plugins.lik.ObjectMap<plugins.childProcess.ChildProcess>();
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>();
private options: ISmartExitOptions;
@@ -108,6 +118,9 @@ export class SmartExit {
/** Unregister a child process. */
public removeProcess(childProcessArg: plugins.childProcess.ChildProcess) {
this.processesToEnd.remove(childProcessArg);
if (childProcessArg.pid) {
this.trackedPids.delete(childProcessArg.pid);
}
}
/**

View File

@@ -9,9 +9,3 @@ import * as smartpromise from '@push.rocks/smartpromise';
export { lik, smartpromise };
// third party scope
import * as treeKill from 'tree-kill';
export {
treeKill
}