Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to handle "Connection reset by peer" ? #455

Closed
slash5k1 opened this issue Aug 6, 2018 · 6 comments
Closed

Unable to handle "Connection reset by peer" ? #455

slash5k1 opened this issue Aug 6, 2018 · 6 comments

Comments

@slash5k1
Copy link

slash5k1 commented Aug 6, 2018

Hi,

Is it possible to handle connection reset by peer by adding in some retry logic ?

after a few hours of pulling down emails - its annoying that the script dies, only for me to start it up again and have to start from scratch :(

Traceback (most recent call last):
  File "/venv/lib/python3.7/site-packages/urllib3/connectionpool.py", line 600, in urlopen
    chunked=chunked)
  File "/venv/lib/python3.7/site-packages/urllib3/connectionpool.py", line 384, in _make_request
    six.raise_from(e, None)
  File "<string>", line 2, in raise_from
  File "/venv/lib/python3.7/site-packages/urllib3/connectionpool.py", line 380, in _make_request
    httplib_response = conn.getresponse()
  File "/usr/local/lib/python3.7/http/client.py", line 1321, in getresponse
    response.begin()
  File "/usr/local/lib/python3.7/http/client.py", line 296, in begin
    version, status, reason = self._read_status()
  File "/usr/local/lib/python3.7/http/client.py", line 257, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
  File "/usr/local/lib/python3.7/socket.py", line 589, in readinto
    return self._sock.recv_into(b)
  File "/usr/local/lib/python3.7/ssl.py", line 1049, in recv_into
    return self.read(nbytes, buffer)
  File "/usr/local/lib/python3.7/ssl.py", line 908, in read
    return self._sslobj.read(len, buffer)
ConnectionResetError: [Errno 104] Connection reset by peer

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/venv/lib/python3.7/site-packages/requests/adapters.py", line 445, in send
    timeout=timeout
  File "/venv/lib/python3.7/site-packages/urllib3/connectionpool.py", line 638, in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/venv/lib/python3.7/site-packages/urllib3/util/retry.py", line 367, in increment
    raise six.reraise(type(error), error, _stacktrace)
  File "/venv/lib/python3.7/site-packages/urllib3/packages/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File "/venv/lib/python3.7/site-packages/urllib3/connectionpool.py", line 600, in urlopen
    chunked=chunked)
  File "/venv/lib/python3.7/site-packages/urllib3/connectionpool.py", line 384, in _make_request
    six.raise_from(e, None)
  File "<string>", line 2, in raise_from
  File "/venv/lib/python3.7/site-packages/urllib3/connectionpool.py", line 380, in _make_request
    httplib_response = conn.getresponse()
  File "/usr/local/lib/python3.7/http/client.py", line 1321, in getresponse
    response.begin()
  File "/usr/local/lib/python3.7/http/client.py", line 296, in begin
    version, status, reason = self._read_status()
  File "/usr/local/lib/python3.7/http/client.py", line 257, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
  File "/usr/local/lib/python3.7/socket.py", line 589, in readinto
    return self._sock.recv_into(b)
  File "/usr/local/lib/python3.7/ssl.py", line 1049, in recv_into
    return self.read(nbytes, buffer)
  File "/usr/local/lib/python3.7/ssl.py", line 908, in read
    return self._sslobj.read(len, buffer)
urllib3.exceptions.ProtocolError: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/app/email_extract.py", line 527, in <module>
    engine(item, email_location)
  File "/app/email_extract.py", line 406, in engine
    email_object = process_email(item, email_location)
  File "/app/email_extract.py", line 352, in process_email
    new_email_object = add_email(item, email_location)
  File "/app/email_extract.py", line 258, in add_email
    if check_for_file_attachments(item.attachments):
  File "/app/email_extract.py", line 244, in check_for_file_attachments
    if check_db_for_attachment(attachment):
  File "/app/email_extract.py", line 214, in check_db_for_attachment
    attachment_content = item_attachment.content
  File "/venv/lib/python3.7/site-packages/exchangelib/attachments.py", line 154, in content
    items=[self.attachment_id], include_mime_content=False))
  File "/venv/lib/python3.7/site-packages/exchangelib/services.py", line 1607, in call
    include_mime_content=include_mime_content,
  File "/venv/lib/python3.7/site-packages/exchangelib/services.py", line 88, in _get_elements
    response = self._get_response_xml(payload=payload)
  File "/venv/lib/python3.7/site-packages/exchangelib/services.py", line 164, in _get_response_xml
    allow_redirects=False)
  File "/venv/lib/python3.7/site-packages/exchangelib/util.py", line 547, in post_ratelimited
    _raise_response_errors(r, protocol, log_msg, log_vals)  # Always raises an exception
  File "/venv/lib/python3.7/site-packages/exchangelib/util.py", line 603, in _raise_response_errors
    raise r.headers['TimeoutException']
  File "/venv/lib/python3.7/site-packages/exchangelib/util.py", line 494, in post_ratelimited
    r = session.post(url=url, headers=headers, data=data, allow_redirects=False, timeout=protocol.TIMEOUT)
  File "/venv/lib/python3.7/site-packages/requests/sessions.py", line 559, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "/venv/lib/python3.7/site-packages/requests/sessions.py", line 512, in request
    resp = self.send(prep, **send_kwargs)
  File "/venv/lib/python3.7/site-packages/requests/sessions.py", line 622, in send
    r = adapter.send(request, **kwargs)
  File "/venv/lib/python3.7/site-packages/requests/adapters.py", line 495, in send
    raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer'))

