SSL connections with timeout dont seem to respect retries

Sending an https request with timeout, does not make retries when it gets a timeout.
Code:

``` python
import requests
s = requests.session()
s.mount('https://localhost:10000', requests.adapters.HTTPAdapter(max_retries = 3))
s.post('https://localhost:10000', timeout = 3)
```

Raises this exception (with no retries):

```
File "/usr/local/lib/python2.7/dist-packages/requests/sessions.py", line 498, in post
    return self.request('POST', url, data=data, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/requests/sessions.py", line 456, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/local/lib/python2.7/dist-packages/requests/sessions.py", line 559, in send
    r = adapter.send(request, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/requests/adapters.py", line 382, in send
    raise SSLError(e, request=request)
SSLError: _ssl.c:492: The handshake operation timed out
```

A similar request (even using the same session object) with `http` instead of `https` raises the exception `Timeout: HTTPConnectionPool(host='localhost', port=10000): Read timed out. (read timeout=1)` after 3 retries as expected.

This does not seem to be a general issue with ssl + retries - I've successfully used both

The problem was found on version 2.3.0 both on linux and windows, and also on the latest version - 2.4.3 (tried on linux only).


Taking a look now


Previously (before v2.4.0) `max_retries` meant requests (specifically, the logic in urllib3 underlying requests) would retry the request if a server failed to return a response (a read timeout). After 2.4.0 (and urllib3 1.9), read timeouts are no longer retried.

I made this change because a read timeout implies the request made it to the server, and if the code retried the request (especially with a POST request as you are trying above) this means developers could un-intentionally send duplicate data. If you are trying to POST to a server to charge a credit card, and the server is accepting data but not responding in time, an auto-retry could have really bad consequences. Another reason not to retry read timeouts is to avoid overloading a server which is (possibly) having a hard time responding to requests in the specified amount of time.

The `max_retries` parameter will still retry anything implying that the connection never made it to the server - connection errors and connection timeouts - as the 3rd code block in the gist, and the logging data, indicates.

I tried this with [the following gist](https://gist.github.com/kevinburke/13e10e23838361955223), and neither protocol (http or https) retried. (For the most accurate timing info run this a few times so DNS, caches are warm etc)

### version 2.3.0

```
# Timeout set to 3 seconds
2.3.0
2014-10-26 12:39:08,697 DEBUG Added an stderr logging handler to logger: requests.packages.urllib3
Checking request read timeout over HTTP
2014-10-26 12:39:08,883 INFO Starting new HTTP connection (1): httpbin.org
2014-10-26 12:39:12,420 WARNING Retrying (3 attempts remain) after connection broken by 'ReadTimeoutError("HTTPConnectionPool(host='httpbin.org', port=80): Read timed out. (read timeout=3)",)': /delay/5
2014-10-26 12:39:12,420 INFO Starting new HTTP connection (2): httpbin.org
2014-10-26 12:39:15,629 WARNING Retrying (2 attempts remain) after connection broken by 'ReadTimeoutError("HTTPConnectionPool(host='httpbin.org', port=80): Read timed out. (read timeout=3)",)': /delay/5
2014-10-26 12:39:15,630 INFO Starting new HTTP connection (3): httpbin.org
2014-10-26 12:39:18,835 WARNING Retrying (1 attempts remain) after connection broken by 'ReadTimeoutError("HTTPConnectionPool(host='httpbin.org', port=80): Read timed out. (read timeout=3)",)': /delay/5
2014-10-26 12:39:18,836 INFO Starting new HTTP connection (4): httpbin.org
request took 13.3297190666 seconds to error
Checking request read timeout over HTTPS
2014-10-26 12:39:22,043 INFO Starting new HTTPS connection (1): httpbin.org
2014-10-26 12:39:25,622 WARNING Retrying (3 attempts remain) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out.",)': /delay/5
2014-10-26 12:39:25,623 INFO Starting new HTTPS connection (2): httpbin.org
2014-10-26 12:39:29,609 WARNING Retrying (2 attempts remain) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out.",)': /delay/5
2014-10-26 12:39:29,610 INFO Starting new HTTPS connection (3): httpbin.org
2014-10-26 12:39:33,173 WARNING Retrying (1 attempts remain) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out.",)': /delay/5
2014-10-26 12:39:33,173 INFO Starting new HTTPS connection (4): httpbin.org
request took 14.7081508636 seconds to error
Checking request connection timeout over HTTP
2014-10-26 12:39:36,752 INFO Starting new HTTP connection (1): httpbin.org
2014-10-26 12:39:38,754 WARNING Retrying (3 attempts remain) after connection broken by 'ConnectTimeoutError(<requests.packages.urllib3.connectionpool.HTTPConnectionPool object at 0x0000000105926720>, 'Connection to httpbin.org timed out. (connect timeout=2)')': /
2014-10-26 12:39:38,754 INFO Starting new HTTP connection (2): httpbin.org
2014-10-26 12:39:40,756 WARNING Retrying (2 attempts remain) after connection broken by 'ConnectTimeoutError(<requests.packages.urllib3.connectionpool.HTTPConnectionPool object at 0x0000000105926720>, 'Connection to httpbin.org timed out. (connect timeout=2)')': /
2014-10-26 12:39:40,757 INFO Starting new HTTP connection (3): httpbin.org
2014-10-26 12:39:42,759 WARNING Retrying (1 attempts remain) after connection broken by 'ConnectTimeoutError(<requests.packages.urllib3.connectionpool.HTTPConnectionPool object at 0x0000000105926720>, 'Connection to httpbin.org timed out. (connect timeout=2)')': /
2014-10-26 12:39:42,759 INFO Starting new HTTP connection (4): httpbin.org
request took 8.01387906075 seconds to error
```

### version 2.4.3

```
# Timeout set to 3 seconds
2.4.3
2014-10-26 12:33:30,025 DEBUG Added a stderr logging handler to logger: requests.packages.urllib3
Checking request read timeout over HTTP
2014-10-26 12:33:30,623 INFO Starting new HTTP connection (1): httpbin.org
request took 3.75001692772 seconds to error

Checking request read timeout over HTTPS
2014-10-26 12:33:33,787 INFO Starting new HTTPS connection (1): httpbin.org
request took 3.53643298149 seconds to error

Checking request connection timeout over HTTP
2014-10-26 12:33:37,325 INFO Starting new HTTP connection (1): httpbin.org
2014-10-26 12:33:39,328 DEBUG Incremented Retry for (url='/'): Retry(total=2, connect=None, read=False, redirect=None)
2014-10-26 12:33:39,328 WARNING Retrying (Retry(total=2, connect=None, read=False, redirect=None)) after connection broken by 'ConnectTimeoutError(<requests.packages.urllib3.connection.HTTPConnection object at 0x0000000103664138>, 'Connection to httpbin.org timed out. (connect timeout=2)')': /
2014-10-26 12:33:39,328 INFO Starting new HTTP connection (2): httpbin.org
2014-10-26 12:33:41,331 DEBUG Incremented Retry for (url='/'): Retry(total=1, connect=None, read=False, redirect=None)
2014-10-26 12:33:41,331 WARNING Retrying (Retry(total=1, connect=None, read=False, redirect=None)) after connection broken by 'ConnectTimeoutError(<requests.packages.urllib3.connection.HTTPConnection object at 0x0000000103664170>, 'Connection to httpbin.org timed out. (connect timeout=2)')': /
2014-10-26 12:33:41,332 INFO Starting new HTTP connection (3): httpbin.org
2014-10-26 12:33:43,334 DEBUG Incremented Retry for (url='/'): Retry(total=0, connect=None, read=False, redirect=None)
2014-10-26 12:33:43,335 WARNING Retrying (Retry(total=0, connect=None, read=False, redirect=None)) after connection broken by 'ConnectTimeoutError(<requests.packages.urllib3.connection.HTTPConnection object at 0x00000001036641a8>, 'Connection to httpbin.org timed out. (connect timeout=2)')': /
2014-10-26 12:33:43,335 INFO Starting new HTTP connection (4): httpbin.org
request took 8.01690006256 seconds to error
```

To retry even when the server doesn't return a response in time, you'll want to either catch a `Timeout` in your code and re-run the request, or wait for #2216 to get merged and then pass a custom `urllib3.util.Retry(total=3, read=3)` object to the `max_retries` parameter.

I'll update the HISTORY log to indicate this change.


This was extremely subtle and something none of us caught. I'm sorry for the inconvenience @itaiin.

Thanks for figuring this out @kevinburke. I agree with your reasoning about not retrying a POST request for those exact reasons.


Hi, thanks for looking into it!

I don't necessarily agreeing with the logic - The developer should know if their requests are made to a method which is idempotent or not. In fact - this is exactly my case, where I have some that do, for which I use retries and some that dont - so I just dont.

In any case, the behavior seems to be inconsistent between http and https, which I believe to be bad anyway.


Agreed that idempotent requests are generally safe to retry. But there are a lot of failures that Requests could probably retry and chooses not to, for example 500 Server Error, 429 Too Many Requests, 503 Service Unavailable, etc. In the hierarchy of errors, a read timeout feels closer to a 500 server error, since data made it to the server in both cases.

[A `Retry(read=3, total=3)` object](https://urllib3.readthedocs.org/en/latest/helpers.html#module-urllib3.util.retry) _will_ retry failed requests made with GET, PUT, DELETE, etc (once #2216 is merged, which should be soon), and you can configure it using the `method_whitelist` parameter.

> In any case, the behavior seems to be inconsistent between http and https, which I believe to be bad anyway.

I wasn't able to reproduce this. I tried to reproduce the code you posted using the following gist: https://gist.github.com/kevinburke/13e10e23838361955223. When running the gist, the timeout behavior was the same for HTTP and HTTPS, in both 2.3.0 and 2.4.3 (for Python 2.7.8). If you could paste more code, log data or turn on debug logging, I can help figure out why the behaviour is inconsistent.


Well, idempotent requests are safe to retry by definition :). I agree that timeout could go either way since its the one error where you are not sure if both parties agree that there was an error, still if it was my call I think I would of vote for retries but.. ok.

About reproducing - there's no more code needed... What you need is having a server that accepts the tcp connection but does do anything else - I used netcat for that (`nc -L -p 10000`). That also allowed me to see if retries actually happen instead of deducing that from the time passed.
So anyway, I've ran the code using the url from your gist and indeed there was no difference for me either. I get 14 seconds for both

So perhaps the difference is that with the netcat scenerio the timeout occures while the SSL handshake is done, and not while waiting for HTTP transmissions? (BTW: Referring back to the error hierarchy - this feels more like a connection timeout to me ;))


Okay, I tried it out and I can see the difference now.
- in v2.3.0, requests via HTTP to this TCP-only endpoint would retry 3 times. requests via HTTPS would fail with an SSLError (and not retry)
- in v2.4.3, requests via HTTP fail once with a read timeout, and requests via HTTPS fail with an SSLError (and not retry). 

It seems bad that "The handshake operation timed out" is raised as an SSLError.

Added https://github.com/shazow/urllib3/issues/492 to discuss this.


@kevinburke thanks for all the effort!