update
This commit is contained in:
72
scripts/shutdown.cjs
Normal file
72
scripts/shutdown.cjs
Normal file
@@ -0,0 +1,72 @@
|
||||
// scripts/shutdown.cjs
|
||||
//
|
||||
// audit r40 (2026-10-15):抽出 start.cjs 的子进程树清理逻辑,便于 vitest
|
||||
// 单元测试(start.cjs 自身是顶层脚本,直接 require 会副作用跑整个 dev
|
||||
// 链路,不便测)。
|
||||
//
|
||||
// 设计目标:
|
||||
// - 输入 child (Node ChildProcess-like,需要 .pid 字段) + 可选 signal
|
||||
// - Windows 走 taskkill /F /T /PID;POSIX 走 process.kill(-pid, ...)
|
||||
// - 1500ms 后 SIGKILL 兜底
|
||||
// - 任意抛错 swallow(启动期容错,本 helper 不该再抛出)
|
||||
//
|
||||
// 注意:Windows 上 Node 不支持 process.kill(-pid) 的 process group 语义,
|
||||
// POSIX 分支仅在非 win32 跑;Windows 必须走 taskkill,负 PID 在 win32 上
|
||||
// 会 ESRCH,这是已知差异。
|
||||
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
/**
|
||||
* 递归杀进程树。
|
||||
* @param {{ pid?: number }} child : 需要 .pid;可无(空对象 → no-op)
|
||||
* @param {string} [signalName] : POSIX 下首次发的信号;默认 'SIGTERM'
|
||||
* @param {object} [opts]
|
||||
* @param {string} [opts.platform] : 默认 process.platform;测时 mock
|
||||
* @param {(pid: number) => unknown} [opts.posixGroupKill] : POSIX 杀进程的 helper;默认 process.kill
|
||||
* @param {() => void} [opts.posixEscalate] : 1500ms 升级到 SIGKILL 的 schedule;默认 setTimeout
|
||||
* @returns {{ killed: boolean, method: 'taskkill'|'posix'|'noop' }}
|
||||
*/
|
||||
function shutdownTree(child, signalName, opts) {
|
||||
const pid = child && child.pid;
|
||||
// PID 0 是 kernel idle process,不可能是 user-space child;统一视为 noop
|
||||
if (typeof pid !== 'number' || !Number.isFinite(pid) || pid <= 0) {
|
||||
return { killed: false, method: 'noop' };
|
||||
}
|
||||
|
||||
const platform = (opts && opts.platform) || process.platform;
|
||||
const posixGroupKill = (opts && opts.posixGroupKill) || ((p) => process.kill(p));
|
||||
const posixEscalate = (opts && opts.posixEscalate) || ((fn) => setTimeout(fn, 1500));
|
||||
|
||||
if (platform === 'win32') {
|
||||
try {
|
||||
// windowsHide: 子进程在父 console handle 内隐藏,不弹黑色 cmd 窗口
|
||||
spawnSync('taskkill', ['/F', '/T', '/PID', String(pid)], {
|
||||
stdio: 'ignore',
|
||||
windowsHide: true,
|
||||
});
|
||||
return { killed: true, method: 'taskkill' };
|
||||
} catch {
|
||||
// taskkill 自身失败 —— child 大概率已死,fallthrough
|
||||
return { killed: false, method: 'taskkill' };
|
||||
}
|
||||
}
|
||||
|
||||
// POSIX:负 PID = process group(shell:true 时 child.pid 是 cmd 的 PID,
|
||||
// cmd 自己起的 electron + vite 子进程都在同一 group)
|
||||
try {
|
||||
posixGroupKill(-pid, signalName || 'SIGTERM');
|
||||
posixEscalate(() => {
|
||||
try {
|
||||
posixGroupKill(-pid, 'SIGKILL');
|
||||
} catch {
|
||||
// already exited
|
||||
}
|
||||
});
|
||||
return { killed: true, method: 'posix' };
|
||||
} catch {
|
||||
// ESRCH = group 已不存在
|
||||
return { killed: false, method: 'posix' };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { shutdownTree };
|
||||
Reference in New Issue
Block a user