httpx: h11._util.RemoteProtocolError: can't handle event type ConnectionClosed when role=SERVER and state=SEND_RESPONSE
I intermittently got this error when load testing uvicorn endpoint.
This error comes from a proxy endpoint where I am also using encode/http3 to perform HTTP client calls.
File "/project/venv/lib/python3.7/site-packages/http3/client.py", line 365, in post
timeout=timeout,
File "/project/venv/lib/python3.7/site-packages/http3/client.py", line 497, in request
timeout=timeout,
File "/project/venv/lib/python3.7/site-packages/http3/client.py", line 112, in send
allow_redirects=allow_redirects,
File "/project/venv/lib/python3.7/site-packages/http3/client.py", line 145, in send_handling_redirects
request, verify=verify, cert=cert, timeout=timeout
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/connection_pool.py", line 121, in send
raise exc
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/connection_pool.py", line 116, in send
request, verify=verify, cert=cert, timeout=timeout
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/connection.py", line 59, in send
response = await self.h11_connection.send(request, timeout=timeout)
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/http11.py", line 65, in send
event = await self._receive_event(timeout)
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/http11.py", line 109, in _receive_event
event = self.h11_state.next_event()
File "/project/venv/lib/python3.7/site-packages/h11/_connection.py", line 439, in next_event
exc._reraise_as_remote_protocol_error()
File "/project/venv/lib/python3.7/site-packages/h11/_util.py", line 72, in _reraise_as_remote_protocol_error
raise self
File "/project/venv/lib/python3.7/site-packages/h11/_connection.py", line 422, in next_event
self._process_event(self.their_role, event)
File "/project/venv/lib/python3.7/site-packages/h11/_connection.py", line 238, in _process_event
self._cstate.process_event(role, type(event), server_switch_event)
File "/project/venv/lib/python3.7/site-packages/h11/_state.py", line 238, in process_event
self._fire_event_triggered_transitions(role, event_type)
File "/project/venv/lib/python3.7/site-packages/h11/_state.py", line 253, in _fire_event_triggered_transitions
.format(event_type.__name__, role, self.states[role]))
h11._util.RemoteProtocolError: can't handle event type ConnectionClosed when role=SERVER and state=SEND_RESPONSE
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 109 (51 by maintainers)
Commits related to this issue
- handle h11.Connection.next_event RemoteProtocolException raise (new) ConnectionClosed exception if this occurs when socket is closed, otherwise reuse ProtocolError exception. Add test case for Conne... — committed to toppk/httpx by toppk 5 years ago
- Handle h11.Connection.next_event() RemoteProtocolException raise (new) ConnectionClosed exception if this occurs when socket is closed, otherwise reuse ProtocolError exception. Add test case for Con... — committed to toppk/httpx by toppk 5 years ago
- Handle h11.Connection.next_event() RemoteProtocolException raise (new) ConnectionClosed exception if this occurs when socket is closed, otherwise reuse ProtocolError exception. Add test case for Con... — committed to toppk/httpx by toppk 5 years ago
- Handle h11.Connection.next_event() RemoteProtocolError (#524) raise (new) ConnectionClosed exception if this occurs when socket is closed, otherwise reuse ProtocolError exception. Add test case f... — committed to encode/httpx by toppk 5 years ago
- Setup Neo4j via Bolt interface This is a workaround for https://github.com/encode/httpx/issues/96 — committed to NCATS-Gamma/robokop-messenger by deleted user 4 years ago
- try out hypercorn as it seems to be less flaky for StreamingResponse https://github.com/encode/httpx/issues/96 — committed to alramalho/ask-paper-ai by alramalho a year ago
There are two web servers.
testapp.pytestapp1.pyThe reproduce steps:
http://127.0.0.1:8000/in browserThis happens, not every time. The logs again:
Sorry for my poor English.
After upgrading from httpx 0.18.2 to 0.21.1 we also suffer from this problem again. As a workaround we pinned httpx to the old version where everything seems to work fine.
fyi @marns93
I don’t think there are any actual behavioral bugs being discussed in this thread. It’s just about how to give a better error message. So I think either there’s something wrong with your server setup that’s causing it to close connections abruptly without sending responses, or else you’ve found a different bug in httpx and should probably open a new issue so your problem doesn’t get lost in the noise.
I can reproduce it. I’ve narrowed it down to this “caller” script:
It is not reproducible against a simple Flask WSGI server:
The script outputs:
But it fails with ASGI servers, interestingly it fails for both FastAPI+Uvicorn and Quart+Hypercorn:
The Quart code:
For both implementations the script’s output is:
@vyahello Yes, this fix along with some others will be released soon, probably as 0.7.9. 😃 If you need them now you can install from master.
Here’s a simple reproducible example. It uses two separate files and requires
fastapiandhttpx.FastAPI Server:
Client:
In one terminal run the server (I’m using
gunicornwith auvicornworker since that’s how I normally run my FastAPI applications).In a second terminal, launch the test (I just did
./test.py). Let it run for a second and then kill the server with aCTRL-Cin the first terminal. The test client, in the second terminal, should die with the expectedh11._util.RemoteProtocolErrorexception. In a quick test (~5 tries) I got the expected error 4 times and aConnectionRefusederror once, which kind of makes sense since it appears to be a race condition of whether or not the server finishes sending the response before it dies.For what it’s worth I’m running on macOS (Mojave) with Homebrew Python3.7 in a virtual environment with the latest
httpxandfastapi.Relevant snippet from the h11 docs:
I could reproduce with this snippet:
the ConnectionClosed seems to be generated only in this place in h11:
and _receive_buffer_closed seems to be closed only in
meaning it was called with an empty
datain turn, this comes from httpcore:
so
_network_stream.readreturned empty dataas I’m using asyncio, it’s an
AsyncIOStreamwith this code:so I guess we reached EndOfStream on the client side then emitted the transition (their_role=SERVER, state=SEND_RESPONSE, event_type=ConnectionClosed) which is not managed.
When programming on the server side, it means client reached ConnectionClosed while the server is the sending the response/client is receiving, I think the bug does not happen since the server would not be calling receive_data while sending the response.
On the client side, it means the server reached ConnectionClosed while it is actually sending/while the client is receiving, and it trigger this bug.
Ok, nailed it: https://github.com/encode/starlette/issues/919
This exception could be seen if your web server closes a tcp connection prematurely (being overloaded could certainly cause this), or if a server dies, although it could just be a bug in the server code.
When using httpx you must capture certain exceptions when legitimate failuremodes occur (like readtimeouts, connect failures, etc).
This code would look something like this.
Unfortunately, seeing the stacktrace is because httpx is leaking an exception type from a dependent package (h11). For the moment, you must also do something like this:
You can then decide how to handle the failure mode (close connection, retry, warn user, etc.).
Thanks @iwoloschin, that’s very helpful. I was able to reproduce using the even simpler setup below (plain ASGI app + uvicorn):
I received the
h11error 3 times out of 5, the 2 other failures were anOSError. I’m running the same setup as you (except for Python 3.7 viapyenv).Another piece of info: I get the same result whether I use the top-level API level, a
Client, or anAsyncClient.Agreed, yup.
Closed via #145 Released to PyPI as 0.6.8
Okay, so I think the right approach would be for us to have an equivelent
is_connection_droppedon theReaderclass in https://github.com/encode/httpx/blob/master/httpx/concurrency.py replacing the existing logic of “suck it and see”.Does that make sense to you too, @yeraydiazdiaz? #143 is a great start - nice test case for the issue that can still be used here.
(Actually I think we’ll also want to combine the
ReaderandWriterinterfaces into a singleSocketConnection, but that’s a different story.)I think this is caused by the client code in
http3.