{"record":{"id":"7517e0be9de55f73","repo":"aio-libs/aiohttp","slug":"multiple-errors-on-cleanup-stage","errorCode":null,"errorMessage":"Multiple errors on cleanup stage","messagePattern":"Multiple errors on cleanup stage","errorType":"exception","errorClass":"CleanupError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_app.py","lineNumber":447,"sourceCode":"\n            if not isinstance(ctx, AbstractAsyncContextManager):\n                ctx = asynccontextmanager(cb)(app)  # type: ignore[arg-type]\n\n            await ctx.__aenter__()\n            self._exits.append(ctx)\n\n    async def _on_cleanup(self, app: Application) -> None:\n        errors = []\n        for it in reversed(self._exits):\n            try:\n                await it.__aexit__(None, None, None)\n            except (Exception, asyncio.CancelledError) as exc:\n                errors.append(exc)\n        if errors:\n            if len(errors) == 1:\n                raise errors[0]\n            else:\n                raise CleanupError(\"Multiple errors on cleanup stage\", errors)\n","sourceCodeStart":429,"sourceCodeEnd":448,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_app.py#L429-L448","documentation":"During application cleanup, aiohttp runs every registered async-context-manager exit (__aexit__) in reverse registration order. If more than one exit raises, the individual exceptions are collected and re-raised as a CleanupError (a RuntimeError subclass) whose .exceptions property holds the full list. This preserves secondary failures rather than silently dropping them.","triggerScenarios":"Two or more cleanup_ctx handlers / async context managers each raise during shutdown; e.g. a DB pool close fails AND a background task cancellation raises AND a redis client times out.","commonSituations":"Shutdown under load where several resource pools fail to close; a signal/timeout tearing down connections mid-teardown; one failing cleanup masking others in naive try/except.","solutions":["Catch CleanupError and iterate err.exceptions to identify every root cause.","Make each __aexit__ log and swallow non-fatal errors so a single failure does not cascade into CleanupError.","Order teardown deliberately — close consumers before their producers / pools."],"exampleFix":"# before\ntry:\n    await runner.cleanup()\nexcept Exception:\n    pass   # masks all failures\n# after\ntry:\n    await runner.cleanup()\nexcept CleanupError as e:\n    for exc in e.exceptions:\n        logger.error('cleanup failed: %r', exc)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    await runner.cleanup()\nexcept CleanupError as e:\n    for exc in e.exceptions:\n        logger.error('cleanup failure: %r', exc)","preventionTips":["Make each cleanup context manager's __aexit__ log-and-swallow non-fatal errors.","Order teardown: close consumers before producers, then pools.","Register a single on_cleanup handler that aggregates and logs, rather than relying on the default."],"tags":["lifecycle","cleanup","shutdown","runtime-error"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}