python / python/cpython

http.client.HTTPConnection.connect() leaks the socket when setting TCP_NODELAY fails (EINVAL on macOS after a reset)

未关闭
#157,174 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

stdlib type-bug
主要语言
Python
星标
77.2k
派生
35.9k
PR 合并指标
PR 指标待抓取

描述

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

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 http.client.HTTPConnection.connect() 开始,重点关注 TCP_NODELAY setsockopt() 的失败路径,以及 _tunnel() 如何处理连接时错误。检查 test.test_ssl.TestPreHandshakeClose.test_https_client_non_tls_response_ignored 及其 ResourceWarning 行为。当设置 TCP_NODELAY 失败时 socket 会被关闭,并且相关测试通过且没有未关闭 socket 的警告,即表示完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
python
领域
backend, networking
Issue 类型
缺陷
难度
2/5
预计耗时
1-3 小时
活跃度
停滞
描述清晰度
描述清楚
新手友好度
25/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。