-
-
Notifications
You must be signed in to change notification settings - Fork 857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Detect EOF signaling remote server closed connection #143
Detect EOF signaling remote server closed connection #143
Conversation
Raise ConnectionClosedByRemote and handle on `send`
@@ -108,6 +108,9 @@ def __init__( | |||
|
|||
return data | |||
|
|||
def is_connection_dropped(self) -> bool: | |||
return self.stream_reader.at_eof() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should clarify this seemingly simplistic implementation. The StreamReaderProtocol.connection_lost
callback calls stream_reader.feed_eof
which in turn sets the EOF flag which at_eof
returns.
httpx/dispatch/http11.py
Outdated
@@ -117,6 +117,8 @@ def __init__( | |||
Send a single `h11` event to the network, waiting for the data to | |||
drain before returning. | |||
""" | |||
if self.reader.is_connection_dropped(): | |||
raise NotConnected |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking the connection status here also prevents us from sending the request event which, as @tomchristie mentioned, makes things ambiguous for non-idempotent requests.
response = await http.request("GET", "http://127.0.0.1:8000/") | ||
await server.shutdown() # shutdown the server to close the connection before receiving the data | ||
|
||
with pytest.raises(httpx.exceptions.NotConnected): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is NotConnected
the right exception here? Maybe we should wrap it into a more descriptive Exception and error message?
Okay - this is looking great! Nice work. Now, rather than using |
That sounds good for the two-request scenario, but wouldn't we still need an exception to cover to the last test case where the connection is lost before the response body is read? |
Not exactly. We’ll remove We will want to make sure we’ve got well defined exception cases for those events, but that doesn’t need to be part of this PR since it’s a different issue from the “keep alive connection had expired” case. |
Yeah, the right logic is: only put idle connections in the pool immediately after a successful call to It seems like y'all are still spending a lot of time reinventing stuff here :-/. Do you have an abstraction over backends yet, or is everything still using asyncio only? |
Btw @njsmith you meant "is NOT readable" for your close and disregard condition? |
No, I mean readable. Remember, we think the connection is supposed to be idle, so there shouldn't be any data to read. If it's marked readable, then that means the server is either sending some data in violation of the protocol, or is sending a 408 Request Timeout, or (most likely) has simply closed the connection. ( But, we shouldn't actually try to read from the connection while doing this check, because if it's not readable, then we'll block forever, and you wouldn't expect taking an idle connection out of the pool to be a blocking operation that needs a timeout. So it's a weird special-case concept. Trio sockets actually have a special method just to handle this case: python-trio/trio#760 |
Let’s try not to railroad this PR with extraneous stuff. There’s an isolated issue with the “is this connection still alive” on reaquiry of keep-alive connections, because I’d incorrectly assumed that “try writing to the socket” would be the robust way to check for that case. This PR resolves that, and now just needs a little extra rejigging to stop using an unneeded exception-based flow. (Nothing to do with h11 state or anything else like that here) Yup we have a concurrency backend interface, let’s discuss that against a separate issue. |
Right, the problem with using writing to check for connection liveness is that you can't tell whether the server started processing the request or not. So if a write fails, you have to give up and report that back to the user, and let them decide what they want to do about it. But if you can tell that the server closed the connection before you started writing, then you know it's safe to go ahead and open a new connection, so the client library can handle it transparently without getting the user involved. I believe that the right way to check for readability on different backends is:
(Does asyncio provide any way to get at the raw socket underneath a |
Closing in favor of #145 which adds a last little bit of clean up on top of @yeraydiazdiaz's great work here. |
Fixes #96
Raise ConnectionClosedByRemote and handle on
send