Kill the installer before its children, not after

The depth-first walk killed the child install.ps1 was waiting on and only
then the root, which races the leader's own reaction to that death. It is
not a theoretical race: in staging run 30424366953 install.ps1 had already
printed "unsloth studio setup failed (exit code -1)" by the time
Stop-Process reached it. A leader that wins the race makes Stop-Process
throw, and the new root-kill assertion would then fail a leg whose
interruption the driver really did deliver.

The tree is now snapshotted first, since a dead parent leaves nothing to
walk, then the root goes down ahead of its descendants. A dead leader cannot
react to a child and cannot respawn one either, which is what the
depth-first order was for.
This commit is contained in:
Daniel Han 2026-07-29 06:11:02 +00:00
commit 4493b14647

View file

@ -59,17 +59,38 @@ Write-Host "[interrupt] installer pid=$($proc.Id) marker='$Marker' deadline=${Ki
# false exactly when there was nothing left to interrupt.
$script:rootKilled = $false
function Get-Descendants([int]$RootId) {
# Depth-first, deepest first. CIM gives the parent link Windows does not expose via
# process groups.
$ids = @()
foreach ($k in @(Get-CimInstance Win32_Process -Filter "ParentProcessId=$RootId" -ErrorAction SilentlyContinue)) {
$kid = [int]$k.ProcessId
$ids += Get-Descendants $kid
$ids += $kid
}
return $ids
}
function Stop-Tree([int]$RootId) {
# Depth-first, so a parent cannot respawn a child we already killed. CIM gives the
# parent link Windows does not expose via process groups.
$kids = @(Get-CimInstance Win32_Process -Filter "ParentProcessId=$RootId" -ErrorAction SilentlyContinue)
foreach ($k in $kids) { Stop-Tree ([int]$k.ProcessId) }
# Snapshot the whole tree BEFORE killing anything. Once a parent is gone its children are
# orphaned and there is no ParentProcessId left to walk, so the walk has to happen first.
$descendants = @(Get-Descendants $RootId)
# Then the ROOT, ahead of its children. install.ps1 watches the child it is waiting on:
# in staging run 30424366953 it had already printed "unsloth studio setup failed (exit
# code -1)" by the time Stop-Process reached it. Killing children first therefore races
# the leader's own exit, and a leader that wins that race makes Stop-Process throw over
# an interruption the driver really did deliver, failing the leg for nothing. Dead first,
# it cannot react to anything, and it cannot respawn what we are about to kill either.
try {
Stop-Process -Id $RootId -Force -ErrorAction Stop
Write-Host "[interrupt] killed pid=$RootId"
Write-Host "[interrupt] killed installer pid=$RootId"
if ($RootId -eq $proc.Id) { $script:rootKilled = $true }
}
catch { if ($RootId -eq $proc.Id) { Write-Host "[interrupt] installer pid=$RootId was already gone: $_" } }
foreach ($id in $descendants) {
try { Stop-Process -Id $id -Force -ErrorAction Stop; Write-Host "[interrupt] killed pid=$id" }
catch { }
}
}
# A leg can be aimed at either kind of phase the installer prints, and only one of them is