Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using custom signal handlers with gunicorn

Tags:

I have a Flask application with custom signal handlers to take care of clean up tasks before exiting. When running the application with gunicorn, gunicorn kills the application before it can complete all clean up tasks.

like image 517
Python Novice Avatar asked Dec 11 '16 21:12

Python Novice


People also ask

Does gunicorn do load balancing?

Gunicorn relies on the operating system to provide all of the load balancing when handling requests.

How many concurrent requests can gunicorn handle?

Yes, with 5 worker processes, each with 8 threads, 40 concurrent requests can be served.

Does gunicorn support async?

The default sync worker is appropriate for many use cases. If you need asynchronous support, Gunicorn provides workers using either gevent or eventlet. This is not the same as Python's async/await , or the ASGI server spec. You must actually use gevent/eventlet in your own code to see any benefit to using the workers.

Is gunicorn multithreaded?

Gunicorn also allows for each of the workers to have multiple threads. In this case, the Python application is loaded once per worker, and each of the threads spawned by the same worker shares the same memory space.


1 Answers

You didn't explain what you mean by custom signal handlers, but I'm not sure that you should be using Flask's signals to capture process-level events, like shutdown. Instead, you can use the signal module from the standard library to hook onto the SIGTERM signal, like so:

# app.py - CREATE THIS FILE
from flask import Flask
from time import sleep, time
import signal
import sys

def create_app():
  signal.signal(signal.SIGTERM, my_teardown_handler)
  app = Flask(__name__)

  @app.route('/')
  def home():
    return 'hi'

  return app


def my_teardown_handler(signal, frame):
  """Sleeps for 3 seconds, then creates/updates a file named app-log.txt with the timestamp."""
  sleep(3)
  with open('app-log.txt', 'w') as f:
    msg = ''.join(['The time is: ', str(time())])
    f.write(msg)
  sys.exit(0)


if __name__ == '__main__':
  app = create_app()
  app.run(port=8888)


# wsgi.py - CREATE THIS FILE, in same folder as app.py
import os
import sys
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.exceptions import NotFound

from app import create_app
app = DispatcherMiddleware(create_app())

Assuming you have a virtual environment with Flask and Gunicorn installed, you should then be able to launch the app with Gunicorn:

$ gunicorn --bind 127.0.0.1:8888 --log-level debug wsgi:app

Next, in a separate terminal, you can send the TERM signal to your app, like so:

$ kill -s TERM [PROCESS ID OF GUNICORN PROCESS / $(ps ax | grep gunicorn | head -n 1 | awk '{print $1}')]

And to observe the results, you should notice that the contents of the app-log.txt file get updated when you run that kill command, after the three-second delay. You could even spawn a third terminal window in this directory and run watch -n 1 "cat app-log.txt" to observe this file being updated in real time, while you cycle between starting the app and sending the TERM signal.

As for tying that into production, I know that Supervisor has a configuration option to specify the stopsignal, like so:

[program:my-app]
command = /path/to/gunicorn [RUNTIME FLAGS]
stopsignal = TERM
...

But that's a separate topic from the original issue of ensuring that your app's clean up tasks are completely executed.

like image 82
YellowShark Avatar answered Sep 22 '22 16:09

YellowShark