{"id":"448eb3aec771d3ab","repo":"pytest-dev/pytest","slug":"multiple-unraisable-exception-warnings","errorCode":null,"errorMessage":"multiple unraisable exception warnings","messagePattern":"multiple unraisable exception warnings","errorType":"exception","errorClass":"ExceptionGroup","httpStatus":null,"severity":"warning","filePath":"src/_pytest/unraisableexception.py","lineNumber":81,"sourceCode":"                continue\n\n            msg = meta.msg\n            try:\n                warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))\n            except pytest.PytestUnraisableExceptionWarning as e:\n                # This except happens when the warning is treated as an error (e.g. `-Werror`).\n                if meta.exc_value is not None:\n                    # Exceptions have a better way to show the traceback, but\n                    # warnings do not, so hide the traceback from the msg and\n                    # set the cause so the traceback shows up in the right place.\n                    e.args = (meta.cause_msg,)\n                    e.__cause__ = meta.exc_value\n                errors.append(e)\n\n        if len(errors) == 1:\n            raise errors[0]\n        if errors:\n            raise ExceptionGroup(\"multiple unraisable exception warnings\", errors)\n    finally:\n        del errors, meta, hook_error\n\n\ndef cleanup(\n    *, config: Config, prev_hook: Callable[[sys.UnraisableHookArgs], object]\n) -> None:\n    # On PyPy, objects (e.g. coroutines) can survive GC rounds because executing\n    # their __del__ can resurrect them. The Trio project determined experimentally\n    # that 5 passes are needed on PyPy to flush everything. On CPython, reference\n    # counting handles most cleanup immediately, so 1 pass is sufficient.\n    _default_gc_collect_iterations = 5 if sys.implementation.name == \"pypy\" else 1\n    gc_collect_iterations = config.stash.get(\n        gc_collect_iterations_key, _default_gc_collect_iterations\n    )\n    try:\n        try:\n            gc_collect_harder(gc_collect_iterations)","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/unraisableexception.py#L63-L99","documentation":"Raised as an ExceptionGroup during session teardown when more than one unraisable exception was captured over the session. pytest installs sys.unraisablehook to record exceptions Python could not raise anywhere (typically from __del__ methods, generator finalization, or C-level callbacks during GC); at teardown collect_unraisable re-emits each as PytestUnraisableExceptionWarning, and several get bundled into this group. A forced gc.collect (5 passes on PyPy, 1 on CPython) is run first to flush pending finalizers.","triggerScenarios":"Test code (or library code under test) defines __del__ that raises, leaks generators/coroutines that fail on close, uses C extensions that write to unraisablehook, or leaves file handles / sockets to be GC'd with errors. Multiple such events across the session produce the group at teardown.","commonSituations":"Async coroutines not awaited/closed (ResourceWarning + __del__ errors); objects with broken __del__ in app code; native modules (numpy, hdf5, db drivers) emitting unraisable warnings on shutdown; running with -Werror which converts each warning into a hard exception.","solutions":["Run with `pytest -W error::pytest.PytestUnraisableExceptionWarning` to surface the first unraisable as a real failure and locate its traceback.","Close resources explicitly (context managers, asyncio.run, contextlib.aclosing) instead of relying on GC.","Fix __del__ methods to swallow/log exceptions; never let __del__ raise.","Disable -Werror for this category if the warnings are benign in CI: `pytest -W default::pytest.PytestUnraisableExceptionWarning`."],"exampleFix":"// before\nclass Conn:\n    def __del__(self):\n        self.sock.close()  # raises if already closed -> unraisable\n\n// after\nclass Conn:\n    def close(self):\n        try: self.sock.close()\n        except OSError: pass\n    def __del__(self):\n        try: self.close()\n        except Exception: pass","handlingStrategy":"try-catch","validationCode":"import gc, sys, warnings\n\ndef flush_unraisable():\n    captured = []\n    prev = sys.unraisablehook\n    sys.unraisablehook = lambda a: captured.append(a)\n    gc.collect()\n    sys.unraisablehook = prev\n    if captured:\n        warnings.warn(f\"{len(captured)} unraisable exceptions pending\")\n    return captured","typeGuard":null,"tryCatchPattern":"try:\n    run_test()\nexcept* pytest.PytestUnraisableExceptionWarning as eg:\n    for e in eg.exceptions:\n        diagnose_unraisable(e)","preventionTips":["Close all resources explicitly with context managers; do not rely on GC/__del__.","Never let __del__ raise; wrap its body in try/except.","Run CI with -W error::pytest.PytestUnraisableExceptionWarning to catch regressions."],"tags":["unraisable","gc","exceptiongroup","teardown","resources"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}