python/cpython · error · RuntimeError

coroutine ignored GeneratorExit

Error message

coroutine ignored GeneratorExit

What it means

Raised by collections.abc.Coroutine.close() (Lib/_collections_abc.py) when throwing GeneratorExit into a coroutine does not end it with GeneratorExit or StopIteration. Per PEP 342/492, a coroutine must not catch GeneratorExit and continue suspending; if it yields or returns a value while being closed, the machinery reports this RuntimeError.

Source

Thrown at Lib/_collections_abc.py:183

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

    def close(self):
        """Raise GeneratorExit inside coroutine.
        """
        try:
            self.throw(GeneratorExit)
        except (GeneratorExit, StopIteration):
            pass
        else:
            raise RuntimeError("coroutine ignored GeneratorExit")

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Coroutine:
            return _check_methods(C, '__await__', 'send', 'throw', 'close')
        return NotImplemented


Coroutine.register(coroutine)


class AsyncIterable(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __aiter__(self):
        return AsyncIterator()

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. In the coroutine body, catch GeneratorExit only to run cleanup, then re-raise it (bare 'raise') or return without suspending again.
  2. Never yield/await again after GeneratorExit is delivered; move post-cleanup suspension out of the finally path.
  3. Narrow existing 'except BaseException' handlers inside the coroutine to specific exceptions where possible.
  4. For custom Coroutine subclasses, ensure throw() propagates GeneratorExit/StopIteration rather than returning normally.

Example fix

# before
async def __await__body():
    try:
        yield
    except BaseException:
        yield 'cleanup-done'  # suspends again -> RuntimeError on close()

# after
async def __await__body():
    try:
        yield
    except GeneratorExit:
        await_cancelled_cleanup()  # no further yield
        raise
Defensive patterns

Strategy: try-catch

Try / catch

try:
    coro.close()
except RuntimeError as e:
    if 'coroutine ignored GeneratorExit' in str(e):
        log.warning('misbehaving coroutine swallowed GeneratorExit: %r', coro)
    else:
        raise

Prevention

When it happens

Trigger: A coroutine's __await__/generator body wraps the suspension point in try/except that swallows GeneratorExit and yields again, or a custom class registered as Coroutine implements throw/send so that throw(GeneratorExit) resumes normally. Calling .close() (directly or via garbage collection / asyncio task cancellation teardown) then triggers it.

Common situations: Custom coroutine-like classes implementing __await__ with cleanup that catches BaseException too broadly; asyncio wrappers where a shielded await intercepts GeneratorExit; debugging why GC emits 'coroutine ignored GeneratorExit' during interpreter shutdown.

Related errors


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