Repository navigation
KeyError: HTTPConnection(origin=...) on ReadTimeout in connection_pool #817
Copy link
Copy link
Closed
Labels
bugSomething isn't workingSomething isn't workingconcurrencyIssues related to concurrency and usage of async librariesIssues related to concurrency and usage of async librariespoolingIssues and PRs relative to connection poolingIssues and PRs relative to connection pooling
Description
In some cases, when httpx._dispatch.connection_pool:ConnectionPool.send fails, the call to httpx._dispatch.connection_pool:ConnectionStore.remove fails because the connection somehow is no longer in ConnectionStore.all.
This is in an http2-only context.
Python: 3.8.1
httpx: 0.11.1
ReadTimeout: null
File "httpx/dispatch/connection_pool.py", line 153, in send
response = await connection.send(request, timeout=timeout)
File "httpx/dispatch/connection.py", line 44, in send
return await self.connection.send(request, timeout=timeout)
File "httpx/dispatch/http2.py", line 68, in send
return await stream.send(request, timeout)
File "httpx/dispatch/http2.py", line 214, in send
status_code, headers = await self.receive_response(timeout)
File "httpx/dispatch/http2.py", line 271, in receive_response
event = await self.connection.wait_for_event(self.stream_id, timeout)
File "httpx/dispatch/http2.py", line 138, in wait_for_event
await self.receive_events(timeout)
File "httpx/dispatch/http2.py", line 145, in receive_events
data = await self.socket.read(self.READ_NUM_BYTES, timeout)
File "httpx/backends/asyncio.py", line 134, in read
raise ReadTimeout() from None
KeyError: HTTPConnection(origin=Origin(scheme='https' host='api.push.apple.com' port=443))
File "app.py", line 240, in func
await self.aapns_client.send_notification(
File "aapns/api.py", line 89, in send_notification
response = await self.client.post(
File "httpx/client.py", line 1305, in post
return await self.request(
File "httpx/client.py", line 1096, in request
response = await self.send(
File "httpx/client.py", line 1117, in send
response = await self.send_handling_redirects(
File "httpx/client.py", line 1147, in send_handling_redirects
response = await self.send_handling_auth(
File "httpx/client.py", line 1184, in send_handling_auth
response = await self.send_single_request(request, timeout)
File "httpx/client.py", line 1208, in send_single_request
response = await dispatcher.send(request, timeout=timeout)
File "httpx/dispatch/connection_pool.py", line 155, in send
self.active_connections.remove(connection)
File "httpx/dispatch/connection_pool.py", line 72, in remove
del self.all[connection]
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingconcurrencyIssues related to concurrency and usage of async librariesIssues related to concurrency and usage of async librariespoolingIssues and PRs relative to connection poolingIssues and PRs relative to connection pooling
Activity
ojii commentedon Feb 19, 2020
I've made a repo that can usually reproduced the issue: https://github.com/HENNGE/httpx-h2-testing
When the client tries to send ~1000 requests the KeyError usually happens.
lovelydinosaur commentedon Feb 19, 2020
Okay, thanks for the report.
I've been taking a refresh onto the connection pool handling in httpcore... https://github.com/encode/httpcore/blob/master/httpcore/_async/connection_pool.py
Might be that I need to prioritize working on that again, in order to get this, #283, and #514 resolved.
ojii commentedon Feb 20, 2020
The example I made indeed also shows #514. I'd expect it to use a single connection (or maybe 1 connection per 100 requests since that's the stream limit). Also with different parameters (eg issue 10k requests) it also exhausts the per-connection stream limit. In our real-world scenario, we don't currently hit the second issue, but the first two (KeyError and lots of connections) do happen and we're also seeing some sort of memory leak (but haven't been able to track this down to httpx or our app yet).
Let me know if there's more information you need or if there are other ways I can help.
dimaqq commentedon Feb 28, 2020
I've been poking at @ojii 's test and I think (not fully sure yet) that
KeyErroronly happens in a very peculiar circumstance:await gather(all those requests)propagates the exceptionasyncio.runfinishesasyncioattempts to clean up outstanding tasks (via cancellation if I get it right)KeyErroris hitIf that's correct, then the problem is in cancellation and not on the happy path.
dimaqq commentedon Feb 28, 2020
Having instrumented connection pool / connection store:
I get log:
This is the traceback when
.clear()was is called:So, basically, the
async withstatement finishes before individual requests.dimaqq commentedon Feb 28, 2020
Work-around: https://github.com/dimaqq/httpx/blob/91ed44abe4f88ce32cc9e82a4d230c5ad5a5c10b/tests/stress/test_cancellation.py#L26-L33
florimondmanca commentedon Mar 13, 2020
Hi here, the
httpcore-based implementation has come along nicely now; has anyone tried to reproduce this against an HTTPX installation pinned on #804? Is it still an issue on that branch?lovelydinosaur commentedon Apr 30, 2020
I'm going to close this on the assumption that its resolved in the 0.13.dev0 pre-release, given that we've totally refreshed the pool handling, and it's a much more careful implementation.