Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling atexit for multiple app objects with Flask dev server reloader

Tags:

This is yet another flask dev server reloader question. There are a million questions asking why it loads everything twice, and this is not one of them. I understand that it loads everything twice, my question involves dealing with this reality and I haven't found an answer that I think addresses what I'm trying to do.

My question is, how can I cleanup all app objects at exit?

My current approach is shown below. In this example I run my cleanup code using an atexit function.

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)

The output of this code is as follows:

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

Note that when first loaded, an app object with ID '864 is created. During the automatic reloading, a new app object with ID '312 is created. Then when I hit Ctrl-C (last line), the atexit routine is called and the original '864 app object is the one that is accessible using the app variable -- not the newer '312 app object.

I want to be able to do cleanup on all app objects floating around when the server closes or is Ctrl-C'd (in this case both '864 and '312). Any recs on how to do this?

Or alternately, if I could just run the cleanup on the newer '312 object created after reloading I could also make that work -- however my current approach only lets me cleanup the original app object.

Thanks.

UPDATE1: I found a link that suggested using try/finally instead of the atexit hook to accomplish what I set out to do above. Switching to this results in exactly the same behavior as atexit and therefore doesn't help with my issue:

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
like image 261
Good Eats Avatar asked May 06 '16 05:05

Good Eats


1 Answers

After some digging through the werkzeug source I found the answer. The answer is that it isn't possible to do what I wanted -- and this is by design.

When using the flask dev server (werkzeug) it isn't possible to cleanup all existing app objects upon termination (e.g. ctrl-C) because the werkzeug server catches the keyboardinterrupt exception and "passes" on it. You can see this in the last lines of werkzeug's _reloader.py in the run_with_reloader function:

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

If you replace the above "except KeyboardInterrupt:" with "finally:", and then run the second code snippet in the original question, you observe that both of the created app objects are cleaned up as desired. Interestingly, the first code snippet (that uses @atexit) still doesn't work as desired after making these changes.

So in conclusion, you can cleanup all existing app objects when using the flask dev server, but you need to modify the werkzeug source to do so.

like image 54
Good Eats Avatar answered Sep 28 '22 02:09

Good Eats