Safe async cancellations. by lovelydinosaur · Pull Request #880 · encode/httpcore · GitHub
Skip to content

Safe async cancellations.#880

Merged
lovelydinosaur merged 14 commits into
masterfrom
clean-state-cancellations
Feb 12, 2024
Merged

Safe async cancellations.#880
lovelydinosaur merged 14 commits into
masterfrom
clean-state-cancellations

Conversation

@lovelydinosaur

@lovelydinosaur lovelydinosaur commented Feb 6, 2024

Copy link
Copy Markdown
Contributor

Closes #830
Closes #785
Closes #861
Closes https://github.com/encode/httpx/issues/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) or aiohttp (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.

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...

<httpcore.ConnectionPool [Requests: 10 active, 3 queued | Connections: 2 active, idle]>

Comment on lines 68 to 71
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]>",
]

Copy link
Copy Markdown
Contributor Author

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.

Comment on lines 77 to 80
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]>",
]

Copy link
Copy Markdown
Contributor Author

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.

Comment on lines 228 to 230
assert info == [
"<AsyncHTTPConnection ['https://example.com:443', HTTP/2, IDLE, Request Count: 1]>",
"<AsyncHTTPConnection ['https://example.com:443', HTTP/2, CLOSED, Request Count: 1]>",
]

Copy link
Copy Markdown
Contributor Author

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.

)

async def response_closed(self, status: RequestStatus) -> None:
def _assign_requests_to_connections(self) -> List[AsyncConnectionInterface]:

Copy link
Copy Markdown
Contributor Author

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.

https://github.com/encode/httpcore/blob/f91789652485d5ec7651458bc20a9164ff56dbd6/httpcore/_async/connection_pool.py

@lovelydinosaur lovelydinosaur marked this pull request as ready for review February 7, 2024 15:01
@lovelydinosaur lovelydinosaur requested a review from a team February 7, 2024 15:14
@lovelydinosaur lovelydinosaur changed the title Connection pool work Safe async cancellations. Feb 7, 2024
@rattrayalex

rattrayalex commented Feb 9, 2024

Copy link
Copy Markdown

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!

@lovelydinosaur

Copy link
Copy Markdown
Contributor Author

Fantastic, thanks for the feedback.

@karpetrosyan

Copy link
Copy Markdown
Contributor

@karpetrosyan karpetrosyan left a comment

Copy link
Copy Markdown
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants