Issue
Let's say I have a simple flask app:
import time
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
for i in range(10):
print(f"Slept for {i + 1}/{seconds} seconds")
time.sleep(1)
return "Hello world"
I can run it with gunicorn with a 5 second timeout:
gunicorn app:app -b 127.0.0.1:5000 -t 5
As expected, http://127.0.0.1:5000 times out after 5 seconds:
Slept for 1/10 seconds
Slept for 2/10 seconds
Slept for 3/10 seconds
Slept for 4/10 seconds
Slept for 5/10 seconds
[2022-07-07 22:45:01 -0700] [57177] [CRITICAL] WORKER TIMEOUT (pid:57196)
Now, I want to run gunicorn with an async worker to allow the web server to use its available resources more efficiently, maximizing time that otherwise would be spent idling to do additional work instead. I'm using gevent, still with a timeout of 5 seconds.
gunicorn app:app -b 127.0.0.1:5000 -t 5 -k gevent
Unexpectedly, http://127.0.0.1:5000 does NOT time out:
Slept for 1/10 seconds
Slept for 2/10 seconds
Slept for 3/10 seconds
Slept for 4/10 seconds
Slept for 5/10 seconds
Slept for 6/10 seconds
Slept for 7/10 seconds
Slept for 8/10 seconds
Slept for 9/10 seconds
Slept for 10/10 seconds
Looks like this is a known issue with gunicorn. The timeout only applies to the default sync worker, not async workers: https://github.com/benoitc/gunicorn/issues/2695
uWSGI is an alternate option to gunicorn. I'm not as familiar with it. Looks like its timeout option is called harakiri
and it can be run with gevent:
uwsgi --http 127.0.0.1:5000 --harakiri 5 --master -w app:app --gevent 100
uWSGI's timeout sometimes works as expected with gevent:
Slept for 1/10 seconds
Slept for 2/10 seconds
Slept for 3/10 seconds
Slept for 4/10 seconds
Slept for 5/10 seconds
Thu Jul 7 23:20:59 2022 - *** HARAKIRI ON WORKER 1 (pid: 59836, try: 1) ***
Thu Jul 7 23:20:59 2022 - HARAKIRI !!! worker 1 status !!!
Thu Jul 7 23:20:59 2022 - HARAKIRI [core 99] 127.0.0.1 - GET / since 1657261253
Thu Jul 7 23:20:59 2022 - HARAKIRI !!! end of worker 1 status !!!
DAMN ! worker 1 (pid: 59836) died, killed by signal 9 :( trying respawn ...
But other times it doesn't time out so it appears to be pretty flaky.
Is there anyway to enforce a timeout using gunicorn with an async worker? If not, are there any other web servers that enforce a consistent timeout with an async worker, similar to uWSGI?
Solution
From https://docs.gunicorn.org/en/stable/settings.html#timeout:
Workers silent for more than this many seconds are killed and restarted.
For the non sync workers it just means that the worker process is still communicating and is not tied to the length of time required to handle a single request.
So timeout
is likely functioning by design — as worker timeout, not request timeout.
You can subclass GeventWorker
to override handle_request()
with gevent.Timeout
:
import gevent
from gunicorn.workers.ggevent import GeventWorker
class MyGeventWorker(GeventWorker):
def handle_request(self, listener_name, req, sock, addr):
with gevent.Timeout(self.cfg.timeout):
super().handle_request(listener_name, req, sock, addr)
Usage:
# gunicorn app:app -b 127.0.0.1:5000 -t 5 -k gevent
gunicorn app:app -b 127.0.0.1:5000 -t 5 -k app.MyGeventWorker
Answered By - aaron
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.