From 2e068880fde48a63975e373603bee2efefeb5826 Mon Sep 17 00:00:00 2001 From: aszubarev Date: Thu, 6 Feb 2025 11:24:23 +0300 Subject: [PATCH] Prevent gthread connection reset on max-requests restart --- gunicorn/config.py | 28 ++++++++++++++++++++++++++++ gunicorn/workers/gthread.py | 18 +++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/gunicorn/config.py b/gunicorn/config.py index 402a26b68..03dafab33 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -125,6 +125,10 @@ def worker_class(self): worker_class.setup() return worker_class + @property + def worker_connections_enqueue_async(self): + return self.settings['worker_connections_enqueue_async'].get() + @property def address(self): s = self.settings['bind'].get() @@ -737,6 +741,30 @@ class WorkerConnections(Setting): """ +class WorkerConnectionsEnqueueAsync(Setting): + name = "worker_connections_enqueue_async" + section = "Worker Processes" + cli = ["--worker-connections-enqueue-async"] + validator = validate_bool + action = 'store_true' + default = False + desc = """\ + Enqueue accepted connections to worker asynchronously. + + It helps to use Gunicorn without reverse proxy. + When clients connect to Gunicorn, the server will start read data only when clients sent data. + If clients keep connections without sending data, server only accept the connections and no block the worker; + + .. note:: + If you're enable ``max-requests`` and it option, the worker can restart with accepted connections. + + When clients send data via connections after restart the worker, + clients may have error ``Connection reset by peer`` + + This setting only affects the ``gthread`` worker types. + """ + + class MaxRequests(Setting): name = "max_requests" section = "Worker Processes" diff --git a/gunicorn/workers/gthread.py b/gunicorn/workers/gthread.py index 7a23228cd..3e3c8693b 100644 --- a/gunicorn/workers/gthread.py +++ b/gunicorn/workers/gthread.py @@ -123,10 +123,18 @@ def accept(self, server, listener): conn = TConn(self.cfg, sock, client, server) self.nr_conns += 1 - # wait until socket is readable - with self._lock: - self.poller.register(conn.sock, selectors.EVENT_READ, - partial(self.on_client_socket_readable, conn)) + + if not self.cfg.worker_connections_enqueue_async: + # submit the connection to a worker + self.enqueue_req(conn) + else: + # wait until socket is readable + with self._lock: + self.poller.register( + conn.sock, + selectors.EVENT_READ, + partial(self.on_client_socket_readable, conn), + ) except OSError as e: if e.errno not in (errno.EAGAIN, errno.ECONNABORTED, errno.EWOULDBLOCK): @@ -137,7 +145,7 @@ def on_client_socket_readable(self, conn, client): # unregister the client from the poller self.poller.unregister(client) - if conn.initialized: + if not self.cfg.worker_connections_enqueue_async or conn.initialized: # remove the connection from keepalive try: self._keep.remove(conn)