fix: Erkennung von bereits laufendem Server und dessen beendung. incl. Tests
This commit is contained in:
1 parent
1519b96c7b
commit
971459c9bf
8 files changed
+420
-169
No files matched your search
@@ -0,0 +1,103 @@
|
||||
"""Stop an existing instance of this application's Windows server."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
_CONFIRM_SCRIPT = r'''
|
||||
function Confirm-ServerRestart {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
$answer = [System.Windows.Forms.MessageBox]::Show(
|
||||
"Es läuft bereits eine Version des Tätigkeitsnachweis-Generators.`n`nSoll das alte Programm beendet und die neue Version gestartet werden?`nDie aktuellen Auswahlen gehen dabei verloren.",
|
||||
'Tätigkeitsnachweis-Generator',
|
||||
[System.Windows.Forms.MessageBoxButtons]::YesNo,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Question,
|
||||
[System.Windows.Forms.MessageBoxDefaultButton]::Button2
|
||||
)
|
||||
return $answer -eq [System.Windows.Forms.DialogResult]::Yes
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def stop_existing_server(port: int = 8501) -> bool:
|
||||
"""Ask before stopping this app. Return False if the user declines."""
|
||||
if os.name != "nt":
|
||||
raise RuntimeError("Der kontrollierte Server-Neustart unterstützt nur Windows.")
|
||||
|
||||
app_path = str(Path(__file__).resolve().with_name("app.py"))
|
||||
# Pass paths through the environment, never interpolate them into shell code.
|
||||
env = dict(os.environ, APP_RESTART_PATH=app_path, APP_RESTART_PID=str(os.getpid()),
|
||||
APP_RESTART_PORT=str(port),
|
||||
APP_RESTART_VENV=str(Path(app_path).parent / ".venv" / "Scripts"))
|
||||
script = _CONFIRM_SCRIPT + r'''
|
||||
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
|
||||
$ErrorActionPreference = 'Stop'
|
||||
try {
|
||||
$port = [int]$env:APP_RESTART_PORT
|
||||
$listeners = @(Get-NetTCPConnection -State Listen -ErrorAction Stop |
|
||||
Where-Object { $_.LocalPort -eq $port })
|
||||
$processIds = @($listeners | Select-Object -ExpandProperty OwningProcess -Unique)
|
||||
$targets = @()
|
||||
foreach ($processId in $processIds) {
|
||||
if ($processId -eq [int]$env:APP_RESTART_PID) {
|
||||
throw "Der aktuelle Prozess belegt bereits Port $port."
|
||||
}
|
||||
$process = Get-CimInstance Win32_Process -Filter "ProcessId = $processId"
|
||||
if (-not $process) { continue }
|
||||
$isExe = $process.Name -eq 'Tätigkeitsnachweis-Generator.exe'
|
||||
$command = $process.CommandLine
|
||||
$appArgument = '(?i)\bstreamlit(?:\.exe)?"?\s+run\s+"?' +
|
||||
[regex]::Escape($env:APP_RESTART_PATH) + '"?(?:\s|$)'
|
||||
$isDev = $process.Name -match '^(python(?:w)?(?:\d+(?:\.\d+)*)?(?:w)?|streamlit)\.exe$' -and
|
||||
$command -match '(?i)\bstreamlit(?:\.exe)?"?\s+run\s+' -and
|
||||
($command -match $appArgument -or
|
||||
($command -match '(?i)\bstreamlit(?:\.exe)?"?\s+run\s+"?app\.py"?(?:\s|$)' -and
|
||||
[IO.Path]::GetDirectoryName($process.ExecutablePath) -eq $env:APP_RESTART_VENV))
|
||||
if (-not ($isExe -or $isDev)) {
|
||||
throw "Port $port wird von einem anderen Programm verwendet (PID $processId, $($process.Name)). Bitte manuell prüfen."
|
||||
}
|
||||
$targets += $processId
|
||||
}
|
||||
if ($targets.Count -gt 0 -and -not (Confirm-ServerRestart)) {
|
||||
exit 2
|
||||
}
|
||||
foreach ($processId in $targets) {
|
||||
Stop-Process -Id $processId -Force -ErrorAction Stop
|
||||
}
|
||||
$deadline = (Get-Date).AddSeconds(10)
|
||||
do {
|
||||
$remaining = @(Get-NetTCPConnection -State Listen -ErrorAction Stop |
|
||||
Where-Object { $_.LocalPort -eq $port })
|
||||
if ($remaining.Count -eq 0) { break }
|
||||
Start-Sleep -Milliseconds 200
|
||||
} while ((Get-Date) -lt $deadline)
|
||||
if ($remaining.Count -gt 0) { throw "Port $port wurde nicht rechtzeitig freigegeben." }
|
||||
ConvertTo-Json -Compress -InputObject @($targets)
|
||||
} catch {
|
||||
[Console]::Error.WriteLine($_.Exception.Message)
|
||||
exit 1
|
||||
}
|
||||
'''
|
||||
result = subprocess.run(
|
||||
["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", script],
|
||||
env=env, capture_output=True, text=True, encoding="utf-8", errors="replace",
|
||||
)
|
||||
if result.returncode == 2:
|
||||
return False
|
||||
if result.returncode:
|
||||
raise RuntimeError((result.stderr or "").strip() or "Der bestehende Server konnte nicht beendet werden.")
|
||||
stopped = json.loads(result.stdout.strip() or "[]")
|
||||
if stopped:
|
||||
print(f"[INFO] Vorherige Instanz beendet (PID: {stopped}).", flush=True)
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
if not stop_existing_server():
|
||||
print("[INFO] Die bisherige Version bleibt geöffnet. Keine neue Instanz gestartet.")
|
||||
raise SystemExit(2)
|
||||
except (RuntimeError, OSError, subprocess.SubprocessError) as exc:
|
||||
print(f"[FEHLER] {exc}")
|
||||
raise SystemExit(1)
|
||||
Reference in new issue
Block a user