# -*- coding: utf-8 -*- """EXE 启动 / 清理公共工具。 背景(踩坑记录): PyInstaller `--onefile --windowed` 的启动器(runw.exe)会**派生子进程**运行真正的 Flask 服务器。若只 `p.terminate()` 父进程,子进程会残留并继续霸占 8080; Windows 允许同一端口被多个 socket 绑定,于是后续请求会被**随机路由到旧实例**, 表现为: 随机 500、改了模板/代码"不生效"、时好时坏的返回内容。 正确做法: 1) 启动前按可执行文件路径清掉 dist 下所有 EXE(兼容 *.old.exe / _prev_*.exe 等改名备份); 2) 收尾用 `taskkill /F /T /PID` 杀掉**整棵进程树**,再兜底按路径清一次。 """ import os import subprocess import time EXE = r"D:\2026-09-10-task-20\material_wms\dist\MaterialWMS.exe" def kill_dist_exes(exe_path=EXE): """杀掉 exe_path 所在目录下的所有 EXE 进程(按路径匹配任意文件名)。""" dist = os.path.dirname(exe_path) ps = ( "$d = '%s'; " "Get-CimInstance Win32_Process | " "Where-Object { $_.ExecutablePath -like ($d + '\\*.exe') } | " "ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }" ) % dist try: subprocess.run(["powershell", "-NoProfile", "-Command", ps], capture_output=True, timeout=30) except Exception as e: print(" [warn] kill_dist_exes:", e) def start_exe(exe_path=EXE): """清残留 -> 启动 EXE -> 返回 Popen(用完务必 stop_exe)。""" kill_dist_exes(exe_path) time.sleep(1) return subprocess.Popen([exe_path]) def stop_exe(p): """杀掉整棵进程树并兜底清理(幂等, 传入 None 安全)。""" if p is not None: try: subprocess.run(["taskkill", "/F", "/T", "/PID", str(p.pid)], capture_output=True, timeout=15) except Exception: pass try: p.wait(timeout=5) except Exception: try: p.kill() except Exception: pass kill_dist_exes()