SSL ConnectionResetError 从哪里来?

from where SSL ConnectionResetError comes from?

TL;DR

我的问题很简单 - 在调用 self._sslobj.read(len, buffer) 之后,负责 raise ConnectionResetError 的代码在哪里 python3 =15=]?

背景

我在尝试使用 ssl 连接到 S3 时有时会遇到 ConnectionResetError。此错误很少发生,因此很难重现。

# trimmed stacktrace
File "/MYPROJECT/MY_FUNC.py", line 123, in <genexpr>
rows = (row for row in reader)
File "/XXX/lib/python3.6/csv.py", line 112, in _next_
row = next(self.reader)
File "/XXX/lib/python3.6/tarfile.py", line 706, in readinto
buf = self.read(len(b))
File "/XXX/lib/python3.6/tarfile.py", line 695, in read
b = self.fileobj.read(length)
File "/XXX/lib/python3.6/gzip.py", line 276, in read
return self._buffer.read(size)
File "/XXX/lib/python3.6/_compression.py", line 68, in readinto
data = self.read(len(byte_view))
File "/XXX/lib/python3.6/gzip.py", line 469, in read
buf = self._fp.read(io.DEFAULT_BUFFER_SIZE)
File "/XXX/lib/python3.6/gzip.py", line 91, in read
self.file.read(size-self._length+read)
File "/XXX/lib/python3.6/site-packages/s3fs/core.py", line 1311, in read
self._fetch(self.loc, self.loc + length)
File "/XXX/lib/python3.6/site-packages/s3fs/core.py", line 1292, in _fetch
req_kw=self.s3.req_kw)
File "/XXX/lib/python3.6/site-packages/s3fs/core.py", line 1496, in _fetch_range
return resp['Body'].read()
File "/XXX/lib/python3.6/site-packages/botocore/response.py", line 74, in read
chunk = self._raw_stream.read(amt)
File "/XXX/lib/python3.6/site-packages/botocore/vendored/requests/packages/urllib3/response.py", line 239, in read
data = self._fp.read()
File "/XXX/lib/python3.6/http/client.py", line 462, in read
s = self._safe_read(self.length)
File "/XXX/lib/python3.6/http/client.py", line 612, in _safe_read
chunk = self.fp.read(min(amt, MAXAMOUNT))
File "/XXX/lib/python3.6/socket.py", line 586, in readinto
return self._sock.recv_into(b)
File "/XXX/lib/python3.6/ssl.py", line 1009, in recv_into
return self.read(nbytes, buffer)
File "/XXX/lib/python3.6/ssl.py", line 871, in read
return self._sslobj.read(len, buffer)
File "/XXX/lib/python3.6/ssl.py", line 631, in read
v = self._sslobj.read(len, buffer)
ConnectionResetError: [Errno 104] Connection reset by peer

我试过的

查看 ssl.py:631 没有给我更多线索 - 我们必须更深入!:

    def read(self, len=1024, buffer=None):
    """Read up to 'len' bytes from the SSL object and return them.

    If 'buffer' is provided, read into this buffer and return the number of
    bytes read.
    """
    if buffer is not None:
        v = self._sslobj.read(len, buffer)  # <--- exception here
    else:
        v = self._sslobj.read(len)
    return v

我已经尝试搜索它 on CPython repo 但 AFAICS 似乎没有任何东西可以引发它,我怀疑它隐藏在 SSL 实现中或隐藏在 OSErrorConnectionError 子类之间的某些映射中。

我的最终目标是通过比较引发此错误的模块的 py2 和 py3 版本来编写 py2 和 py3 兼容代码来处理此异常(ConnectionError 在 py3 上是新的)。


更新 - ConnectionError 子类的 py2 和 py3 捕获

我的问题起源是找到一种方法来捕获 ConnectionError 及其在 python2 和 python3 上的子类,所以这里是:

import errno

# ref: https://docs.python.org/3/library/exceptions.html#ConnectionError
_CONNECTION_ERRORS = frozenset({
    errno.ECONNRESET,  # ConnectionResetError
    errno.EPIPE, errno.ESHUTDOWN,  # BrokenPipeError
    errno.ECONNABORTED,  # ConnectionAbortedError
    errno.ECONNREFUSED,  # ConnectionRefusedError
})

try:
    ...
except OSError as e:
    if e.errno not in _CONNECTION_ERRORS:
        raise
    print('got ConnectionError - %e' % e)

ConnectionResetErrorerrnoECONNRESET 时引发。 errno 是 libc 指示系统调用是否发生错误的方式。

您可以在 Objects/exceptions.c 中搜索 ConnectionResetError 以了解如何初始化此异常类型并将其添加到 errnomap dict。

self._sslobj.read引发ConnectionResetError的情况下,_sslobj.read是用_ssl__SSLSocket_read_impl实现的,实际的ssl读取是用openssl的SSL_read完成的:

count = SSL_read(self->ssl, mem, len);
_PySSL_UPDATE_ERRNO_IF(count <= 0, self, count);

由于发生错误,_PySSL_UPDATE_ERRNO_IF 将设置 (sock)->ssl_errno = SSL_ERROR_SYSCALL(sock)->c_errno = ECONNRESET

稍后,在 PySSL_SetError:

    err = obj->ssl_errno;
    switch (err) {
    ...
    case SSL_ERROR_SYSCALL:

        if (obj->c_errno) {
            errno = obj->c_errno;
            return PyErr_SetFromErrno(PyExc_OSError);
        }

PyErr_SetFromErrno(PyExc_OSError) 等于:

OSError(errno.ECONNRESET, 'Connection reset by peer', ...)

OSErrorerrnoit will lookup a more specified subclass构造时,通过在上述errnomap字典中查找errno值:

newtype = PyDict_GetItem(errnomap, myerrno);
if (newtype) {
    assert(PyType_Check(newtype));
    type = (PyTypeObject *) newtype;
}

它实际上 returns 并引发了 ConnectionResetError 异常。