mirror of
https://github.com/hansjone/netx.git
synced 2026-10-15 06:03:18 +08:00
Never wait on the process tree when starting NetX.
Windows PowerShell 5.1 Start-Process -Wait (and `& exe` with captured output) waits for every descendant / until the stdout pipe closes. The postgres and NetX processes a start leaves running made runtime_ctl.ps1 never return: tray stuck on "Starting", Start Menu "Start NetX" never opened the UI. Start-NetxPowerShell -Wait, the UAC relaunch and the Setup wait in check_update now wait for the process itself (WaitForExit). Service mode in runtime_ctl: start via sc.exe without blocking on SERVICE_RUNNING; stop writes the supervisor stop flag, polls up to 120s, then kills the WinSW tree as a last resort. Verified in an offline Windows Sandbox (no VC++ redist, no Python). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ce9a78b5ec
commit
c8806c38bd
3 changed files with 61 additions and 13 deletions
|
|
@ -313,6 +313,14 @@ function Get-NetxManagedRunner {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Write-NetxServiceStopFlag {
|
||||||
|
param([Parameter(Mandatory = $true)][string]$DataRoot)
|
||||||
|
# service_run.ps1 (the WinSW supervisor) exits when this file appears.
|
||||||
|
$runtimeDir = Join-Path $DataRoot "data\runtime"
|
||||||
|
if (-not (Test-Path $runtimeDir)) { return }
|
||||||
|
Set-Content -Path (Join-Path $runtimeDir "service.stop") -Value (Get-Date -Format o) -Encoding ascii -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
function Wait-NetxApiHealthy {
|
function Wait-NetxApiHealthy {
|
||||||
param(
|
param(
|
||||||
[string]$HostName = "127.0.0.1",
|
[string]$HostName = "127.0.0.1",
|
||||||
|
|
@ -371,10 +379,19 @@ function Start-NetxPowerShell {
|
||||||
$sp.WindowStyle = $WindowStyle
|
$sp.WindowStyle = $WindowStyle
|
||||||
}
|
}
|
||||||
if ($WorkingDirectory) { $sp.WorkingDirectory = $WorkingDirectory }
|
if ($WorkingDirectory) { $sp.WorkingDirectory = $WorkingDirectory }
|
||||||
if ($Wait) { $sp.Wait = $true }
|
|
||||||
if ($PassThru -or $Wait) { $sp.PassThru = $true }
|
if ($PassThru -or $Wait) { $sp.PassThru = $true }
|
||||||
if ($RunAs) { $sp.Verb = "RunAs" }
|
if ($RunAs) { $sp.Verb = "RunAs" }
|
||||||
return Start-Process @sp
|
$p = Start-Process @sp
|
||||||
|
if ($Wait -and $p) { Wait-NetxProcessExit -Process $p }
|
||||||
|
return $p
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-NetxProcessExit {
|
||||||
|
param([Parameter(Mandatory = $true)]$Process)
|
||||||
|
# Windows PowerShell 5.1 Start-Process -Wait also waits for every descendant; the
|
||||||
|
# postgres / NetX processes a start script leaves running would block it forever.
|
||||||
|
$null = $Process.Handle # keep a handle so ExitCode survives the exit
|
||||||
|
$Process.WaitForExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
function Test-NetxIsAdmin {
|
function Test-NetxIsAdmin {
|
||||||
|
|
@ -449,7 +466,8 @@ function Assert-NetxAdminOrRelaunch {
|
||||||
$p = Start-Process -FilePath "powershell.exe" `
|
$p = Start-Process -FilePath "powershell.exe" `
|
||||||
-ArgumentList (ConvertTo-NetxProcessArgumentString -Arguments $parts) `
|
-ArgumentList (ConvertTo-NetxProcessArgumentString -Arguments $parts) `
|
||||||
-WorkingDirectory $wd `
|
-WorkingDirectory $wd `
|
||||||
-Verb RunAs -Wait -PassThru
|
-Verb RunAs -PassThru
|
||||||
|
if ($p) { Wait-NetxProcessExit -Process $p }
|
||||||
} catch {
|
} catch {
|
||||||
if ($null -ne $prevRel) { $env:NETX_ELEVATION_RELAUNCH = $prevRel } else { Remove-Item Env:NETX_ELEVATION_RELAUNCH -ErrorAction SilentlyContinue }
|
if ($null -ne $prevRel) { $env:NETX_ELEVATION_RELAUNCH = $prevRel } else { Remove-Item Env:NETX_ELEVATION_RELAUNCH -ErrorAction SilentlyContinue }
|
||||||
throw ("admin_required: UAC cancelled or elevation failed. Run NetX as Administrator. {0}" -f $_.Exception.Message)
|
throw ("admin_required: UAC cancelled or elevation failed. Run NetX as Administrator. {0}" -f $_.Exception.Message)
|
||||||
|
|
|
||||||
|
|
@ -429,7 +429,8 @@ try {
|
||||||
Write-Host "==> Program directory is not writable; relaunching update as Administrator (UAC)..." -ForegroundColor Yellow
|
Write-Host "==> Program directory is not writable; relaunching update as Administrator (UAC)..." -ForegroundColor Yellow
|
||||||
$arg = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -ProgramRoot `"$prog`" -DataRoot `"$data`" -Apply"
|
$arg = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -ProgramRoot `"$prog`" -DataRoot `"$data`" -Apply"
|
||||||
$ep = Start-Process -FilePath "powershell.exe" -ArgumentList $arg -WorkingDirectory $prog `
|
$ep = Start-Process -FilePath "powershell.exe" -ArgumentList $arg -WorkingDirectory $prog `
|
||||||
-Verb RunAs -Wait -PassThru
|
-Verb RunAs -PassThru
|
||||||
|
Wait-NetxProcessExit -Process $ep
|
||||||
if ($null -eq $ep.ExitCode) { exit 1 }
|
if ($null -eq $ep.ExitCode) { exit 1 }
|
||||||
exit $ep.ExitCode
|
exit $ep.ExitCode
|
||||||
}
|
}
|
||||||
|
|
@ -444,7 +445,8 @@ try {
|
||||||
$trayWasRunning = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
|
$trayWasRunning = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
|
||||||
$_.CommandLine -and $_.CommandLine -match 'netx_tray\.ps1'
|
$_.CommandLine -and $_.CommandLine -match 'netx_tray\.ps1'
|
||||||
}).Count -gt 0
|
}).Count -gt 0
|
||||||
$sp = Start-Process -FilePath $pkgPath -ArgumentList $setupArgs -Wait -PassThru
|
$sp = Start-Process -FilePath $pkgPath -ArgumentList $setupArgs -PassThru
|
||||||
|
Wait-NetxProcessExit -Process $sp
|
||||||
if ($null -eq $sp.ExitCode -or $sp.ExitCode -ne 0) {
|
if ($null -eq $sp.ExitCode -or $sp.ExitCode -ne 0) {
|
||||||
$code = if ($null -eq $sp.ExitCode) { "null" } else { $sp.ExitCode }
|
$code = if ($null -eq $sp.ExitCode) { "null" } else { $sp.ExitCode }
|
||||||
throw "setup_update_failed: exit $code"
|
throw "setup_update_failed: exit $code"
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,47 @@ $prog = Get-NetxProgramRoot -Override $ProgramRoot
|
||||||
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot
|
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot
|
||||||
|
|
||||||
function Invoke-NetxChildScript([string]$Name, [string[]]$Extra = @()) {
|
function Invoke-NetxChildScript([string]$Name, [string[]]$Extra = @()) {
|
||||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot $Name) `
|
# Never read the child's stdout: postgres / NetX processes it starts inherit the
|
||||||
-ProgramRoot $prog -DataRoot $data @Extra
|
# pipe, so `& powershell.exe` with captured output blocks until they exit (forever).
|
||||||
return $LASTEXITCODE
|
# Start-Process -Wait is no good either (it waits for the whole process tree).
|
||||||
|
$p = Start-NetxPowerShell -File (Join-Path $PSScriptRoot $Name) `
|
||||||
|
-Arguments (@("-ProgramRoot", $prog, "-DataRoot", $data) + $Extra) `
|
||||||
|
-WorkingDirectory $prog -WindowStyle Hidden -PassThru
|
||||||
|
$null = $p.Handle # keep a handle so ExitCode is available after exit
|
||||||
|
$p.WaitForExit()
|
||||||
|
return $p.ExitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
function Stop-NetxService {
|
||||||
|
# Stop-Service waits forever on a service that will not stop; poll with a deadline
|
||||||
|
# and kill the WinSW process tree as a last resort.
|
||||||
|
$svc = Get-Service -Name "NetX" -ErrorAction SilentlyContinue
|
||||||
|
if (-not $svc -or $svc.Status -eq "Stopped") { return }
|
||||||
|
# Ask service_run.ps1 to exit ourselves rather than relying only on WinSW's stop hook.
|
||||||
|
Write-NetxServiceStopFlag -DataRoot $data
|
||||||
|
& sc.exe stop NetX | Out-Null
|
||||||
|
$deadline = (Get-Date).AddSeconds(120)
|
||||||
|
while ((Get-Date) -lt $deadline) {
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
$svc.Refresh()
|
||||||
|
if ($svc.Status -eq "Stopped") { return }
|
||||||
|
}
|
||||||
|
$wmi = Get-CimInstance Win32_Service -Filter "Name='NetX'" -ErrorAction SilentlyContinue
|
||||||
|
if ($wmi -and $wmi.ProcessId) { & taskkill.exe /PID $wmi.ProcessId /T /F | Out-Null }
|
||||||
|
$null = Invoke-NetxChildScript "stop_netx_app.ps1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Start-NetxService {
|
||||||
|
# Don't block on SERVICE_RUNNING: callers wait for /health instead.
|
||||||
|
$svc = Get-Service -Name "NetX" -ErrorAction SilentlyContinue
|
||||||
|
if ($svc -and $svc.Status -eq "Stopped") { & sc.exe start NetX | Out-Null }
|
||||||
}
|
}
|
||||||
|
|
||||||
$runner = Get-NetxManagedRunner
|
$runner = Get-NetxManagedRunner
|
||||||
switch ($runner) {
|
switch ($runner) {
|
||||||
"service" {
|
"service" {
|
||||||
switch ($Action) {
|
if ($Action -ne "start") { Stop-NetxService }
|
||||||
"start" { Start-Service -Name "NetX" }
|
if ($Action -ne "stop") { Start-NetxService }
|
||||||
"stop" { Stop-Service -Name "NetX" -Force }
|
|
||||||
"restart" { Restart-Service -Name "NetX" -Force }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"task" {
|
"task" {
|
||||||
if ($Action -ne "start") {
|
if ($Action -ne "start") {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue