откуда взялась SSL ConnectionResetError? - PullRequest
0 голосов
/ 13 сентября 2018

TL; DR

Мой вопрос прост - где код отвечает за повышение ConnectionResetError на cpython3 после вызова self._sslobj.read(len, buffer) на ssl.py?

Фон

Иногда я получаю ConnectionResetError при попытке подключиться к S3 с помощью ssl. эта ошибка возникает редко, поэтому сложно воспроизвести ее.

# 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

Я пытался найти его в репозитории CPython , но AFAICS, похоже, ничего не вызывает, я подозреваю, что он скрыт в реализации SSL или при отображении между OSError в ConnectionError подклассами.

Моя конечная цель - написать совместимый с py2 & py3 код для обработки этих исключений (ConnectionError является новым для py3) путем сравнения версий модуля py2 & py3, которые вызывают эту ошибку.


Обновление - поймать py2 & py3 для ConnectionError подклассов

Мой вопрос был в том, чтобы найти способ поймать 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)

1 Ответ

0 голосов
/ 17 сентября 2018

ConnectionResetError повышается, когда errno равно ECONNRESET.errno - это то, как libc указывает, произошла или нет ошибка в системном вызове.

Вы можете выполнить поиск ConnectionResetError в Objects/exceptions.c, чтобы узнать, как этот тип исключения инициализируется и добавляется в errnomap dict.

В случае self._sslobj.read поднял ConnectionResetError, _sslobj.read реализован с _ssl__SSLSocket_read_impl, фактическое чтение ssl выполняется с SSL_read openssl:

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, , он будет искать более определенный подкласс , путем поиска значения errno в вышеупомянутом errnomap dict:

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

фактически возвращается и вызывает исключение ConnectionResetError.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...