4

I have the following Session object :

    import requests
    from requests.adapters import Retry, HTTPAdapter
    from requests.sessions import Session

    retry_strategy: Retry = Retry(
        total=5,
        status_forcelist=[429, 500, 502, 503, 504],
        allowed_methods=["HEAD", "GET", "OPTIONS", "POST"],
        backoff_factor=1
    )
    http_adapter = HTTPAdapter(max_retries=retry_strategy)
    session = requests.Session()
    session.mount("https://", http_adapter)

and once in a while the server seems to break the connection with an RST TCP-packet resulting in the following Error:

{"isError": true,
 "type": "ConnectionError",
 "message": "[Errno 104] Connection reset by peer",
 "traceback": "...  omitted for brevity ...
    File "/var/task/requests/sessions.py", line 590, in post
    return self.request('POST', url, data=data, json=json, **kwargs)    
    File "/var/task/requests/sessions.py", line 542, in request
    resp = self.send(prep, **send_kwargs)   
    File "/var/task/requests/sessions.py", line 655, in send
    r = adapter.send(request, **kwargs) 
    File "/var/task/requests/adapters.py", line 498, in send
    raise ConnectionError(err, request=request)"
    
  1. Is it possible to inject a Retry object in a Session object so that this particular ConnectionError is automatically retried?
  2. Is there any other way to retry automatically without any additional custom logic such as wrapping it in a try... except statement or creating a custom HTTPAdapter?
  3. I have seen multiple posts regarding 104 connection reset by peer errors, but no one asking how to retry it automatically. Why is that? I assume I won't be the only one with this problem?
Hadronymous
  • 566
  • 5
  • 16
  • I have the same question as well. I don't like try...except around requests.post, just to retry, It looks dirty. The Retry from HttpAdapter fails to capture the ConnectionError that are "connection reset by peer." – Chester Ayala Sep 24 '21 at 09:48
  • If I recall correctly, in my case, it did retry the connection error, however the server it was connecting to, kept on resetting the connection, so all the retries failed in a certain window, after increasing the amount of retries and the backoff factor, it started to occur less... Unless you are using an older version of urllib3 because older versions did have this problem. – Hadronymous Sep 24 '21 at 14:10

4 Answers4

1

Give a look at https://github.com/jd/tenacity

I usually do something like this

from tenacity import wait_exponential

@retry(wait=wait_exponential(multiplier=1, min=4, max=10))
    def wait_exponential_1():
        # my request here

You can create a function that yields a session wrapped with tenacity.

Manuel Fedele
  • 1,390
  • 13
  • 25
1

I have eventually ended up with backoff:

@backoff.on_exception(
    wait_gen=backoff.expo,
    exception=requests.ConnectionError,
    max_time=time_units.in_s.SEC_15,
)
def robust_session_request(*args, **kwargs) -> requests.Response:
    return session.request(*args, **kwargs)
CapedHero
  • 597
  • 1
  • 9
  • 20
0

You would need to provide the "connect" argument:

  from requests import adapters
  import urllib3

  sync_session = requests.Session()
  retry_adapter = adapters.HTTPAdapter(max_retries=urllib3.Retry(total=1, connect=1))
  session.mount('https://', retry_adapter)
  session.mount('http://', retry_adapter)

Test:

class AdapterTest(unittest.TestCase):

  @mock.patch.object(urllib3.connectionpool.HTTPConnectionPool, '_make_request',
                     side_effect=ConnectionResetError)
  def test_adapter(self, mocked):

    s = requests_extensions.HookableSession()
    try:
      s.mount('http://', adapters.HTTPAdapter(max_retries=urllib3.Retry(total=1, connect=1)))
      s.request('GET', 'http://www.test.com')
    except requests.ConnectionError:
      pass

    self.assertEqual(mocked.call_count, 2)
Hoda
  • 280
  • 2
  • 5
  • 1
    I do not think this would work, since the question mentions the "connection reset by peer" error. If I look to the documentation: https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html#urllib3.util.Retry it mentions connection errors without being able to contact the server. I was able to connect to the server, the server just reset the connection... – Hadronymous Jul 16 '22 at 19:34
0

Using this answer, I created a server that returns "Connection reset by peer" all the time. Start the server on one terminal:

$ python server.py

and test it on another terminal:

$ curl http://localhost:5500
curl: (56) Recv failure: Connection reset by peer

Server nicely shows one connection was attempted:

$ python server.py 
new client from 127.0.0.1:55894

Now building on this answer, let's try with Python with Retry:

>>> import requests
>>> import urllib3.util
>>> import requests.adapters
>>> s = requests.Session()
>>> retries = urllib3.util.Retry(connect=2, read=3, redirect=4, status=5, other=6)
>>> s.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries))
>>> s.get("http://localhost:5500")

And server shows 4 connection attempts (assuming you have restarted it since above curl test) - 1 initial attempt and 3 read retries.

$ python server.py 
new client from 127.0.0.1:60790
new client from 127.0.0.1:60798
new client from 127.0.0.1:60810
new client from 127.0.0.1:60818

You can also just use with total:

>>> import requests
>>> import urllib3.util
>>> import requests.adapters
>>> s = requests.Session()
>>> retries = urllib3.util.Retry(total=5)
>>> s.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries))
>>> s.get("http://localhost:5500")
$ python server.py 
new client from 127.0.0.1:37164
new client from 127.0.0.1:37176
new client from 127.0.0.1:37190
new client from 127.0.0.1:37192
new client from 127.0.0.1:42852
new client from 127.0.0.1:42864
jhutar
  • 1,369
  • 2
  • 17
  • 32