使用 Flask 开发服务器重新加载器处理多个应用程序对象的 atexit

Goo*_*ats 5 atexit resource-cleanup flask devserver python-3.x

这是另一个烧瓶开发服务器重新加载器问题。有一百万个问题询问为什么它会两次加载所有内容,而这不是其中之一。我知道它两次加载所有内容,我的问题涉及处理这个现实,我还没有找到我认为解决我正在尝试做的事情的答案。

我的问题是,如何在退出时清除所有应用程序对象?

我目前的方法如下所示。在这个例子中,我使用 atexit 函数运行我的清理代码。

from flask import Flask

app = Flask(__name__)
print("start_app_id: ", '{}'.format(id(app)))

import atexit
@atexit.register
def shutdown():
    print("AtExit_app_id: ", '{}'.format(id(app)))
    #do some cleanup on the app object here

if __name__ == "__main__":
    import os
    if os.environ.get('WERKZEUG_RUN_MAIN') == "true":
        print("reloaded_main_app_id: ", '{}'.format(id(app)))
    else:
        print("first_main_app_id: ", '{}'.format(id(app)))

    app.run(host='0.0.0.0', debug=True)
Run Code Online (Sandbox Code Playgroud)

这段代码的输出如下:

start_app_id:  140521561348864
first_main_app_id:  140521561348864
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
start_app_id:  140105598483312
reloaded_main_app_id:  140105598483312
 * Debugger is active!
 * Debugger pin code: xxx-xxx-xxx
^CAtExit_app_id:  140521561348864
Run Code Online (Sandbox Code Playgroud)

请注意,首次加载时,会创建一个 ID 为 '864 的应用程序对象。在自动重新加载期间,会创建一个 ID 为 '312 的新应用程序对象。然后,当我按下 Ctrl-C(最后一行)时,会调用 atexit 例程,并且原始的 '864 app 对象是可以使用 app 变量访问的对象——而不是较新的 '312 app 对象。

我希望能够在服务器关闭或按 Ctrl-C 键(在本例中为 '864 和 '312)时对所有浮动的应用程序对象进行清理。关于如何做到这一点的任何建议?

或者,如果我可以在重新加载后创建的较新的 '312 对象上运行清理,我也可以使其工作 - 但是我目前的方法只允许我清理原始应用程序对象。

谢谢。

UPDATE1:我找到了一个链接,建议使用 try/finally 而不是 atexit 钩子来完成我上面打算做的事情。切换到此会导致与 atexit 完全相同的行为,因此对我的问题没有帮助:

from flask import Flask

app = Flask(__name__)
print("start_app_id: ", '{}'.format(id(app)))

if __name__ == "__main__":
    import os
    if os.environ.get('WERKZEUG_RUN_MAIN') == "true":
        print("reloaded_main_app_id: ", '{}'.format(id(app)))
    else:
        print("first_main_app_id: ", '{}'.format(id(app)))

    try:
        app.run(host='0.0.0.0', debug=True)
    finally:
        print("Finally_app_id: ", '{}'.format(id(app)))
        #do app cleanup code here
Run Code Online (Sandbox Code Playgroud)

Goo*_*ats 4

经过一番挖掘 werkzeug 源代码后,我找到了答案。答案是不可能做我想做的事——这是设计使然。

当使用flask开发服务器(werkzeug)时,不可能在终止时清除所有现有的应用程序对象(例如ctrl-C),因为werkzeug服务器捕获键盘中断异常并“传递”它。您可以在 run_with_reloader 函数中 werkzeug 的 _reloader.py 的最后几行中看到这一点:

def run_with_reloader(main_func, extra_files=None, interval=1,
                      reloader_type='auto'):
    """Run the given function in an independent python interpreter."""
    import signal
    reloader = reloader_loops[reloader_type](extra_files, interval)
    signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
    try:
        if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
            t = threading.Thread(target=main_func, args=())
            t.setDaemon(True)
            t.start()
            reloader.run()
        else:
            sys.exit(reloader.restart_with_reloader())
    except KeyboardInterrupt:
        pass
Run Code Online (Sandbox Code Playgroud)

如果将上面的“ except KeyboardInterrupt:”替换为“finally:”,然后运行原始问题中的第二个代码片段,您会发现两个创建的应用程序对象都已根据需要清理。有趣的是,在进行这些更改后,第一个代码片段(使用@atexit)仍然无法按预期工作。

所以总而言之,您可以在使用 Flask 开发服务器时清理所有现有的应用程序对象,但您需要修改 werkzeug 源才能执行此操作。