http.client.HTTPConnection.connect() leaks the socket when setting TCP_NODELAY fails (EINVAL on macOS after a reset)
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Python
- Star
- 77.2k
- Fork
- 35.9k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
Bug report
Bug description:
http.client.HTTPConnection.connect() creates the socket, then sets TCP_NODELAY on it:
self.sock = self._create_connection(
(self.host,self.port), self.timeout, self.source_address)
# Might fail in OSs that don't implement TCP_NODELAY
try:
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except OSError as e:
if e.errno != errno.ENOPROTOOPT:
raise
If setsockopt() fails with anything other than ENOPROTOOPT, the error propagates with self.sock still holding the open, connected socket. A caller that drops the connection object then leaks the socket:
ResourceWarning: unclosed <socket.socket fd=4, family=2, type=1, proto=6, laddr=('127.0.0.1', 62584), raddr=('127.0.0.1', 62583)>
This is not hypothetical. On macOS, setsockopt(TCP_NODELAY) raises EINVAL once the peer has reset the connection. Racing a server that accepts and immediately closes with SO_LINGER set to zero (an RST) against a client that connects and sets TCP_NODELAY, 299 of 400 attempts on macOS 26 raised OSError(22, 'Invalid argument') from setsockopt(), one raised ECONNRESET, and 100 succeeded.
That is exactly the scenario of test_ssl.TestPreHandshakeClose.test_https_client_non_tls_response_ignored, whose server responds and resets the connection right after accepting. When the reset wins the race, connect() raises from setsockopt() instead of from the TLS handshake the test expects, and the plain socket is never closed. The test still passes (it only requires an OSError), but the socket is finalized after the test method returns, and regrtest reports the module as "env changed". Seen on the macOS x86-64 CI job of 374851f3db, in both the initial run and the rerun: https://github.com/python/cpython/actions/runs/33514119791/job/99876792339
test_https_client_non_tls_response_ignored (test.test_ssl.TestPreHandshakeClose.test_https_client_non_tls_response_ignored) ... Warning -- Unraisable exception
Exception ignored while finalizing socket <socket.socket fd=6, family=2, type=1, proto=6, laddr=('127.0.0.1', 49773)>:
ResourceWarning: unclosed <socket.socket fd=6, family=2, type=1, proto=6, laddr=('127.0.0.1', 49773)>
ok
The leaked object is a plain socket.socket, not an ssl.SSLSocket, which is what shows the failure happened before wrap_socket() took the file descriptor over: the only step in between that can raise while leaving self.sock assigned is that setsockopt() call. gh-110011 lists this test's ResourceWarning among others but does not identify the cause.
_tunnel() already handles its own connect-time failure by calling self.close() before raising. connect() should do the same when TCP_NODELAY cannot be set. I have a PR with the fix and a test.
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Linked PRs
- gh-157175
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu tại http.client.HTTPConnection.connect(), tập trung vào nhánh lỗi của TCP_NODELAY setsockopt() và cách _tunnel() xử lý các lỗi xảy ra khi kết nối. Xem lại test.test_ssl.TestPreHandshakeClose.test_https_client_non_tls_response_ignored và hành vi ResourceWarning của nó. Được xem là hoàn tất khi socket được đóng nếu việc thiết lập TCP_NODELAY thất bại và bài kiểm thử liên quan chạy qua mà không có cảnh báo socket chưa được đóng.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- python
- Lĩnh vực
- backend, networking
- Loại issue
- Lỗi
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức độ hoạt động
- Đình trệ
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 25/100