Files
orca/src/main/git/command-runner/spawned-command-tree-kill.ts
T
Neil ee1a0a4e2d fix(git): avoid Windows tree kills after the command has exited (#20606)
Validated and independently reviewed OMP integration fix.
2026-09-14 13:56:25 -07:00

68 lines
2.0 KiB
TypeScript

import { spawn, type ChildProcess } from 'node:child_process'
import { admitSelfInitiatedTreeKill } from '../../own-chromium-tree-kill-guard'
const WINDOWS_TREE_KILL_WAIT_MS = 2_000
export function killSpawnedCommandTree(child: ChildProcess): Promise<void> {
const pid = child.pid
if (!pid || process.platform !== 'win32') {
child.kill()
return Promise.resolve()
}
// Windows may reuse the pid after exit while inherited pipes still delay close.
if ((child.exitCode ?? null) !== null || (child.signalCode ?? null) !== null) {
child.kill()
return Promise.resolve()
}
if (
!admitSelfInitiatedTreeKill({ pid, site: 'git-command-tree-kill', scope: 'win-taskkill-tree' })
) {
// Refusal blocks the pid-addressed tree walk, never the termination: the
// handle-addressed root kill cannot reach a recycled pid.
child.kill()
return Promise.resolve()
}
return new Promise((resolve) => {
let killer: ChildProcess
try {
// Why: Windows shims/wsl.exe own descendants; wait for /t tree cleanup so a timed-out command can't outlive its probe.
killer = spawn('taskkill', ['/pid', String(pid), '/t', '/f'], {
stdio: 'ignore',
windowsHide: true
})
if (!killer || typeof killer.unref !== 'function') {
child.kill()
resolve()
return
}
} catch {
child.kill()
resolve()
return
}
let settled = false
let timer: NodeJS.Timeout | null = null
const finish = (fallbackToChildKill: boolean): void => {
if (settled) {
return
}
settled = true
if (timer) {
clearTimeout(timer)
}
killer.removeAllListeners()
if (fallbackToChildKill) {
child.kill()
}
resolve()
}
killer.once('error', () => finish(true))
killer.once('close', (code) => finish(code !== 0))
timer = setTimeout(() => {
killer.kill()
finish(true)
}, WINDOWS_TREE_KILL_WAIT_MS)
killer.unref()
})
}