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
(sock, timeout=2.0, max_drain=65536)
| 280 | |
| 281 | |
| 282 | def close_graceful(sock, timeout=2.0, max_drain=65536): |
| 283 | """Close a TCP socket following RFC 9112 section 9.6. |
| 284 | |
| 285 | Half-closes the write side to send FIN, then lingers on the read side |
| 286 | to drain the kernel recv buffer until the peer closes or a cap is hit, |
| 287 | then fully closes. This avoids the kernel sending RST (truncating the |
| 288 | last response segment) when unread request data remains in the buffer. |
| 289 | """ |
| 290 | try: |
| 291 | try: |
| 292 | sock.shutdown(socket.SHUT_WR) |
| 293 | except OSError: |
| 294 | return |
| 295 | deadline = time.monotonic() + timeout |
| 296 | drained = 0 |
| 297 | while drained < max_drain: |
| 298 | remaining = deadline - time.monotonic() |
| 299 | if remaining <= 0: |
| 300 | break |
| 301 | try: |
| 302 | sock.settimeout(remaining) |
| 303 | data = sock.recv(4096) |
| 304 | except (socket.timeout, OSError): |
| 305 | break |
| 306 | if not data: |
| 307 | break |
| 308 | drained += len(data) |
| 309 | finally: |
| 310 | try: |
| 311 | sock.close() |
| 312 | except OSError: |
| 313 | pass |
| 314 | |
| 315 | |
| 316 | try: |
nothing calls this directly
no test coverage detected