-
Notifications
You must be signed in to change notification settings - Fork 105
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
Safe async cancellations. #880
Conversation
assert info == [ | ||
"<AsyncHTTPConnection ['http://example.com:80', HTTP/1.1, ACTIVE, Request Count: 1]>", | ||
"<AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 2]>", | ||
"<AsyncHTTPConnection ['http://example.com:80', HTTP/1.1, ACTIVE, Request Count: 1]>", | ||
] |
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.
The ordering of connections changes slightly here.
We're now working with a policy of... connections remain in the order they were created.
assert info == [ | ||
"<AsyncHTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>", | ||
"<AsyncHTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 2]>", | ||
"<AsyncHTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>", | ||
] |
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.
Again the ordering is slightly different from previous behaviour.
I would suggest this is more obvious.
assert info == [ | ||
"<AsyncHTTPConnection ['https://example.com:443', HTTP/2, IDLE, Request Count: 1]>", | ||
"<AsyncHTTPConnection ['https://example.com:443', HTTP/2, CLOSED, Request Count: 1]>", | ||
] |
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.
There is a marginal behavior change here.
We've (correctly) removed the closed connection from the pool.
extensions=response.extensions, | ||
) | ||
|
||
async def response_closed(self, status: RequestStatus) -> None: | ||
def _assign_requests_to_connections(self) -> List[AsyncConnectionInterface]: |
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.
This is where we mange the state of the connection pool, entirely within a non-I/O block. The async
case cannot have cancellations or context-switches midway through the state management. The sync
case is explicitly guarded with a thread lock.
Review is probably best made against the module as a whole, rather than the diff.
FYI, a user reports that this branch does indeed fix issues they saw in the OpenAI Python client library: openai/openai-python#1059 (comment) Thank you for the work on this so far! |
Fantastic, thanks for the feedback. |
Amazing work @tomchristie, this PR rsolves many problems.. |
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.
I guess we also need a changelog here
Closes #830
Closes #785
Closes #861
Closes encode/httpx#1171
Let's talk about what's going on here.
We've got an issue with handling async cancellations correctly, which needs some re-working in order to comprehensively resolve it. We're somewhat in contrast to either
urllib3
(sync) oraiohttp
(async) here, because we're having to get both the thread-safe and the task-safe-plus-also-support-cancellations cases correct.Currently we're at thread-safe plus task-safe, but missing correct handling of also allowing external cancellations at any point.
So then...
This pull request reworks the handling of the connection pool state. The state is a list of connections, a list of requests, and an association between each request and the connection that has been selected to handle it.
The fundamental change in this pull request is that the management of the pool state has been re-worked so that it does not include I/O.
httpcore/httpcore/_async/connection_pool.py
Lines 228 to 299 in cda040d
This ensures that updating the state of the connection pool is always atomic.
As part of resolving this, also adds connection pool
__repr__
to demonstate the state more clearly.The look like so...