Skip to content

Commit

Permalink
Prevent gthread connection reset on max-requests restart
Browse files Browse the repository at this point in the history
  • Loading branch information
aszubarev committed Feb 6, 2025
1 parent bacbf8a commit 2e06888
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 5 deletions.
28 changes: 28 additions & 0 deletions gunicorn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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"
Expand Down
18 changes: 13 additions & 5 deletions gunicorn/workers/gthread.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand Down

0 comments on commit 2e06888

Please sign in to comment.