python/cpython · error · RuntimeError

asynchronous generator ignored GeneratorExit

Error message

asynchronous generator ignored GeneratorExit

What it means

Raised by collections.abc.AsyncGenerator.aclose() (Lib/_collections_abc.py) when athrow(GeneratorExit) does not terminate the asynchronous generator with GeneratorExit or StopAsyncIteration. An async generator must not yield another value after receiving GeneratorExit; doing so is a protocol violation reported as this RuntimeError.

Source

Thrown at Lib/_collections_abc.py:269

        Return next yielded value or raise StopAsyncIteration.
        """
        if val is None:
            if tb is None:
                raise typ
            val = typ()
        if tb is not None:
            val = val.with_traceback(tb)
        raise val

    async def aclose(self):
        """Raise GeneratorExit inside coroutine.
        """
        try:
            await self.athrow(GeneratorExit)
        except (GeneratorExit, StopAsyncIteration):
            pass
        else:
            raise RuntimeError("asynchronous generator ignored GeneratorExit")

    @classmethod
    def __subclasshook__(cls, C):
        if cls is AsyncGenerator:
            return _check_methods(C, '__aiter__', '__anext__',
                                  'asend', 'athrow', 'aclose')
        return NotImplemented


AsyncGenerator.register(async_generator)


class Iterable(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __iter__(self):

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Remove every yield/asend suspension from the GeneratorExit/finally path; perform cleanup with plain awaits (asyncio.sleep-free) only.
  2. Re-raise GeneratorExit after cleanup in the except block instead of falling through to more yields.
  3. Replace 'except BaseException' with specific exception types so GeneratorExit is not swallowed.
  4. Restructure so the generator signals cleanup completion via a queue/callback instead of yielding during teardown.

Example fix

# before
async def agen():
    try:
        while True:
            yield await produce()
    finally:
        yield 'closed'  # RuntimeError: asynchronous generator ignored GeneratorExit

# after
async def agen():
    try:
        while True:
            yield await produce()
    finally:
        await flush()  # await only, never yield
Defensive patterns

Strategy: try-catch

Try / catch

try:
    await agen.aclose()
except RuntimeError as e:
    if 'asynchronous generator ignored GeneratorExit' in str(e):
        log.warning('async generator %r yielded during aclose', agen)
    else:
        raise

Prevention

When it happens

Trigger: An async generator's except/finally path catches GeneratorExit (or BaseException) and executes another 'yield' before finishing; calling aclose() explicitly, letting asyncio's shutdown_asyncgens hook close it, or 'async for' early exit followed by aclose() triggers the path.

Common situations: Cleanup code in an async generator's finally block that awaits-and-yields (e.g. yielding progress updates during shutdown); overly broad exception handlers inside async generators; asyncio apps seeing the error during loop.close() when async generators are finalized.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/dd21736f0b3c567e. Report an issue: GitHub.