You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Write Error on reusing potentially closed connection #44
With a service deployed with httpx version with httpcore interface, I started receiving the following error:
<class 'httpcore._exceptions.WriteError'>: [Errno 104] Connection reset by peer
The only problem is that this is when connecting to a service that others applications have no problems. The connection is (should be) HTTP/1.1 in this case.
I'm going to close this one off given the ancedotal encode/httpx#841 (comment) "I just tested the original gist against httpx==0.13.0.dev2 and got the same result for http1.1 and http2."
If there's something reproducible here or any anecdotal reports of similar connection closing then we can reopen it, but given that it's just "the server closed the connection" there's not a lot here for us to act on.
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1364, in post
return await self.request(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1137, in request
response = await self.send(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1158, in send
response = await self.send_handling_redirects(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1185, in send_handling_redirects
response = await self.send_handling_auth(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1222, in send_handling_auth
response = await self.send_single_request(request, timeout)
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1254, in send_single_request
) = await transport.request(
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/connection_pool.py", line 152, in request
response = await connection.request(
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/connection.py", line 78, in request
return await self.connection.request(method, url, headers, stream, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http11.py", line 55, in request
await self._send_request(method, url, headers, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http11.py", line 82, in _send_request
await self._send_event(event, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http11.py", line 106, in _send_event
await self.socket.write(bytes_to_send, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_backends/asyncio.py", line 146, in write
return await asyncio.wait_for(
File "/usr/local/lib/python3.8/contextlib.py", line 131, in __exit__
self.gen.throw(type, value, traceback)
File "/usr/local/lib/python3.8/site-packages/httpcore/_exceptions.py", line 12, in map_exceptions
raise to_exc(exc) from None
httpcore._exceptions.WriteError: [Errno 104] Connection reset by peer
What it seems: after a while, the connection closes, but httpcore does not now that, attempts to write the request and throws an error.
Does httpx 0.12 reuses http 1.1 connections? I'm seeing in the trace that it is always using a new connection in http 1.1, as 0.13.2 reuses a connection, which can be the cause.
Thanks for the info - Is there a minimal reproducible example we can use to dig into this?
Is it failing against any particular host, or can you reproduce more generally?
Does httpx 0.12 reuses http 1.1 connections? I'm seeing in the trace that it is always using a new connection in http 1.1, as 0.13.2 reuses a connection, which can be the cause.
httpcore does reuse HTTP/1.1 connections if a previous request has been closed (the connection's state is IDLE at that point). In your trace I can see a reuse_connection entry, which suggests it an idle connection was found and it tried to reuse it.
What it seems: after a while, the connection closes, but httpcore does not now that, attempts to write the request and throws an error.
There's a specific check in the connection pool for closed connections, I remember @florimondmanca working on the asyncio version and discussing it with core developers so I'd be surprised if that's not working as expected but of course you never know.
It'd be also interesting to know how many concurrent requests are you attempting when the error occurs.
We could also do with more trace logging on the _get_connection_from_pool to help us debug these situations.
Is it failing against any particular host, or can you reproduce more generally?
In my case was a specific host because it was my only test. I will try to update my minimal case repo to reflex this issue!
It'd be also interesting to know how many concurrent requests are you attempting when the error occurs.
Only 1, local server with 1 request at a time using Postman. My tests were 1 request everything works. 5 minutes later I do another request and it fails when reusing the connection.
Okay needs looking into then. The connection ought to have been expired, as we have a keep-alive timeout, and close connections once they pass that mark.
Could the host in question be closing the connection before the returned keep alive duration, causing this issue?
I ask this because the host is a FastAPI server, running with uvicorn worker o gunicorn, with keep alive settings = 120. After exactly 121 seconds I receive the described error.
I changed the setting to 240 and I still receive the error after 121 seconds.
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1364, in post
return await self.request(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1137, in request
response = await self.send(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1158, in send
response = await self.send_handling_redirects(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1185, in send_handling_redirects
response = await self.send_handling_auth(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1222, in send_handling_auth
response = await self.send_single_request(request, timeout)
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1254, in send_single_request
) = await transport.request(
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/connection_pool.py", line 152, in request
response = await connection.request(
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/connection.py", line 78, in request
return await self.connection.request(method, url, headers, stream, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http2.py", line 118, in request
return await h2_stream.request(method, url, headers, stream, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http2.py", line 287, in request
await self.send_headers(method, url, headers, has_body, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http2.py", line 323, in send_headers
await self.connection.send_headers(self.stream_id, headers, end_stream, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http2.py", line 226, in send_headers
await self.socket.write(data_to_send, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_backends/asyncio.py", line 146, in write
return await asyncio.wait_for(
File "/usr/local/lib/python3.8/contextlib.py", line 131, in __exit__
self.gen.throw(type, value, traceback)
File "/usr/local/lib/python3.8/site-packages/httpcore/_exceptions.py", line 12, in map_exceptions
raise to_exc(exc) from None
httpcore._exceptions.WriteError: [Errno 104] Connection reset by peer
The default behaviour is to check if the connection has been dropped on the next request and create a new one if so, which seems to works fine on my tests.
On my Mac, using the example HTTP/1.1 Uvicorn app with TLS, a keepalive timeout of 5 seconds, the enhanced trace logs in #101 and this simple script:
This right thing for use to do first is switch those init_transport/init_proxy_transport methods to include a fixed keepalive_expiry=5.0 value, and then issue a minor point release of httpx.
However it'd also be really helpful to have another dig into the "is the connection still alive" detection, and see if there's anything we can do to improve the robustness there. What'd be helpful for that would be having a minimal reproducible example, so...
Can you reproduce the behaviour, just using uvicorn, with a basic hello world app?...
Resolved in httpx 0.13.3, but leaving this open ATM, as it would still be good to dig into making our “connection is still open” is as robust as possible.
I guess it's possible our detection of dropped connections fails on your architecture, can you detail what you are using?
My architecture is the host is a FastAPI service running behind gunicorn 20.0.4 inside a docker container in Azure App Service. The client follows the same architecture.
@tomchristie I will check your comments during the weekend and come with detailed comments after, if that's ok. I will also apply #101 locally into httpx=0.13.2 to help with the trace.
2. On the client side, is this sufficient to reproduce the behaviour, or do you need to do anything else?...
This snippet is enough to replicate, using sleep of 130 seconds.
3. Can you reproduce when using the sync client? When using the trio client?
No, only asyncio version of the snippet. I tried the 3 versions 5 times. Running against the server while running it in Azure App Service and running locally. Locally I do not have this error, but it is an http app while on Azure it is https, which can be part of the cause.
Locally I do not have this error, but it is an http app while on Azure it is https, which can be part of the cause
Ok, so running against https locally and http remotely still gives an error with the api running on Azure. So I guess the option here is to deploy a mock service on azure so I can send the url here? (verify=False gives same result, error while running against Azure)
Activity
lovelydinosaur commentedon May 13, 2020
I'm going to close this one off given the ancedotal encode/httpx#841 (comment) "I just tested the original gist against httpx==0.13.0.dev2 and got the same result for http1.1 and http2."
If there's something reproducible here or any anecdotal reports of similar connection closing then we can reopen it, but given that it's just "the server closed the connection" there's not a lot here for us to act on.
victoraugustolls commentedon May 27, 2020
I have a service that after upgrading httpx to 0.13 started receiving the same error, and rolling back to 0.12 seems to resolve the issue.
victoraugustolls commentedon May 27, 2020
I don't know what details I can give here, other that:
victoraugustolls commentedon May 27, 2020
Stack trace:
victoraugustolls commentedon May 27, 2020
TRACE:
victoraugustolls commentedon May 28, 2020
What it seems: after a while, the connection closes, but httpcore does not now that, attempts to write the request and throws an error.
Does httpx 0.12 reuses http 1.1 connections? I'm seeing in the trace that it is always using a new connection in http 1.1, as 0.13.2 reuses a connection, which can be the cause.
yeraydiazdiaz commentedon May 28, 2020
Hey @victoraugustolls, thanks for the detailed report. Reopening and temptatively renaming this issue.
[-]Write Error[/-][+]Write Error on reusing potentially closed connection[/+]lovelydinosaur commentedon May 28, 2020
Thanks for the info - Is there a minimal reproducible example we can use to dig into this?
Is it failing against any particular host, or can you reproduce more generally?
yeraydiazdiaz commentedon May 28, 2020
httpcore does reuse HTTP/1.1 connections if a previous request has been closed (the connection's state is
IDLEat that point). In your trace I can see areuse_connectionentry, which suggests it an idle connection was found and it tried to reuse it.There's a specific check in the connection pool for closed connections, I remember @florimondmanca working on the asyncio version and discussing it with core developers so I'd be surprised if that's not working as expected but of course you never know.
It'd be also interesting to know how many concurrent requests are you attempting when the error occurs.
We could also do with more trace logging on the
_get_connection_from_poolto help us debug these situations.victoraugustolls commentedon May 28, 2020
In my case was a specific host because it was my only test. I will try to update my minimal case repo to reflex this issue!
Only 1, local server with 1 request at a time using Postman. My tests were 1 request everything works. 5 minutes later I do another request and it fails when reusing the connection.
lovelydinosaur commentedon May 28, 2020
Okay needs looking into then. The connection ought to have been expired, as we have a keep-alive timeout, and close connections once they pass that mark.
victoraugustolls commentedon May 28, 2020
Could the host in question be closing the connection before the returned keep alive duration, causing this issue?
victoraugustolls commentedon May 28, 2020
I ask this because the host is a FastAPI server, running with uvicorn worker o gunicorn, with keep alive settings = 120. After exactly 121 seconds I receive the described error.
I changed the setting to 240 and I still receive the error after 121 seconds.
Response headers:
victoraugustolls commentedon May 28, 2020
So, on 0.13 if I force http/2.0 enabling it on the host, I have the same error after 121s.
The trace:
Traceback:
victoraugustolls commentedon May 28, 2020
Trace on httpx=0.12:
Wonder why does it start with closing the connection.
yeraydiazdiaz commentedon May 29, 2020
If you're talking about
keepalive_expiry, httpx does not pass it to httpcore, though it probably should.The default behaviour is to check if the connection has been dropped on the next request and create a new one if so, which seems to works fine on my tests.
On my Mac, using the example HTTP/1.1 Uvicorn app with TLS, a keepalive timeout of 5 seconds, the enhanced trace logs in #101 and this simple script:
No exceptions are raised and I get the following output:
Note how the connection is not closed after reading the body and when the next request is sent we see:
TRACE [2020-05-29 09:38:32] httpcore._async.connection_pool - removing dropped idle connection=<httpcore._async.connection.AsyncHTTPConnection object at 0x10e027970>Proving it detected the connection being closed and proceeded to create a new one.
I guess it's possible our detection of dropped connections fails on your architecture, can you detail what you are using?
lovelydinosaur commentedon May 29, 2020
Oh right sure...
asyncio's dropped connection is less robust than the standard sync case, so we introduced a fairly short keep-alive period on the httpx side, which in practise means that it's not an issue since we'll expire the connections anyways... https://github.com/encode/httpx/blob/0.12.1/httpx/_dispatch/connection_pool.py#L89
However, when we switched over to httpcore in 0.13, we switched
httpcoreitself to default the keepalive value toNonehttpcore/httpcore/_async/connection_pool.py
Line 86 in 888c163
keepalive_expiry=5.0when instantiating the connection pool / proxy classes fromhttpx... https://github.com/encode/httpx/blob/093cb4250006dc0b73af986f7e6537b8c4395dcf/httpx/_client.py#L516This right thing for use to do first is switch those
init_transport/init_proxy_transportmethods to include a fixedkeepalive_expiry=5.0value, and then issue a minor point release ofhttpx.However it'd also be really helpful to have another dig into the "is the connection still alive" detection, and see if there's anything we can do to improve the robustness there. What'd be helpful for that would be having a minimal reproducible example, so...
If not, then what extra config, or setup do you need to introduce before being able to reproduce?
Can you reproduce when using the sync client? When using the trio client?
Which platform are you running on?
lovelydinosaur commentedon May 29, 2020
Resolved in httpx 0.13.3, but leaving this open ATM, as it would still be good to dig into making our “connection is still open” is as robust as possible.
victoraugustolls commentedon May 29, 2020
My architecture is the host is a FastAPI service running behind gunicorn 20.0.4 inside a docker container in Azure App Service. The client follows the same architecture.
victoraugustolls commentedon May 29, 2020
@tomchristie I will check your comments during the weekend and come with detailed comments after, if that's ok. I will also apply #101 locally into httpx=0.13.2 to help with the trace.
victoraugustolls commentedon May 29, 2020
This snippet is enough to replicate, using sleep of 130 seconds.
No, only asyncio version of the snippet. I tried the 3 versions 5 times. Running against the server while running it in Azure App Service and running locally. Locally I do not have this error, but it is an http app while on Azure it is https, which can be part of the cause.
Locally Arch Linux (running code inside docker). Remotely Azure App Service.
victoraugustolls commentedon May 29, 2020
Ok, so running against https locally and http remotely still gives an error with the api running on Azure. So I guess the option here is to deploy a mock service on azure so I can send the url here? (verify=False gives same result, error while running against Azure)
yeraydiazdiaz commentedon May 30, 2020
I'm a bit confused here, let's see if we can summarize the situation:
Is that correct? Could you share details on the client Docker image?
lovelydinosaur commentedon Aug 10, 2020
Closing as stale.
victoraugustolls commentedon Aug 10, 2020
@tomchristie @yeraydiazdiaz I have missed the notification about you comments, I'm deeply sorry.
After encode/httpx#1005 the error stopped happening! So closing this is fine 😄