简体   繁体   中英

How to run a background timer in Flask

I'm trying to write a Flask app that behaves correctly when running in Kubernetes , especially when it comes to graceful shutdown .

As such, I need to have the code:

  • recieve the shutdown signal
  • start the "shutdown timer"
  • continue serving requests as normal until the time is "up"
  • then shut itself down

So far, what I've got is this:

from flask import Flask, abort
import signal
import time
app = Flask(__name__)
shuttingDown = False

def exit_gracefully(self, signum):
    app.logger.error('Received shutdown signal. Exiting gracefully')
    global shuttingDown
    shuttingDown = True
    # TODO: wait for some time here to ensure we are not receiving any more
    # traffic
    time.sleep(20)
    exit(0)

signal.signal(signal.SIGTERM, exit_gracefully)

@app.route("/")
def hello():
        return "Hello World!"

@app.route("/_status/liveness")
def liveness():
        return "I am alive"

@app.route("/_status/readiness")
def readiness():
        if not shuttingDown:
            return "I am ready"
        else:
            abort(500, 'not ready anymore')

The above "works" except after I send the "shutdown" signal, regular requests to "/" are not responded to, even though we are still within the shutdown "grace period" (20 seconds in the above code).

It seems that the call to "time.sleep()" is synchronous.

Does anyone know of any way to make this "asynchronous"? So that the app continues to service requests until the time is "up"?

At a guess you are using the builtin Flask development server. Such builtin development servers in Flask and Django, or anything based on the web server or WSGI server in the standard library are not intended for production systems and don't normally handle shutdown on signals properly.

As such, you should really use a proper production grade WSGI server such as Apache/mod_wsgi (mod_wsgi-express), gunicorn or uWSGI. These all handle signals properly and you don't have the issue with the development servers where they ignore the signal causing the container shutdown to be delayed, with it eventually being killed by Kubernetes when the shutdown timeout occurs.

This works with the internal server. The caveat is there is a /_shutdown URL that shuts the server down, and this is open to malicious shutdowns. If this is not what you want, then remove requests.post() and uncomment os._exit() . And of course remove @app.route("/_shutdown") and the function as well.

from flask import Flask, abort, request
import signal
import threading
import time
import os
import requests
app = Flask(__name__)
shuttingDown = False


def exit_call():
    time.sleep(20)
    requests.post("http://localhost:5420/_shutdown")
    # os._exit(0)


def exit_gracefully(self, signum):
    app.logger.error('Received shutdown signal. Exiting gracefully')
    global shuttingDown
    shuttingDown = True
    # TODO: wait for some time here to ensure we are not receiving any more
    # traffic
    _et = threading.Thread(target=exit_call)
    _et.daemon = True
    _et.start()


signal.signal(signal.SIGTERM, exit_gracefully)


@app.route("/")
def hello():
        return "Hello World!"


@app.route("/_status/liveness")
def liveness():
        return "I am alive"


@app.route("/_shutdown", methods=["POST"])
def shutdown():
    func = request.environ.get('werkzeug.server.shutdown')
    if func is None:
        return "Not a werkzeug server"
    func()
    return "shutdown"


@app.route("/_status/readiness")
def readiness():
        if not shuttingDown:
            return "I am ready"
        else:
            abort(500, 'not ready anymore')


app.run(port=5420)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM