mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-07 21:21:30 +08:00
fix: drain connection on close per RFC 9112 section 9.6
Avoids TCP RST truncating the response tail when unread request data (body, pipelined bytes, trailers) sits in the kernel recv buffer at close time. Half-closes write, linger-reads (bounded 2s / 64 KB), then closes.
This commit is contained in:
parent
9aa54703f4
commit
9d422c3ef0
@ -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