Flask开发服务器重启时关闭Popen进程句柄的方法
Flask开发模式下esbuild进程残留问题的解决方法
1. 钩子重启流程,旧服务器停止时杀死进程
利用Werkzeug的before_server_shutdown信号,在服务器关闭时主动终止esbuild相关进程。需要全局保存Popen实例,以便在钩子中调用终止方法。
代码示例:
import os from pathlib import Path from subprocess import Popen from ctypes import CDLL from signal import SIGKILL from flask import Flask from werkzeug.serving import run_simple from werkzeug.signals import before_server_shutdown app = Flask(__name__) esbuild_process = None def dies_with_parent(): return CDLL("libc.so.6").prctl(1, SIGKILL) @before_server_shutdown.connect_via(app) def shutdown_esbuild(sender, **extra): global esbuild_process if esbuild_process and esbuild_process.poll() is None: esbuild_process.terminate() esbuild_process.wait() if __name__ == "__main__": if os.environ.get("FLASK_ENV") == "development": abs_frontend_path = (Path(__file__) / "path" / "to" / "frontend").resolve() esbuild_process = Popen(["npm", "run", "dev-js"], cwd=abs_frontend_path, preexec_fn=dies_with_parent) run_simple('0.0.0.0', 5000, app, use_reloader=True)
通过before_server_shutdown信号,在Werkzeug关闭旧服务器实例时,自动终止对应的esbuild进程。使用run_simple替代Flask默认的app.run(),可以更直接地利用Werkzeug的信号机制,确保重启时旧进程被清理。
2. 维护旧进程句柄状态,在首次请求前关闭
由于@app.before_first_request在Flask 2.0+已被弃用,改用@app.before_request配合全局状态判断,在首次处理请求时检查并终止残留的旧进程,再启动新的esbuild进程。
代码示例:
import os from pathlib import Path from subprocess import Popen from ctypes import CDLL from signal import SIGKILL from flask import Flask app = Flask(__name__) esbuild_process = None is_first_request = True def dies_with_parent(): return CDLL("libc.so.6").prctl(1, SIGKILL) @app.before_request def manage_esbuild_process(): global esbuild_process, is_first_request if not is_first_request: return is_first_request = False # 终止残留的旧进程 if esbuild_process and esbuild_process.poll() is None: esbuild_process.terminate() esbuild_process.wait() # 启动新的esbuild进程 if os.environ.get("FLASK_ENV") == "development": abs_frontend_path = (Path(__file__) / "path" / "to" / "frontend").resolve() esbuild_process = Popen(["npm", "run", "dev-js"], cwd=abs_frontend_path, preexec_fn=dies_with_parent) if __name__ == "__main__": app.run(debug=True)
通过全局变量is_first_request确保只在首次请求时执行进程清理和启动逻辑。每次服务器重启后,首次请求会触发该逻辑,终止之前残留的esbuild进程,再启动新的实例,避免进程累加。
3. 按命令模式匹配并杀死进程
如果无法维护进程句柄,可以使用psutil库遍历系统进程,通过命令行匹配找到esbuild相关进程并终止。
首先安装psutil:
pip install psutil
代码示例:
import os import psutil from pathlib import Path from subprocess import Popen from ctypes import CDLL from signal import SIGKILL from flask import Flask app = Flask(__name__) def dies_with_parent(): return CDLL("libc.so.6").prctl(1, SIGKILL) def kill_existing_esbuild_processes(): # 匹配包含"npm run dev-js"或"esbuild"的进程 for proc in psutil.process_iter(['pid', 'cmdline']): try: cmdline = ' '.join(proc.info['cmdline']) if "npm run dev-js" in cmdline or "esbuild" in cmdline: proc.terminate() proc.wait(timeout=3) except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.TimeoutExpired): pass if os.environ.get("FLASK_ENV") == "development": kill_existing_esbuild_processes() abs_frontend_path = (Path(__file__) / "path" / "to" / "frontend").resolve() Popen(["npm", "run", "dev-js"], cwd=abs_frontend_path, preexec_fn=dies_with_parent) if __name__ == "__main__": app.run(debug=True)
启动Flask前,先调用kill_existing_esbuild_processes清理所有匹配命令行的进程。这种方式无需维护进程句柄,但要注意匹配规则的准确性,避免误杀无关进程。
内容的提问来源于stack exchange,提问作者JMA
相关产品推荐
相关产品推荐

