189 lines
4.0 KiB
Go
189 lines
4.0 KiB
Go
//go:build !windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
func prepareAIServerCommandForLifecycle(cmd *exec.Cmd) {
|
|
if cmd == nil {
|
|
return
|
|
}
|
|
if cmd.SysProcAttr == nil {
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
|
}
|
|
cmd.SysProcAttr.Setpgid = true
|
|
}
|
|
|
|
func terminateAIServerProcessTree(cmd *exec.Cmd) {
|
|
if cmd == nil || cmd.Process == nil || cmd.Process.Pid <= 0 {
|
|
return
|
|
}
|
|
|
|
pid := cmd.Process.Pid
|
|
_ = syscall.Kill(-pid, syscall.SIGTERM)
|
|
time.Sleep(1200 * time.Millisecond)
|
|
_ = syscall.Kill(-pid, syscall.SIGKILL)
|
|
_ = cmd.Process.Kill()
|
|
}
|
|
|
|
func stopStaleAIServerOnPort(port string) error {
|
|
if runtime.GOOS != "linux" {
|
|
return nil
|
|
}
|
|
|
|
portNumber, err := strconv.Atoi(strings.TrimSpace(port))
|
|
if err != nil || portNumber <= 0 || portNumber > 65535 {
|
|
return nil
|
|
}
|
|
|
|
pids, err := linuxPIDsListeningOnPort(portNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
currentPID := os.Getpid()
|
|
for _, pid := range pids {
|
|
if pid <= 0 || pid == currentPID {
|
|
continue
|
|
}
|
|
if !linuxPIDLooksLikeAIServer(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)
|
|
terminateStaleUnixPID(pid)
|
|
}
|
|
|
|
if len(pids) > 0 {
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func linuxPIDsListeningOnPort(port int) ([]int, error) {
|
|
inodes := map[string]struct{}{}
|
|
for _, path := range []string{"/proc/net/tcp", "/proc/net/tcp6"} {
|
|
found, err := linuxSocketInodesListeningOnPort(path, port)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
for inode := range found {
|
|
inodes[inode] = struct{}{}
|
|
}
|
|
}
|
|
if len(inodes) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
entries, err := os.ReadDir("/proc")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pidSet := map[int]struct{}{}
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
continue
|
|
}
|
|
pid, err := strconv.Atoi(entry.Name())
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
fdDir := filepath.Join("/proc", entry.Name(), "fd")
|
|
fds, err := os.ReadDir(fdDir)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, fd := range fds {
|
|
target, err := os.Readlink(filepath.Join(fdDir, fd.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if !strings.HasPrefix(target, "socket:[") || !strings.HasSuffix(target, "]") {
|
|
continue
|
|
}
|
|
inode := strings.TrimSuffix(strings.TrimPrefix(target, "socket:["), "]")
|
|
if _, ok := inodes[inode]; ok {
|
|
pidSet[pid] = struct{}{}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
pids := make([]int, 0, len(pidSet))
|
|
for pid := range pidSet {
|
|
pids = append(pids, pid)
|
|
}
|
|
sort.Ints(pids)
|
|
return pids, nil
|
|
}
|
|
|
|
func linuxSocketInodesListeningOnPort(path string, port int) (map[string]struct{}, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inodes := map[string]struct{}{}
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines[1:] {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 10 || fields[3] != "0A" {
|
|
continue
|
|
}
|
|
|
|
local := fields[1]
|
|
idx := strings.LastIndex(local, ":")
|
|
if idx < 0 || idx+1 >= len(local) {
|
|
continue
|
|
}
|
|
|
|
localPort, err := strconv.ParseInt(local[idx+1:], 16, 32)
|
|
if err != nil || int(localPort) != port {
|
|
continue
|
|
}
|
|
|
|
inode := strings.TrimSpace(fields[9])
|
|
if inode != "" && inode != "0" {
|
|
inodes[inode] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return inodes, nil
|
|
}
|
|
|
|
func terminateStaleUnixPID(pid int) {
|
|
proc, err := os.FindProcess(pid)
|
|
if err != nil || proc == nil {
|
|
return
|
|
}
|
|
|
|
_ = proc.Signal(syscall.SIGTERM)
|
|
time.Sleep(900 * time.Millisecond)
|
|
_ = proc.Kill()
|
|
}
|
|
|
|
func linuxPIDLooksLikeAIServer(pid int) bool {
|
|
data, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "cmdline"))
|
|
if err != nil || len(data) == 0 {
|
|
return false
|
|
}
|
|
|
|
cmdline := strings.ToLower(strings.ReplaceAll(string(data), "\x00", " "))
|
|
return strings.Contains(cmdline, "ai_server:app") ||
|
|
strings.Contains(cmdline, "ai_server.py") ||
|
|
(strings.Contains(cmdline, "uvicorn") && strings.Contains(cmdline, "ai_server"))
|
|
}
|