//go:build windows package main import ( "os" "os/exec" "strconv" "strings" "syscall" "time" ) const windowsCreateNewProcessGroupForAIServer = 0x00000200 func prepareAIServerCommandForLifecycle(cmd *exec.Cmd) { if cmd == nil { return } if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } cmd.SysProcAttr.CreationFlags |= windowsCreateNewProcessGroupForAIServer } func terminateAIServerProcessTree(cmd *exec.Cmd) { if cmd == nil || cmd.Process == nil || cmd.Process.Pid <= 0 { return } terminateWindowsPIDTree(cmd.Process.Pid) _ = cmd.Process.Kill() } func stopStaleAIServerOnPort(port string) error { portNumber, err := strconv.Atoi(strings.TrimSpace(port)) if err != nil || portNumber <= 0 || portNumber > 65535 { return nil } pids, err := windowsPIDsListeningOnPort(portNumber) if err != nil { return err } currentPID := os.Getpid() for _, pid := range pids { if pid <= 0 || pid == currentPID { continue } if !windowsPIDLooksLikeAIServer(pid) { appLogf("⚠️ Listener auf AI-Port %d übersprungen, Prozess sieht nicht nach AI Server aus: pid=%d", portNumber, pid) continue } appLogf("🧹 Alter AI Server auf Port %d wird beendet: pid=%d", portNumber, pid) terminateWindowsPIDTree(pid) } if len(pids) > 0 { time.Sleep(500 * time.Millisecond) } return nil } func windowsPIDsListeningOnPort(port int) ([]int, error) { ps := "Get-NetTCPConnection -LocalPort " + strconv.Itoa(port) + " -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique" cmd := exec.Command( "powershell", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", ps, ) hideCommandWindow(cmd) out, err := cmd.Output() if err != nil { return nil, err } pidSet := map[int]struct{}{} for _, line := range strings.Split(string(out), "\n") { pid, err := strconv.Atoi(strings.TrimSpace(line)) if err != nil || pid <= 0 { continue } pidSet[pid] = struct{}{} } pids := make([]int, 0, len(pidSet)) for pid := range pidSet { pids = append(pids, pid) } return pids, nil } func terminateWindowsPIDTree(pid int) { if pid <= 0 { return } cmd := exec.Command("taskkill", "/PID", strconv.Itoa(pid), "/T", "/F") hideCommandWindow(cmd) _ = cmd.Run() } func windowsPIDLooksLikeAIServer(pid int) bool { ps := "(Get-CimInstance Win32_Process -Filter \"ProcessId = " + strconv.Itoa(pid) + "\" -ErrorAction SilentlyContinue).CommandLine" cmd := exec.Command( "powershell", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", ps, ) hideCommandWindow(cmd) out, err := cmd.Output() if err != nil { return false } cmdline := strings.ToLower(strings.TrimSpace(string(out))) return strings.Contains(cmdline, "ai_server:app") || strings.Contains(cmdline, "ai_server.py") || (strings.Contains(cmdline, "uvicorn") && strings.Contains(cmdline, "ai_server")) }