Skip to content
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

Improve handling of server disconnects #112

Merged
merged 4 commits into from
Jul 15, 2020
Merged

Conversation

florimondmanca
Copy link
Member

Fixes #110, cc @nawarnoori

Now, the test setup detailed in #110 (comment) results in the following error in all cases:

httpcore._exceptions.ReadError: Server disconnected while attempting read

@@ -54,12 +54,15 @@ def start_tls(

def read(self, n: int, timeout: TimeoutDict) -> bytes:
read_timeout = timeout.get("read")
exc_map = {socket.timeout: ReadTimeout, socket.error: ReadError}
exc_map = {socket.timeout: ReadTimeout, OSError: ReadError}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

socket.error is a synonym for OSError in Python 3.

Copy link

@nawarnoori nawarnoori left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, one minor comment, do you guys add tests? I suspect you'd have to spin up something to test properly against it.

raise OSError(message) from None

if data == b"":
raise OSError("Server disconnected while attempting read")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very minor: you could reuse message here.

@florimondmanca
Copy link
Member Author

do you guys add tests?

Yes, ideally we'd have a test for this. I did try something - spinning up a Uvicorn server and restart it while we're reading from the stream (which should trigger the error case). It feels pretty heavy just for this particular edge case. But I don't know how we'd go with mocking either, so…

@nawarnoori
Copy link

Are you happy to merge this? The fix looks good, even without tests it's better than not being in.

@florimondmanca
Copy link
Member Author

I'm tempted to be okay with merging this in to issue a bug fix release too, but I'd like to hear thoughts from @encode/maintainers about this. :-)

@florimondmanca florimondmanca requested a review from a team July 9, 2020 09:43
Copy link
Member

@JayH5 JayH5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. My only question is why OSError was chosen? Seems unusual for that to be raised by something that's not stdlib, but I don't really know a better error type (EOFError? but that's also quite stdlib-specific).

[...] spinning up a Uvicorn server and restart it while we're reading from the stream (which should trigger the error case).

I think one other case for HTTP/1 where this may occur (if I'm understanding it right) is if the server disconnects due to a keepalive timeout.

httpcore/_backends/trio.py Outdated Show resolved Hide resolved
@florimondmanca
Copy link
Member Author

@JayH5 There's ConnectionAbortedError (a subclass of OSError) - I was hesitant to use it because we're mapping from OSError (so ReadError is raised in all cases) and it's not immediate knowledge that ConnectionAbortedError will be raised too.

So hmm, actually I think I could just raise an httpcore.ReadError directly. :-)

@nawarnoori
Copy link

Hi chaps, is there something I could do to help this along? I've patched the bug locally with a temporary hack but would really benefit from having this in the library.

@florimondmanca
Copy link
Member Author

@nawarnoori I just need to make a new pass on this to raise a ReadError exceptions directly instead of OSError, and then this should be good for a final review… Unfortunately I don't think outside contributors can push to this branch, but if you'd like to speed things up feel free to bundle these ideas into a new PR.

@nawarnoori
Copy link

@florimondmanca I'm getting 403 on either pushing to this branch or pushing a new branch, but I've formatted the commit as a patch that I've attached here (had to add .txt to get github to upload it). It's very small, I've removed some exception mappings that I thought were unnecessary (since I couldn't see what would raise those exceptions) and just raise ReadError directly.

0001-Just-raise-ReadError-directly.patch.txt

Copy link
Member

@JayH5 JayH5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to test but that seems difficult + the fix seems important.

@florimondmanca
Copy link
Member Author

Would be nice to test

For the record, to test this as part of the test suite we would need these:

  • Spin up an HTTP/1.1 server locally (the bug was also reproducible on HTTP/2 but it's not an absolute requirement - it also occurred on HTTP/1.1).
  • Programmatically close the client connection on the server during the test (eg restart the server).
  • Synchronize all of this (eg server in one thread vs tests in the main thread).

We have / used to have something similar in HTTPX for connection pooling tests, but since at this point the test suite is very minimal (integration tests against a live website), we'd need some more work to be able to automatically test this properly.

Any case, I did some manual testing and my claim in the PR description holds: we get a proper ReadError in all cases instead of undefined behavior (eg loop forever on sock.recv()).

Shall we :shipit:?

Copy link
Member

@tomchristie tomchristie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I'm good with this.

@florimondmanca florimondmanca merged commit 44d4413 into master Jul 15, 2020
@florimondmanca florimondmanca deleted the client-disconnects branch July 15, 2020 09:43
@florimondmanca
Copy link
Member Author

Thanks all! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants