timeout causes exception unrelated to timeout

When I submit a request without specifying a timeout, I get a 404 response (appropriately). For the same URL, requested with a timeout, I immediately get an exception claiming that the remote host has forcibly closed the connection. I can't imagine how the timeout mechanism could result in this behavior.

```
H:\tests>python
ActivePython 2.7.10.12 (ActiveState Software Inc.) based on
Python 2.7.10 (default, Aug 21 2015, 12:07:58) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> requests.__version__
'2.13.0'
>>> requests.get("http://www.cancer.gov/cam")
<Response [404]>
>>> requests.get("http://www.cancer.gov/cam", timeout=5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\Python\lib\site-packages\requests\api.py", line 70, in get
    return request('get', url, params=params, **kwargs)
  File "D:\Python\lib\site-packages\requests\api.py", line 56, in request
    return session.request(method=method, url=url, **kwargs)
  File "D:\Python\lib\site-packages\requests\sessions.py", line 488, in request
    resp = self.send(prep, **send_kwargs)
  File "D:\Python\lib\site-packages\requests\sessions.py", line 609, in send
    r = adapter.send(request, **kwargs)
  File "D:\Python\lib\site-packages\requests\adapters.py", line 473, in send
    raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', error(10054, 'An existing connection was forcibly closed by the remote host'))
```

Does this reproduce consistently?

Yes. Every time I request this URL with a timeout, an exception is thrown.

So, I can't reproduce this *at all*, and it seems absurd. Adding a timeout should not cause the remote peer to shut the connection in your face. This is a tough question, I know, but have you tested whether it really is the timeout? Can you, for example, make a sequence of requests without timeouts and then add one at the end with one to see how that goes?

Sure. I added a timer to make it clear that it's not timing out (though I suppose printing the exception takes care of that).

```
D:> cat repro.py
import datetime
import requests

start = datetime.datetime.now()
url = "http://www.cancer.gov/cam"
for i in range(10):
    print requests.get(url)
try:
    print requests.get(url, timeout=10)
except Exception as e:
    print e
print "elapsed", datetime.datetime.now() - start

D:> python repro.py
<Response [404]>
<Response [404]>
<Response [404]>
<Response [404]>
<Response [404]>
<Response [404]>
<Response [404]>
<Response [404]>
<Response [404]>
<Response [404]>
('Connection aborted.', error(10054, 'An existing connection was forcibly closed by the remote host'))
elapsed 0:00:01.103000

```

Ok, so this is bizarre. Can you get me a packet capture using a tool like Wireshark?

Let me see what I can do.

Here you go. Let me know if you need anything else.

[requests-issue-3880-201702180702.zip](https://github.com/kennethreitz/requests/files/785003/requests-issue-3880-201702180702.zip)


And here's the capture for the same request without the timeout parameter (and without the exception):

[requests-issue-3880-201702180720.zip](https://github.com/kennethreitz/requests/files/785007/requests-issue-3880-201702180720.zip)

Doesn't look much different, does it?

It seems you are using "ActivePython" from ActiveState, did you install Requests with `pip` or is that the Requests that comes builtin with your Python distribution ?

ActiveState doesn't bundle Requests, so I used pip. I ran `pip install -U requests` before creating the ticket for this issue.

@bkline Ok so some thoughts that leap out to me looking at this capture.

Firstly, the server is responding with `Connection: keep-alive` and then sending a TCP RST immediately after. And I mean *immediately*, it arrives within 1µs of the 301 response. The packet is a RST because it doesn't even wait for the ACK response. That's really not how that's supposed to go: the webserver should really be sending `Connection: close` if it's planning to slam the door in our face like that. But nevermind, that's not where the problem is.

It also redirects you to a new host: cam.cancer.gov, on port 443. That is the actual host that is causing you trouble, I suspect, so it'd be helpful to get a capture that includes both so we can see where in the redirect the problem occurs.

So the question becomes, what exactly is going on here? My *suspicion* is that there is a problem in the TLS handshake, but without the packet capture it'll be hard to see. Mind giving that a shot?

I'm willing to go down any path to track this down, but if I tell you that the exception is thrown even if I set allow_redirects to False, would you still think additional captures will be useful (or even exist)?

I have started trying to debug on my end, and I have followed the trail into the first call of `self._sock.recv()` in `socket._fileobject.readline()` and that call never returns (that is, the exception is thrown inside the `recv()` function. I'm trying to figure out how to compile my own DLL for the `_socket` module, but I'm pretty sure we're not getting to the redirect, which would mean that cam.cancer.gov:443 wouldn't be part of the story here. What do you think?

Oh, well, that's even weirder.

Ok, new theory. I *suspect* that this is actually an artefact of the way Python handles socket timeouts and RST packets. Specifically, I *suspect* (but cannot prove) that the RST packet on non-blocking sockets (which is what a timeout is) causes the recv call to receive an error. Presumably the blocking socket either returns the read data as-is (thereby causing it to raise on the next call to `recv`), or it swallows the error entirely at treats it as a clean EOF (which would be bad).

Interestingly, this is *also* probably a Windows artefact: I'm on a Mac and haven't been able to reproduce it, probably because of the different way the Mac socket library behaves to the Windows socket library.

Out of interest, can you see if this reproduces on Python 3?

Same failure on Python 3 (btw, with Python 3 ActiveState bundles in Requests, unlike with 2.7):

```
C:\tmp>\python36\python.exe
ActivePython 3.6.0.3600 (ActiveState Software Inc.) based on
Python 3.6.0 (default, Jan 23 2017, 20:01:14) [MSC v.1900 64 bit (AMD64)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> try:
...     requests.get("http://www.cancer.gov/cam", timeout=5)
... except Exception as e:
...     print(e)
...
('Connection aborted.', ConnectionResetError(10054, 'An existing connection was forcibly closed by the remote host', None, 10054, None))
>>>
```

As an aside, I had been hoping that upgrading to 2.7.13 (which we haven't been able to do on our servers just yet) might fix the problem. When I saw that it still persists with 3.6, there wasn't much left of those hopes, but I tried anyway. No joy (but I did see that ActiveState includes Requests with 2.7.13).

If anyone has any tips on building a custom `_socket.pyd`, please pass them on.

I gave up on building a custom _socket.pyd and plugging it into ActiveState's Python. There's probably a way to get it to work, but I couldn't figure it out. As soon as I dropped in my own _socket.pyd my import of the requests module failed, with an error message complaining that there was "[n]o module named urllib3." I assume some sort of manifest is being embedded in the DLL by the compiler, and used by the interpreter to figure out where other modules are.

So I've built my own Python interpreter from source code, which should let me insert some trace logging into the _socket module. I can report that the freshly built Python (with Requests added via pip) reproduces the bug.

Here's the relevant output from my debugging efforts inside the _socket DLL:

```
return from internal_select_ex() is 0
calling recv(s->sock_fd=564, len=8192, flags=0)
recv() returned -1; windows error is 10054
```

10054 is WSAECONNRESET; Microsoft's documentation for this error says:

> Connection reset by peer.
> An existing connection was forcibly closed by the remote host. This normally results if the peer application on the remote host is suddenly stopped, the host is rebooted, the host or remote network interface is disabled, or the remote host uses a hard close (see setsockopt for more information on the SO_LINGER option on the remote socket). This error may also result if a connection was broken due to keep-alive activity detecting a failure while one or more operations are in progress. Operations that were in progress fail with WSAENETRESET. Subsequent operations fail with WSAECONNRESET.

But you knew that. :-)

So Python never sees a byte of the 301 response. I'm open to trying anything you have to suggest, but I've sort of wandered out beyond my depth here. :-)

I wonder if something happens with the socket when we call `settimeout`. I wonder if this would happen only with a read timeout (for example).

`requests.get("http://www.cancer.gov/cam", timeout=(5, None))` successfully retrieves the 404 response.

`requests.get("http://www.cancer.gov/cam", timeout=(None, 5))` blows up.

Yeah, so to be clear, I now believe pretty strongly that this is a Windows-platform-specific problem with the way Python handles ECONNRESET on Windows for non-blocking sockets. This makes it very difficult for us to work around it: that error is almost certainly masking some actual data so we can't find it.

It should be possible to reproduce this with socket level code. E.g.:

```python
import socket

request = (
    'GET /cam HTTP/1.1\r\n'
    'Host: www.cancer.gov\r\n'
    '\r\n'
)

s = socket.create_connection(('www.cancer.gov', 80))
s.settimeout(5)
s.sendall(request)
data = ''
while not data.endwith(''):
    data += s.recv(8192)
```

Does that reproduce the error? If not, I have some other suggestions for possible problems at this level.


I assume you meant `data.endswith('')` in the next-to-last line. That's not an idiom I'm familiar with. At least with my copy of Python (2.7.10.12) the loop is never entered. If I eliminate the loop and just call `s.recv(8192)` directly, I get the exception.

Sorry, i mean `data.endswith('\r\n\r\n')`. That's what happens when I reply on an iPad.

I'm going to close this ticket, as I agree that the problem is caused in a layer underneath the Requests package, and there's nothing you can do about it. It's clearly a timing issue and (as you pointed out) platform-specific. If the server sends a packet with the 301 redirection response, followed by another packet with the RST bit set (which it's doing in this case, as you can see from the packet capture you asked me to post), the socket library always delivers the 301 response to the requests library's code on Linux or on OS X. On Windows, it delivers the 301 response to the request library's call **_unless the packet with the RST bit has already arrived_**, in which case the exception is raised. The reason the call succeeded without the timeout, but failed with the timeout was that the millisecond or so that it took to apply the timeout to the read provided enough time for the packet with the RST bit set to arrive. I'm going to guess that the reason you couldn't reproduce the problem on Windows was that your machine was faster than the one we are working with. You should be able to see the exception, even without setting a timeout, by running the create_connection(), sendall(), and recv() calls by hand in the interpreter. No matter how fast you type, there will be enough time for the RST packet to get there. But again, it will only fail on Windows. I've filed a report for the socket library with the Python team (https://github.com/python/cpython/issues/73853).

Thanks for following up here @bkline! Good luck with the bug report: I hope your problem gets resolved soon! :sparkles: