From 0a5aaa492230d4b6f549ec2e12b87ccc42ca9d46 Mon Sep 17 00:00:00 2001 From: gaoanze Date: Wed, 14 Oct 2026 18:58:33 +0800 Subject: [PATCH 1/2] fix(asyncio): suppress duplicate reraised task exceptions --- Lib/asyncio/tasks.py | 4 ++++ Lib/test/test_asyncio/test_tasks.py | 11 +++++++++++ Modules/_asynciomodule.c | 5 ++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 33284288a7cca70..e94b2fd0ae78459 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -313,6 +313,10 @@ def __step_run_and_handle_result(self, exc): super().cancel() # I.e., Future.cancel(self). except (KeyboardInterrupt, SystemExit) as exc: super().set_exception(exc) + # This exception is re-raised to the event loop instead of being + # retained solely by this Task, so it is not unhandled if the + # task cannot subsequently be awaited during loop shutdown. + self._log_traceback = False raise except BaseException as exc: super().set_exception(exc) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index f460029aff839d8..d791c3c2f228c00 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1965,6 +1965,17 @@ async def current_task(exc): self.assertTrue(not t.cancelled()) self.assertEqual(t.result(), "ok") + def test_step_does_not_log_reraised_base_exceptions(self): + async def raise_exception(exc): + raise exc + + for exc_type in (SystemExit, KeyboardInterrupt): + with self.subTest(exc_type=exc_type): + task = self.new_task(self.loop, raise_exception(exc_type())) + with self.assertRaises(exc_type): + self.loop.run_until_complete(task) + self.assertFalse(task._log_traceback) + def test_step_result_future(self): # If coroutine returns future, task waits on this future. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 18e731336d6da1d..00a42f6844ab3ae 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3129,7 +3129,10 @@ task_step_impl(asyncio_state *state, TaskObj *task, PyObject *exc) if (PyErr_GivenExceptionMatches(exc, PyExc_KeyboardInterrupt) || PyErr_GivenExceptionMatches(exc, PyExc_SystemExit)) { - /* We've got a KeyboardInterrupt or a SystemError; re-raise it */ + /* The exception is re-raised to the event loop, so it cannot be + lost even if loop shutdown prevents this task from being + awaited. */ + task->task_log_tb = 0; PyErr_SetRaisedException(exc); goto fail; } From bf4b20be24c297c4e8874c57de9cc665f03ab84c Mon Sep 17 00:00:00 2001 From: gaoanze Date: Wed, 14 Oct 2026 19:05:44 +0800 Subject: [PATCH 2/2] news(asyncio): document reraised task warning fix --- .../Library/2026-10-14-10-59-00.gh-issue-159401.c17QdV.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-14-10-59-00.gh-issue-159401.c17QdV.rst diff --git a/Misc/NEWS.d/next/Library/2026-10-14-10-59-00.gh-issue-159401.c17QdV.rst b/Misc/NEWS.d/next/Library/2026-10-14-10-59-00.gh-issue-159401.c17QdV.rst new file mode 100644 index 000000000000000..1f54099f0a141c3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-14-10-59-00.gh-issue-159401.c17QdV.rst @@ -0,0 +1,3 @@ +Suppress a duplicate ``Task exception was never retrieved`` warning when an +asyncio task raises :exc:`SystemExit` or :exc:`KeyboardInterrupt`, which is +already propagated by the event loop.