You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 15:43:10