diff --git a/packaging/install_service.ps1 b/packaging/install_service.ps1 index 058a5f5..6a46763 100644 --- a/packaging/install_service.ps1 +++ b/packaging/install_service.ps1 @@ -65,11 +65,21 @@ function Ensure-WinSW { $stopPs1 = ConvertTo-WinSWPath (Join-Path $PSScriptRoot "stop_netx_app.ps1") $progFs = ConvertTo-WinSWPath $prog $dataFs = ConvertTo-WinSWPath $data - $logPath = ConvertTo-WinSWPath (Join-Path $data "data\runtime") - if (-not (Test-Path (Join-Path $data "data\runtime"))) { - New-Item -ItemType Directory -Path (Join-Path $data "data\runtime") -Force | Out-Null + # Own folder: WinSW writes .out.log / .err.log = NetX.out.log, which on Windows is + # the same file as start_netx.ps1's netx.out.log. WinSW keeps it open, so NetX could not + # truncate its log and every service start failed. + $svcLogDir = Join-Path $data "data\runtime\service" + $logPath = ConvertTo-WinSWPath $svcLogDir + if (-not (Test-Path $svcLogDir)) { + New-Item -ItemType Directory -Path $svcLogDir -Force | Out-Null } + # Stop hook via cmd: NUL stdin + output to a file. Launched by WinSW directly, + # powershell.exe never reached the script's first line (stuck on the inherited + # std handles), so the supervisor was never told to exit and stops hung. + $stopLog = Join-Path $svcLogDir "stopexec.log" + $stopCmd = "/d /s /c `"powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$stopPs1`" -ProgramRoot `"$progFs`" -DataRoot `"$dataFs`" <NUL >>`"$stopLog`" 2>&1`"" + $xml = @" $svcName @@ -86,8 +96,8 @@ function Ensure-WinSW { 1 hour 60sec - powershell.exe - -NoProfile -ExecutionPolicy Bypass -File "$stopPs1" -ProgramRoot "$progFs" -DataRoot "$dataFs" + cmd.exe + $stopCmd $progFs "@ diff --git a/packaging/service_lifecycle.ps1 b/packaging/service_lifecycle.ps1 index c3f7e4f..4431991 100644 --- a/packaging/service_lifecycle.ps1 +++ b/packaging/service_lifecycle.ps1 @@ -32,6 +32,7 @@ function Stop-NetxRunners([switch]$Remove) { $state | ConvertTo-Json | Set-Content -LiteralPath $stateFile -Encoding utf8 if ($svc.Status -ne 'Stopped') { Write-LifecycleLog 'Stopping Windows service NetX' + Write-NetxServiceStopFlag -DataRoot $data Stop-Service -Name 'NetX' -Force -ErrorAction Stop (Get-Service -Name 'NetX').WaitForStatus('Stopped', [TimeSpan]::FromSeconds(60)) } diff --git a/packaging/service_run.ps1 b/packaging/service_run.ps1 index e514352..3504485 100644 --- a/packaging/service_run.ps1 +++ b/packaging/service_run.ps1 @@ -57,7 +57,16 @@ function Start-NetxChildren { function Stop-NetxChildren { & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $stopPs1 ` - -ProgramRoot $prog -DataRoot $data + -ProgramRoot $prog -DataRoot $data -KeepSupervisor +} + +function Wait-NetxStopFlag([int]$Seconds) { + # Sleep in 1s steps so a service stop (stop flag) is noticed quickly. + for ($i = 0; $i -lt $Seconds; $i++) { + if (Test-Path $stopFlag) { return $true } + Start-Sleep -Seconds 1 + } + return (Test-Path $stopFlag) } try { @@ -83,11 +92,11 @@ try { Start-NetxChildren $failStreak = 0 # Give API time to come up before counting failures again. - Start-Sleep -Seconds ([Math]::Max(15, $HealthIntervalSec)) + $null = Wait-NetxStopFlag ([Math]::Max(15, $HealthIntervalSec)) continue } } - Start-Sleep -Seconds $HealthIntervalSec + $null = Wait-NetxStopFlag $HealthIntervalSec } } catch { Write-SvcLog "ERROR: $($_.Exception.Message)" diff --git a/packaging/stop_netx_app.ps1 b/packaging/stop_netx_app.ps1 index 48d99c1..5f091cb 100644 --- a/packaging/stop_netx_app.ps1 +++ b/packaging/stop_netx_app.ps1 @@ -2,10 +2,23 @@ param( [string]$ProgramRoot = "", [string]$DataRoot = "", [switch]$KeepPostgres = $false, - [switch]$KillTray = $false + [switch]$KillTray = $false, + # Used by service_run.ps1 when it restarts NetX itself: do not ask it to exit. + [switch]$KeepSupervisor = $false ) $ErrorActionPreference = "Continue" +# Timestamped trace (service stop path is hard to observe otherwise). +$script:stopTrace = $null +function Write-StopTrace([string]$Msg) { + if (-not $script:stopTrace) { return } + try { Add-Content -Path $script:stopTrace -Value ("{0} [{1}] {2}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff"), $PID, $Msg) -Encoding utf8 } catch {} +} +if ($DataRoot) { + $traceDir = Join-Path $DataRoot "data\runtime\service" + if (Test-Path $traceDir) { $script:stopTrace = Join-Path $traceDir "stop_netx_app.log" } +} +Write-StopTrace "begin user=$([Environment]::UserName) keepSupervisor=$KeepSupervisor" . "$PSScriptRoot\_common.ps1" Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Hidden @@ -17,14 +30,24 @@ if (Test-Path $envPath) { $map = Read-DotEnv -Path $envPath } +# Tell the service supervisor (service_run.ps1) to exit first. WinSW runs this script as +# and then waits for the supervisor to exit; without the flag it kept +# looping, restarted NetX, and the service never stopped. +if (-not $KeepSupervisor) { + Write-NetxServiceStopFlag -DataRoot $data + Write-StopTrace "stop flag written" +} + $stopScript = Join-Path $prog "scripts\stop_netx.ps1" if (-not (Test-Path $stopScript)) { $stopScript = Join-Path (Get-NetxRepoRoot) "scripts\stop_netx.ps1" } if (Test-Path $stopScript) { Write-Host "==> Stopping NetX processes" + Write-StopTrace "stop_netx.ps1 begin" Start-NetxPowerShell -File $stopScript -Arguments @("-Force") ` -WorkingDirectory $prog -WindowStyle Hidden -Wait | Out-Null + Write-StopTrace "stop_netx.ps1 end" } else { Write-Host "[WARN] stop_netx.ps1 not found" } @@ -51,4 +74,5 @@ if ($mode -eq "bundled" -and -not $KeepPostgres) { } } +Write-StopTrace "done" Write-Host "==> Stop done"