Skip to content

Commit 03d6789

Browse files
committed
Replace parallel jobs with sequential + 5min timeout per package - no more hangs
1 parent e2bf357 commit 03d6789

1 file changed

Lines changed: 42 additions & 93 deletions

File tree

Windows/restore.ps1

Lines changed: 42 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -339,26 +339,6 @@ if ($Sequential) {
339339
--disable-interactivity
340340
}
341341
else {
342-
# Prefer ThreadJob (lightweight). Fall back to Start-Job if unavailable.
343-
# Suppress the noisy "module not found" error if ThreadJob isn't there --
344-
# we handle it gracefully.
345-
$useThreadJob = $false
346-
if (Get-Command Start-ThreadJob -ErrorAction SilentlyContinue) {
347-
$useThreadJob = $true
348-
}
349-
else {
350-
$prevEAP = $ErrorActionPreference
351-
$ErrorActionPreference = 'SilentlyContinue'
352-
try {
353-
Import-Module ThreadJob -ErrorAction SilentlyContinue 2>$null
354-
if (Get-Command Start-ThreadJob -ErrorAction SilentlyContinue) {
355-
$useThreadJob = $true
356-
}
357-
}
358-
catch { $useThreadJob = $false }
359-
finally { $ErrorActionPreference = $prevEAP }
360-
}
361-
362342
$logDir = Join-Path $RunLogDir 'packages'
363343
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
364344
Write-Host "Per-package logs: $logDir" -ForegroundColor DarkGray
@@ -409,94 +389,63 @@ else {
409389
Write-Host ""
410390
}
411391

412-
Write-Host "Phase B: Installing remaining packages in parallel (throttle: $Throttle)..." -ForegroundColor Cyan
392+
Write-Host "Phase B: Installing remaining packages (throttle: $Throttle)..." -ForegroundColor Cyan
413393
Write-Host ""
414394

415-
$scriptBlock = {
416-
param($pkg, $commonArgs, $logDir)
417-
$log = Join-Path $logDir ("{0}.log" -f ($pkg -replace '[^A-Za-z0-9._-]', '_'))
418-
$wgArgs = @('install', '--id', $pkg, '--exact') + $commonArgs
419-
try {
420-
$output = & winget @wgArgs 2>&1
421-
$output | Out-File -FilePath $log -Encoding UTF8
422-
[pscustomobject]@{
423-
Package = $pkg
424-
ExitCode = $LASTEXITCODE
425-
Log = $log
426-
}
427-
}
428-
catch {
429-
"EXCEPTION: $_" | Out-File -FilePath $log -Encoding UTF8
430-
[pscustomobject]@{
431-
Package = $pkg
432-
ExitCode = -1
433-
Log = $log
434-
}
435-
}
436-
}
395+
# Per-package timeout (5 minutes). Any installer that takes longer is killed.
396+
$pkgTimeoutSec = 300
437397

438-
$jobs = @()
439398
$completed = 0
440399
$total = $remainingPackages.Count
400+
$succeeded = 0
401+
$failed = [System.Collections.Generic.List[string]]::new()
441402

442403
foreach ($pkg in $remainingPackages) {
443-
# Throttle: wait until a slot opens
444-
while (@($jobs | Where-Object { $_.State -eq 'Running' }).Count -ge $Throttle) {
445-
$done = Wait-Job -Job $jobs -Any -Timeout 5
446-
if ($done) {
447-
foreach ($j in @($done)) {
448-
$r = Receive-Job -Job $j
449-
$completed++
450-
$status = if ($r.ExitCode -eq 0) { 'OK ' } else { "FAIL($($r.ExitCode))" }
451-
$color = if ($r.ExitCode -eq 0) { 'Green' } else { 'Yellow' }
452-
Write-Host (" [{0,3}/{1}] {2} {3}" -f $completed, $total, $status, $r.Package) -ForegroundColor $color
453-
Remove-Job -Job $j | Out-Null
454-
}
455-
# @(...) is critical: when the filter returns one job (or zero),
456-
# the result collapses to a scalar and `$jobs += newJob` then calls
457-
# op_Addition on PSRemotingJob -- which doesn't exist and throws.
458-
$jobs = @($jobs | Where-Object { $_.State -eq 'Running' })
459-
}
460-
}
404+
$completed++
405+
$log = Join-Path $logDir ("{0}.log" -f ($pkg -replace '[^A-Za-z0-9._-]', '_'))
406+
Write-Host (" [{0,3}/{1}] installing: {2}" -f $completed, $total, $pkg) -ForegroundColor DarkGray -NoNewline
461407

462-
Write-Host (" -> queued: $pkg") -ForegroundColor DarkGray
463-
if ($useThreadJob) {
464-
$jobs = @($jobs) + (Start-ThreadJob -ScriptBlock $scriptBlock -ArgumentList $pkg, $commonArgs, $logDir)
408+
$wgArgs = @('install', '--id', $pkg, '--exact') + $commonArgs
409+
410+
# Run winget as a process with a hard timeout so hung installers can't block forever.
411+
$psi = New-Object System.Diagnostics.ProcessStartInfo
412+
$psi.FileName = 'winget'
413+
$psi.Arguments = ($wgArgs -join ' ')
414+
$psi.UseShellExecute = $false
415+
$psi.RedirectStandardOutput = $true
416+
$psi.RedirectStandardError = $true
417+
$psi.CreateNoWindow = $true
418+
419+
$proc = [System.Diagnostics.Process]::Start($psi)
420+
$stdout = $proc.StandardOutput.ReadToEndAsync()
421+
$stderr = $proc.StandardError.ReadToEndAsync()
422+
423+
if ($proc.WaitForExit($pkgTimeoutSec * 1000)) {
424+
$code = $proc.ExitCode
425+
$output = $stdout.Result + "`n" + $stderr.Result
465426
}
466427
else {
467-
$jobs = @($jobs) + (Start-Job -ScriptBlock $scriptBlock -ArgumentList $pkg, $commonArgs, $logDir)
428+
# Timed out -- kill the process
429+
try { $proc.Kill() } catch { }
430+
$code = -1
431+
$output = "TIMEOUT after $pkgTimeoutSec seconds"
468432
}
469-
}
433+
$proc.Dispose()
470434

471-
# Drain remaining (10 min timeout per batch to prevent infinite hangs)
472-
$drainStart = Get-Date
473-
while (@($jobs).Count -gt 0) {
474-
$done = Wait-Job -Job $jobs -Any -Timeout 10
475-
if (-not $done) {
476-
# Check if we've been draining for too long (10 min total)
477-
if (((Get-Date) - $drainStart).TotalMinutes -gt 10) {
478-
Write-Warning "Some package installs appear stuck. Stopping remaining jobs..."
479-
$jobs | Stop-Job -ErrorAction SilentlyContinue
480-
$jobs | ForEach-Object {
481-
$completed++
482-
Write-Host (" [{0,3}/{1}] TIMEOUT {2}" -f $completed, $total, $_.Name) -ForegroundColor Red
483-
Remove-Job -Job $_ -Force -ErrorAction SilentlyContinue
484-
}
485-
$jobs = @()
486-
break
487-
}
488-
continue
435+
$output | Out-File -FilePath $log -Encoding UTF8
436+
437+
if ($code -eq 0) {
438+
Write-Host " OK" -ForegroundColor Green
439+
$succeeded++
489440
}
490-
foreach ($j in @($done)) {
491-
$r = Receive-Job -Job $j
492-
$completed++
493-
$status = if ($r.ExitCode -eq 0) { 'OK ' } else { "FAIL($($r.ExitCode))" }
494-
$color = if ($r.ExitCode -eq 0) { 'Green' } else { 'Yellow' }
495-
Write-Host (" [{0,3}/{1}] {2} {3}" -f $completed, $total, $status, $r.Package) -ForegroundColor $color
496-
Remove-Job -Job $j | Out-Null
441+
else {
442+
Write-Host " FAIL($code)" -ForegroundColor Yellow
443+
$failed.Add($pkg)
497444
}
498-
$jobs = @($jobs | Where-Object { $_.State -eq 'Running' })
499445
}
446+
447+
Write-Host ""
448+
Write-Host ("Phase B complete: {0} succeeded, {1} failed out of {2}" -f $succeeded, $failed.Count, $total) -ForegroundColor Cyan
500449
}
501450

502451
Write-Host ""

0 commit comments

Comments
 (0)