{"record":{"id":"cf0f882f84050b5e","repo":"python/cpython","slug":"deprecated-removed-second-argument-cannot-be-ne","errorCode":null,"errorMessage":"deprecated-removed:: second argument cannot be `next`","messagePattern":"deprecated-removed:: second argument cannot be `next`","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Doc/tools/extensions/changes.py","lineNumber":57,"sourceCode":"class DeprecatedRemoved(VersionChange):\n    required_arguments = 2\n\n    _deprecated_label = sphinx_gettext(\n        \"Deprecated since version %s, will be removed in version %s\"\n    )\n    _removed_label = sphinx_gettext(\n        \"Deprecated since version %s, removed in version %s\"\n    )\n\n    def run(self) -> list[Node]:\n        # Replace the first two arguments (deprecated version and removed version)\n        # with a single tuple of both versions.\n        version_deprecated = expand_version_arg(\n            self.arguments[0], self.config.release\n        )\n        version_removed = self.arguments.pop(1)\n        if version_removed == \"next\":\n            raise ValueError(\n                \"deprecated-removed:: second argument cannot be `next`\"\n            )\n        self.arguments[0] = version_deprecated, version_removed\n\n        # Set the label based on if we have reached the removal version\n        current_version = tuple(map(int, self.config.version.split(\".\")))\n        removed_version = tuple(map(int, version_removed.split(\".\")))\n        if current_version < removed_version:\n            versionlabels[self.name] = self._deprecated_label\n            versionlabel_classes[self.name] = \"deprecated\"\n        else:\n            versionlabels[self.name] = self._removed_label\n            versionlabel_classes[self.name] = \"removed\"\n        try:\n            return super().run()\n        finally:\n            # reset versionlabels and versionlabel_classes\n            versionlabels[self.name] = \"\"","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Doc/tools/extensions/changes.py#L39-L75","documentation":"BaseSelectorEventLoop.close() (Unix/selector-based loops) refuses to close a loop that is currently running, mirroring the base-class contract: close only after run_forever()/run_until_complete() returns. Closing a live loop would destroy its selector and self-pipe while callbacks still execute, so it raises this RuntimeError as a guard.","triggerScenarios":"Calling loop.close() from a coroutine, a task, or a callback scheduled on the running loop; closing the loop from a signal handler while run_forever() is active; teardown in pytest fixtures executed while a loop thread is still running the loop.","commonSituations":"Application shutdown code in finally blocks or atexit that closes the loop before the runner thread stopped; mixing asyncio.run with manual loop management; background-thread loops closed by the main thread without stopping them first.","solutions":["Call loop.close() only after the run call has returned (run_until_complete/run_forever + stop)","From inside a coroutine, end by stopping the loop (loop.stop()) or simply return and let the driver close it","Use asyncio.run() so close-ordering is handled correctly","For loops in other threads, request stop via loop.call_soon_threadsafe(loop.stop), join the thread, then close"],"exampleFix":"# before\nasync def main(loop):\n    await work()\n    loop.close()  # RuntimeError: still running\n# after\nasync def main():\n    await work()\nloop.run_until_complete(main())\nloop.close()","handlingStrategy":"validation","validationCode":"def shutdown_loop(loop, loop_thread=None):\n    if loop.is_running():\n        loop.call_soon_threadsafe(loop.stop)\n        if loop_thread:\n            loop_thread.join(timeout=5)\n    if not loop.is_closed():\n        loop.close()","typeGuard":null,"tryCatchPattern":"try:\n    loop.close()\nexcept RuntimeError as e:\n    if 'running event loop' in str(e):\n        loop.stop()\n        return  # retry close after run_forever() returns\n    raise","preventionTips":["Use asyncio.run() instead of manual loop lifecycle where possible","Never close a loop from its own coroutine or callback","Stop cross-thread loops via call_soon_threadsafe(loop.stop) then join","Order teardown: stop -> run returns -> close"],"tags":["asyncio","event-loop","lifecycle","selector"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}