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 实现中或隐藏在 OSError
到 ConnectionError
子类之间的某些映射中。
我的最终目标是通过比较引发此错误的模块的 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)
ConnectionResetError
在 errno
为 ECONNRESET
时引发。 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', ...)
当OSError
用errno
、it will lookup a more specified subclass构造时,通过在上述errnomap
字典中查找errno
值:
newtype = PyDict_GetItem(errnomap, myerrno);
if (newtype) {
assert(PyType_Check(newtype));
type = (PyTypeObject *) newtype;
}
它实际上 returns 并引发了 ConnectionResetError
异常。
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 实现中或隐藏在 OSError
到 ConnectionError
子类之间的某些映射中。
我的最终目标是通过比较引发此错误的模块的 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)
ConnectionResetError
在 errno
为 ECONNRESET
时引发。 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', ...)
当OSError
用errno
、it will lookup a more specified subclass构造时,通过在上述errnomap
字典中查找errno
值:
newtype = PyDict_GetItem(errnomap, myerrno);
if (newtype) {
assert(PyType_Check(newtype));
type = (PyTypeObject *) newtype;
}
它实际上 returns 并引发了 ConnectionResetError
异常。