From 34ce875c4e488c7c2e8f2d76882e6593fe0b7f5a Mon Sep 17 00:00:00 2001 From: Michal Daniliszyn 1 Date: Thu, 4 Jul 2024 23:51:53 +0200 Subject: [PATCH 1/2] 3227: Wait for the keepalive period to end before stopping the acceptance of new requests. This will prevent potential disruptions in request handling. --- gunicorn/workers/base_async.py | 3 ++- gunicorn/workers/ggevent.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/gunicorn/workers/base_async.py b/gunicorn/workers/base_async.py index 6a79d7ed0..9f648a17d 100644 --- a/gunicorn/workers/base_async.py +++ b/gunicorn/workers/base_async.py @@ -36,7 +36,8 @@ def handle(self, listener, client, addr): parser = http.RequestParser(self.cfg, client, addr) try: listener_name = listener.getsockname() - if not self.cfg.keepalive: + # do not allow keepalive if the worker is about to be restarted + if not self.cfg.keepalive or not self.alive: req = next(parser) self.handle_request(listener_name, req, client, addr) else: diff --git a/gunicorn/workers/ggevent.py b/gunicorn/workers/ggevent.py index 2125a32d0..740bc8d11 100644 --- a/gunicorn/workers/ggevent.py +++ b/gunicorn/workers/ggevent.py @@ -87,6 +87,10 @@ def run(self): self.notify() gevent.sleep(1.0) + # Wait for pending keepalive connections to complete before stopping the acceptance of new requests + if self.cfg.keepalive: + self._wait_for_keepalive(servers) + try: # Stop accepting requests for server in servers: @@ -117,6 +121,19 @@ def run(self): except Exception: pass + def _wait_for_keepalive(self, servers): + # Retrieve all active greenlets and repeatedly check, until the keepalive period ends, + # if any of those greenlets are still active. If none are active, exit the loop. + + greenlets = {id(greenlet) for server in servers for greenlet in server.pool.greenlets} + ts = time.time() + + while time.time() - ts <= self.cfg.keepalive: + if not greenlets.intersection({id(greenlet) for server in servers for greenlet in server.pool.greenlets}): + break + self.notify() + gevent.sleep(1.0) + def handle(self, listener, client, addr): # Connected socket timeout defaults to socket.getdefaulttimeout(). # This forces to blocking mode. From bcdbc09f8f65cc34a0a0cc5d60e76d95d0cccd8e Mon Sep 17 00:00:00 2001 From: Michal Daniliszyn 1 Date: Fri, 12 Jul 2024 13:58:10 +0200 Subject: [PATCH 2/2] #3227: Use a monotonic clock to measure elapsed time. --- gunicorn/workers/ggevent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gunicorn/workers/ggevent.py b/gunicorn/workers/ggevent.py index 740bc8d11..960e50ec4 100644 --- a/gunicorn/workers/ggevent.py +++ b/gunicorn/workers/ggevent.py @@ -126,9 +126,9 @@ def _wait_for_keepalive(self, servers): # if any of those greenlets are still active. If none are active, exit the loop. greenlets = {id(greenlet) for server in servers for greenlet in server.pool.greenlets} - ts = time.time() + ts = time.monotonic() - while time.time() - ts <= self.cfg.keepalive: + while time.monotonic() - ts <= self.cfg.keepalive: if not greenlets.intersection({id(greenlet) for server in servers for greenlet in server.pool.greenlets}): break self.notify()