mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-04 19:51: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
|
||||
|
||||
|
||||
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:
|
||||
from os import closerange
|
||||
except ImportError:
|
||||
|
||||
@ -15,7 +15,6 @@ import errno
|
||||
import os
|
||||
import queue
|
||||
import selectors
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
import time
|
||||
@ -121,8 +120,12 @@ class TConn:
|
||||
finally:
|
||||
sel.close()
|
||||
|
||||
def close(self):
|
||||
util.close(self.sock)
|
||||
def close(self, graceful=False):
|
||||
if graceful:
|
||||
self.sock.setblocking(True)
|
||||
util.close_graceful(self.sock)
|
||||
else:
|
||||
util.close(self.sock)
|
||||
|
||||
|
||||
class PollableMethodQueue:
|
||||
@ -435,7 +438,7 @@ class ThreadWorker(base.Worker):
|
||||
partial(self.on_client_socket_readable, conn))
|
||||
else:
|
||||
self.nr_conns -= 1
|
||||
conn.close()
|
||||
conn.close(graceful=True)
|
||||
except Exception:
|
||||
self.nr_conns -= 1
|
||||
conn.close()
|
||||
@ -693,11 +696,7 @@ class ThreadWorker(base.Worker):
|
||||
# If the requests have already been sent, we should close the
|
||||
# connection to indicate the error.
|
||||
self.log.exception("Error handling request")
|
||||
try:
|
||||
conn.sock.shutdown(socket.SHUT_RDWR)
|
||||
conn.sock.close()
|
||||
except OSError:
|
||||
pass
|
||||
util.close_graceful(conn.sock)
|
||||
raise StopIteration()
|
||||
raise
|
||||
finally:
|
||||
|
||||
@ -7,7 +7,6 @@ from datetime import datetime
|
||||
import errno
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
@ -164,7 +163,7 @@ class SyncWorker(base.Worker):
|
||||
except BaseException as e:
|
||||
self.handle_error(req, client, addr, e)
|
||||
finally:
|
||||
util.close(client)
|
||||
util.close_graceful(client)
|
||||
|
||||
def handle_request(self, listener, req, client, addr):
|
||||
environ = {}
|
||||
@ -203,11 +202,7 @@ class SyncWorker(base.Worker):
|
||||
# If the requests have already been sent, we should close the
|
||||
# connection to indicate the error.
|
||||
self.log.exception("Error handling request")
|
||||
try:
|
||||
client.shutdown(socket.SHUT_RDWR)
|
||||
client.close()
|
||||
except OSError:
|
||||
pass
|
||||
util.close_graceful(client)
|
||||
raise StopIteration()
|
||||
raise
|
||||
finally:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user