Im assuming a try, except block is needed to handle this condition ?

Cheers,

C

@slash5k1
Copy link
Author

slash5k1 commented Aug 7, 2018

just had a look at util.py and I can see the following:

CONNECTION_ERRORS = (requests.exceptions.ChunkedEncodingError, requests.exceptions.ConnectionError,
                     requests.exceptions.Timeout, socket.timeout)
if not PY2:
    # Python2 does not have ConnectionResetError
    CONNECTION_ERRORS += (ConnectionResetError,)
    try:
        while True:
            back_off_until = protocol.credentials.back_off_until
            if back_off_until:
                sleep_secs = (back_off_until - datetime.datetime.now()).total_seconds()
                # The back off value may have expired within the last few milliseconds
                if sleep_secs > 0:
                    log.warning('Server requested back off until %s. Sleeping %s seconds', back_off_until, sleep_secs)
                    time.sleep(sleep_secs)

            log.debug('Session %s thread %s: retry %s timeout %s POST\'ing to %s after %ss wait', session.session_id,
                      thread_id, retry, protocol.TIMEOUT, url, wait)
            d_start = time_func()
            try:
                r = session.post(url=url, headers=headers, data=data, allow_redirects=False, timeout=protocol.TIMEOUT)
            except CONNECTION_ERRORS as e:
                log.debug('Session %s thread %s: connection error POST\'ing to %s', session.session_id, thread_id, url)
                r = DummyResponse(url=url, headers={'TimeoutException': e}, request_headers=headers)
            except Exception:
                # Always create a dummy response for logging purposes, before re-raising
                r = DummyResponse(url=url, headers={}, request_headers=headers)
                raise
            finally:
                log_vals = dict(
                    retry=retry,
                    wait=wait,
                    timeout=protocol.TIMEOUT,
                    session_id=session.session_id,
                    thread_id=thread_id,
                    auth=session.auth,
                    url=str(r.url),
                    adapter=session.get_adapter(url),
                    allow_redirects=allow_redirects,
                    response_time=time_func() - d_start,
                    status_code=r.status_code,
                    request_headers=r.request.headers,
                    response_headers=r.headers,
                    xml_request=data,
                    xml_response=r.content,
                )
            log.debug(log_msg, log_vals)
            if _may_retry_on_error(r, protocol, wait):
                log.info("Session %s thread %s: Connection error on URL %s (code %s). Cool down %s secs",
                         session.session_id, thread_id, r.url, r.status_code, wait)
                time.sleep(wait)  # Increase delay for every retry
                retry += 1
                wait *= 2
                session = protocol.renew_session(session)
                continue
def _may_retry_on_error(r, protocol, wait):
    # The genericerrorpage.htm/internalerror.asp is ridiculous behaviour for random outages. Redirect to
    # '/internalsite/internalerror.asp' or '/internalsite/initparams.aspx' is caused by e.g. TLS certificate
    # f*ckups on the Exchange server.
    if (r.status_code == 401) \
            or (r.headers.get('connection') == 'close') \
            or (r.status_code == 302 and r.headers.get('location', '').lower() ==
                '/ews/genericerrorpage.htm?aspxerrorpath=/ews/exchange.asmx') \
            or (r.status_code == 503):
        if r.status_code not in (301, 302, 401, 503):
            # Don't retry if we didn't get a status code that we can hope to recover from
            return False
        if protocol.credentials.fail_fast:
            return False
        if wait > protocol.credentials.max_wait:
            # We lost patience. Session is cleaned up in outer loop
            raise RateLimitError('URL %s: Max timeout reached' % r.url)
        return True
    return False

So it looks like there is logic to handle errors and retry however I don't see how to handle "ConnectionResetError, 104"

is it a matter of making changes to the _may_retry_on_error function to include requests.exceptions.ConnectionError ?

Cheers,

C

@ecederstrand
Copy link
Owner

ecederstrand commented Aug 7, 2018

The ServiceAccount class will activate the retry logic. Use this class in place of the Credentials class to handle these errors gracefully.

@slash5k1
Copy link
Author

slash5k1 commented Aug 7, 2018

Wow - staring me in the face 😄

        if protocol.credentials.fail_fast:
            return False

Thankyou and thankyou for a fantastic library

@slash5k1 slash5k1 closed this as completed Aug 7, 2018
@cn3A13
Copy link

cn3A13 commented Mar 22, 2019

+100 on the fantastic library. I had the same issue. Came here and now it is resolved. Appreciate the code and explanation posted here

@yousufinternet
Copy link

The ServiceAccount class will activate the retry logic. Use this class in place of the Credentials class to handle these errors gracefully.

I am sorry I didn't understand what to use instead of the Credentials class, can you write an example please.

@ecederstrand
Copy link
Owner

The ServiceAccount class was removed in v 2.0.0: https://github.com/ecederstrand/exchangelib/blob/master/CHANGELOG.md#200

Instead, use a retry policy: https://github.com/ecederstrand/exchangelib#fault-tolerance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants