From 70c3b91ce7aaca22ab24ca56c7c204494912d8e5 Mon Sep 17 00:00:00 2001 From: Michael Bell Date: Thu, 15 Mar 2018 09:31:13 +0000 Subject: [PATCH] Fix eventlet graceful timeout handling The `StopServer` exception can lead to a handler blocked waiting for an available greenthread to never be processed. This change ensures we attempt to handle any accepted socket connection within the graceful timeout period. --- gunicorn/workers/async.py | 3 +-- gunicorn/workers/geventlet.py | 18 ++++++++++++------ gunicorn/workers/ggevent.py | 7 ++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/gunicorn/workers/async.py b/gunicorn/workers/async.py index a3a0f9129..d94f0b877 100644 --- a/gunicorn/workers/async.py +++ b/gunicorn/workers/async.py @@ -31,12 +31,11 @@ def is_already_handled(self, respiter): # some workers will need to overload this function to raise a StopIteration return respiter == ALREADY_HANDLED - def handle(self, listener, client, addr): + def handle(self, listener_name, client, addr): req = None try: parser = http.RequestParser(self.cfg, client) try: - listener_name = listener.getsockname() if not self.cfg.keepalive: req = six.next(parser) self.handle_request(listener_name, req, client, addr) diff --git a/gunicorn/workers/geventlet.py b/gunicorn/workers/geventlet.py index f0bb06495..b70911a3e 100644 --- a/gunicorn/workers/geventlet.py +++ b/gunicorn/workers/geventlet.py @@ -48,13 +48,18 @@ def _eventlet_serve(sock, handle, concurrency): pool = eventlet.greenpool.GreenPool(concurrency) server_gt = eventlet.greenthread.getcurrent() + def _eventlet_handle(conn, addr): + gt = pool.spawn(handle, conn, addr) + gt.link(_eventlet_stop, server_gt, conn) + while True: try: conn, addr = sock.accept() - gt = pool.spawn(handle, conn, addr) - gt.link(_eventlet_stop, server_gt, conn) - conn, addr, gt = None, None, None + _eventlet_handle(conn, addr) + conn, addr = None, None except eventlet.StopServe: + if conn and addr: + _eventlet_handle(conn, addr) sock.close() pool.waitall() return @@ -111,19 +116,20 @@ def handle_usr1(self, sig, frame): def timeout_ctx(self): return eventlet.Timeout(self.cfg.keepalive or None, False) - def handle(self, listener, client, addr): + def handle(self, listener_name, client, addr): if self.cfg.is_ssl: client = eventlet.wrap_ssl(client, server_side=True, **self.cfg.ssl_options) - super(EventletWorker, self).handle(listener, client, addr) + super(EventletWorker, self).handle(listener_name, client, addr) def run(self): acceptors = [] for sock in self.sockets: gsock = GreenSocket(sock) gsock.setblocking(1) - hfun = partial(self.handle, gsock) + listener_name = gsock.getsockname() + hfun = partial(self.handle, listener_name) acceptor = eventlet.spawn(_eventlet_serve, gsock, hfun, self.worker_connections) diff --git a/gunicorn/workers/ggevent.py b/gunicorn/workers/ggevent.py index 34ee72a82..7bac47931 100644 --- a/gunicorn/workers/ggevent.py +++ b/gunicorn/workers/ggevent.py @@ -108,7 +108,8 @@ def run(self): handler_class=self.wsgi_handler, environ=environ, **ssl_args) else: - hfun = partial(self.handle, s) + listener_name = s.getsockname() + hfun = partial(self.handle, listener_name) server = StreamServer(s, handle=hfun, spawn=pool, **ssl_args) server.start() @@ -148,11 +149,11 @@ def run(self): except: pass - def handle(self, listener, client, addr): + def handle(self, listener_name, client, addr): # Connected socket timeout defaults to socket.getdefaulttimeout(). # This forces to blocking mode. client.setblocking(1) - super(GeventWorker, self).handle(listener, client, addr) + super(GeventWorker, self).handle(listener_name, client, addr) def handle_request(self, listener_name, req, sock, addr): try: