Bug report
Bug description:
asyncio.BaseEventLoop.shutdown_asyncgens silently swallows CancelledError raised during asynchronous generator cleanup:
# Lib/asyncio/base_events.py:592-599 for result, agen in zip(results, closing_agens): if isinstance(result, Exception): self.call_exception_handler({ 'message': f'an error occurred during closing of ' f'asynchronous generator {agen!r}', 'exception': result, 'asyncgen': agen })
CancelledError that can be raised by the aclose() is not inherited from the Exception, it inherits from BaseException.
Reproducer:
import asyncio async def agen(): try: yield 1 finally: raise asyncio.CancelledError("cancelled during cleanup") caught = [] async def main(): loop = asyncio.get_running_loop() loop.set_exception_handler(lambda loop, ctx: caught.append(ctx)) g = agen() await g.__anext__() await loop.shutdown_asyncgens() asyncio.run(main()) assert len(caught) == 1, f"Expected 1 handler call, got {len(caught)}"
Proposed fix:
Match both Exception and CancelledError
if isinstance(result, (Exception, asyncio.CancelledError)):
CPython versions tested on:
3.16, CPython main branch
Operating systems tested on:
macOS