Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Disclaimer: this PR is dense, and I'm not expecting it to be merged. If the idea seems sound, I'll cut it into 3 pieces (ASGI dispatcher, HTTP/1.1 dispatcher, HTTP/2 dispatcher) that can be reviewed and merged independently.
Currently, the way we send the request and receive the response, in both HTTP/1.1 and HTTP/2 cases, is:
Right now, 1) is achieved via a custom
BackgroundManager
API. This works, but it has its own quirks. In particular, we can't always adhere to the context-managed usage, meaning that we sometimes have to manually deal with.__aenter__()
and__aexit__()
(e.g. in the ASGI dispatcher), which is fiddly and bug-prone. The context manager implementation also makes propagation of exceptions difficult (currently I'm not sure we get that aspect right).@tomchristie, I think we mentioned several times in the past that the current situation is not ideal. urllib3-async takes a different approach which this PR takes inspiration from (cc @pquentin).
This PR proposes to split the background manager API into several more narrowly-scoped primitives that address the two actual use cases we use
BackgroundManager
for:Point 1) is solved by two new APIs. First, a writer/reader pair:
The construction of these coroutines is provided by a single
.build_writer_reader_pair()
implementation in the base backend, which builds on top of.write()
and.read()
.Then we have a
.run_concurrently()
method (implemented by each backend) that runs those coroutines in parallel and waits for them to terminate.We can then use this API to send the request (headers and body) and receive the response headers. This requires a bit of rejigging to the h11 and h2 dispatchers, as the corresponding methods must be turned into (async) iterators, whose yielded chunks of data are fed into the writer/reader pair. (Hopefully the code is more clear than I can be in prose.)
Finally, to solve 2), a new
.start_in_background()
method is introduced. It returns an opaque callback that we can call to properly terminate the coroutine.Still to do:
AsyncExitStack
backport on 3.6. (Require users to install it, because it's only used to make the trio implementation more robust.)I'll admit it's not immediately obvious why this is an interesting change, as the resulting code is not the most natural. So happy to read feedback on this!