Skip to content
Closed
Prev Previous commit
ci(studio): use native process exit contract for Windows GUI lifecycle
  • Loading branch information
masarray committed Sep 15, 2026
commit dbb564e396ed6e6b064b9b79ad21de110d1f342d
63 changes: 22 additions & 41 deletions .github/workflows/arstack-studio-windows-lifecycle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,59 +64,40 @@ jobs:
Write-Host "Lifecycle executable: $exe"

function Invoke-LifecycleCycle([int]$Cycle) {
$stdout = Join-Path $env:RUNNER_TEMP "arstack-lifecycle-$Cycle.out.log"
$stderr = Join-Path $env:RUNNER_TEMP "arstack-lifecycle-$Cycle.err.log"
Remove-Item $stdout,$stderr -Force -ErrorAction SilentlyContinue

$process = Start-Process -FilePath $exe `
-ArgumentList '--check-app-lifecycle' `
-PassThru `
-RedirectStandardOutput $stdout `
-RedirectStandardError $stderr
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $exe
$startInfo.Arguments = '--check-app-lifecycle'
$startInfo.UseShellExecute = $false
$startInfo.CreateNoWindow = $true

$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
if (-not $process.Start()) {
$process.Dispose()
throw "Cycle ${Cycle}: failed to start Studio process."
}

$pidValue = $process.Id
if (-not $process.WaitForExit(12000)) {
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
Get-Content $stdout -ErrorAction SilentlyContinue
Get-Content $stderr -ErrorAction SilentlyContinue
Stop-Process -Id $pidValue -Force -ErrorAction SilentlyContinue
$process.Dispose()
throw "Cycle ${Cycle}: Studio process did not exit within 12 seconds after primary-window close."
}

$process.WaitForExit()
Get-Content $stdout -ErrorAction SilentlyContinue
Get-Content $stderr -ErrorAction SilentlyContinue
$exitCode = $process.ExitCode
$process.Dispose()

if ($process.ExitCode -ne 0) {
throw "Cycle ${Cycle}: Studio exited with code $($process.ExitCode)."
if ($exitCode -ne 0) {
throw "Cycle ${Cycle}: Studio lifecycle coordinator exited with code $exitCode."
}
if (Get-Process -Id $process.Id -ErrorAction SilentlyContinue) {
throw "Cycle ${Cycle}: Studio PID $($process.Id) remained alive after WaitForExit."
if (Get-Process -Id $pidValue -ErrorAction SilentlyContinue) {
throw "Cycle ${Cycle}: Studio PID $pidValue remained alive after native WaitForExit."
}

$combined = ((Get-Content $stdout -Raw -ErrorAction SilentlyContinue) + "`n" +
(Get-Content $stderr -Raw -ErrorAction SilentlyContinue))
if ($combined -notmatch 'Application lifecycle regression: PASS') {
throw "Cycle ${Cycle}: lifecycle coordinator did not report clean worker retirement."
}
if ($combined -match 'emergency terminate fallback|emergency worker-retirement fallback|could not be retired') {
throw "Cycle ${Cycle}: emergency shutdown fallback was reached."
}
Write-Host "Cycle $Cycle PASS: close -> worker retirement -> PID exit."
Write-Host "Cycle $Cycle PASS: close -> clean worker retirement -> PID exit."
}

Invoke-LifecycleCycle 1
Start-Sleep -Milliseconds 250
Invoke-LifecycleCycle 2
Write-Host 'Windows lifecycle gate: PASS · close -> process exit -> lock release -> immediate reopen -> clean exit'

- name: Retain lifecycle logs
if: always()
uses: actions/upload-artifact@v4
with:
name: arstack-studio-windows-lifecycle-logs
path: |
${{ runner.temp }}/arstack-lifecycle-1.out.log
${{ runner.temp }}/arstack-lifecycle-1.err.log
${{ runner.temp }}/arstack-lifecycle-2.out.log
${{ runner.temp }}/arstack-lifecycle-2.err.log
if-no-files-found: ignore
retention-days: 7
Loading