mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-07 21:21:30 +08:00
Merge pull request #3590 from benoitc/fix/graceful-connection-close
fix: drain connection on close per RFC 9112 section 9.6
This commit is contained in:
commit
5de593708f
@ -279,6 +279,40 @@ def close(sock):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def close_graceful(sock, timeout=2.0, max_drain=65536):
|
||||||
|
"""Close a TCP socket following RFC 9112 section 9.6.
|
||||||
|
|
||||||
|
Half-closes the write side to send FIN, then lingers on the read side
|
||||||
|
to drain the kernel recv buffer until the peer closes or a cap is hit,
|
||||||
|
then fully closes. This avoids the kernel sending RST (truncating the
|
||||||
|
last response segment) when unread request data remains in the buffer.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
sock.shutdown(socket.SHUT_WR)
|
||||||
|
except OSError:
|
||||||
|
return
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
drained = 0
|
||||||
|
while drained < max_drain:
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
if remaining <= 0:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
sock.settimeout(remaining)
|
||||||
|
data = sock.recv(4096)
|
||||||
|
except (socket.timeout, OSError):
|
||||||
|
break
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
drained += len(data)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
sock.close()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from os import closerange
|
from os import closerange
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import errno
|
|||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
import selectors
|
import selectors
|
||||||
import socket
|
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@ -121,8 +120,12 @@ class TConn:
|
|||||||
finally:
|
finally:
|
||||||
sel.close()
|
sel.close()
|
||||||
|
|
||||||
def close(self):
|
def close(self, graceful=False):
|
||||||
util.close(self.sock)
|
if graceful:
|
||||||
|
self.sock.setblocking(True)
|
||||||
|
util.close_graceful(self.sock)
|
||||||
|
else:
|
||||||
|
util.close(self.sock)
|
||||||
|
|
||||||
|
|
||||||
class PollableMethodQueue:
|
class PollableMethodQueue:
|
||||||
@ -435,7 +438,7 @@ class ThreadWorker(base.Worker):
|
|||||||
partial(self.on_client_socket_readable, conn))
|
partial(self.on_client_socket_readable, conn))
|
||||||
else:
|
else:
|
||||||
self.nr_conns -= 1
|
self.nr_conns -= 1
|
||||||
conn.close()
|
conn.close(graceful=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.nr_conns -= 1
|
self.nr_conns -= 1
|
||||||
conn.close()
|
conn.close()
|
||||||
@ -693,11 +696,7 @@ class ThreadWorker(base.Worker):
|
|||||||
# If the requests have already been sent, we should close the
|
# If the requests have already been sent, we should close the
|
||||||
# connection to indicate the error.
|
# connection to indicate the error.
|
||||||
self.log.exception("Error handling request")
|
self.log.exception("Error handling request")
|
||||||
try:
|
util.close_graceful(conn.sock)
|
||||||
conn.sock.shutdown(socket.SHUT_RDWR)
|
|
||||||
conn.sock.close()
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
raise StopIteration()
|
raise StopIteration()
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@ -7,7 +7,6 @@ from datetime import datetime
|
|||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
import socket
|
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -164,7 +163,7 @@ class SyncWorker(base.Worker):
|
|||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
self.handle_error(req, client, addr, e)
|
self.handle_error(req, client, addr, e)
|
||||||
finally:
|
finally:
|
||||||
util.close(client)
|
util.close_graceful(client)
|
||||||
|
|
||||||
def handle_request(self, listener, req, client, addr):
|
def handle_request(self, listener, req, client, addr):
|
||||||
environ = {}
|
environ = {}
|
||||||
@ -203,11 +202,7 @@ class SyncWorker(base.Worker):
|
|||||||
# If the requests have already been sent, we should close the
|
# If the requests have already been sent, we should close the
|
||||||
# connection to indicate the error.
|
# connection to indicate the error.
|
||||||
self.log.exception("Error handling request")
|
self.log.exception("Error handling request")
|
||||||
try:
|
util.close_graceful(client)
|
||||||
client.shutdown(socket.SHUT_RDWR)
|
|
||||||
client.close()
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
raise StopIteration()
|
raise StopIteration()
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user