Catch asyncio.CancelledError and raise GeneratorExit in aiter functions
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 65/100
Research direction
Start in httpx/_client.py by reading the aiter_raw and aiter_bytes functions and how their async streams are closed. Reproduce cancellation with generator.close() or asyncio.gather(..., return_exceptions=True), then verify that cancellation produces GeneratorExit while response cleanup still occurs.
Written by the indexing model from the issue text.
Description
Originally opened by
@gjmhmm8on 2023-11-24 18:32:59 in encode/httpx
Hello, I am using httpx to make asynchronous requests in a generator function. However, I encountered a problem when I tried to cancel the generator using generator.close() or asyncio.gather(*tasks, return_exceptions=True). The generator did not exit gracefully and raised an asyncio.CancelledError instead of a GeneratorExit. This caused some unwanted side effects and made it difficult to handle the cancellation properly.
I looked into the source code of httpx and found that the problem was in the aiter_raw and aiter_bytes functions in the httpx/_client.py module. These functions use async for to iterate over the response stream, but they do not catch the asyncio.CancelledError that may be raised when the stream is cancelled. According to the [documentation] of asyncio.CancelledError, this exception should be caught and either re-raised or suppressed. In this case, I think it would make sense to re-raise it as a GeneratorExit, which is the expected exception for generator termination.
I suggest adding a try-except block around the async for loop in the aiter_raw and aiter_bytes functions, like this:
async def aiter_raw(
self, chunk_size: typing.Optional[int] = None
) -> typing.AsyncIterator[bytes]:
"""
A byte-iterator over the raw response content.
"""
if self.is_stream_consumed:
raise StreamConsumed()
if self.is_closed:
raise StreamClosed()
if not isinstance(self.stream, AsyncByteStream):
raise RuntimeError("Attempted to call an async iterator on an sync stream.")
self.is_stream_consumed = True
self._num_bytes_downloaded = 0
chunker = ByteChunker(chunk_size=chunk_size)
try:
with request_context(request=self._request):
async for raw_stream_bytes in self.stream:
self._num_bytes_downloaded += len(raw_stream_bytes)
for chunk in chunker.decode(raw_stream_bytes):
await asyncio.sleep(0)
yield chunk
for chunk in chunker.flush():
await asyncio.sleep(0)
yield chunk
except asyncio.CancelledError:
raise GeneratorExit
finally:
await self.aclose()
- Dominant language
- Python
- Stars
- 1.6k
- Forks
- 103
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 25
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from pydantic/httpx2
-
Please revert aiter_bytes signature to AsyncIteratorMay be free again A pull request for this issue was closed without being merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
pydantic/httpx2#1158 · 2 comments ·
Maintainers usually reply within 1 day
-
Export AutoBackendMay be free again A pull request for this issue was closed without being merged. Open
Difficulty 1/5 Under an hour Newbie friendliness 92/100
pydantic/httpx2#1131 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
Mention header name error message for invalid header encodingsMay be free again A pull request for this issue was closed without being merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
pydantic/httpx2#864 · 3 comments ·
Maintainers usually reply within 1 day
-
Support DigestAuth with "x-www-authenticate" in first response headerPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
Similar issues
-
Email validation accepts a trailing newlinePossibly taken @marcello-pichini claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
dbt-labs/spark-utils#51 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
bug frontend
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
vendrabuck/HERD#1419 ·
Maintainers usually reply within 1 day