]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-150866: Fix asyncio shutdown_asyncgens to report BaseException (#150867)
authorTimofei Ivankov <128279579+deadlovelll@users.noreply.github.com>
Tue, 28 Jul 2026 06:59:49 +0000 (09:59 +0300)
committerGitHub <noreply@github.com>
Tue, 28 Jul 2026 06:59:49 +0000 (06:59 +0000)
Lib/asyncio/base_events.py
Lib/test/test_asyncio/test_base_events.py
Misc/NEWS.d/next/Library/2026-06-03-18-41-42.gh-issue-150866.gXGyX-.rst [new file with mode: 0644]

index 3836aaad326f62e282a0226cf60b541c7e267ac4..f26fba175b63cd72e472f0c529123f08b47918b1 100644 (file)
@@ -590,7 +590,7 @@ class BaseEventLoop(events.AbstractEventLoop):
             return_exceptions=True)
 
         for result, agen in zip(results, closing_agens):
-            if isinstance(result, Exception):
+            if isinstance(result, BaseException):
                 self.call_exception_handler({
                     'message': f'an error occurred during closing of '
                                f'asynchronous generator {agen!r}',
index 1622f19f0213f3c840b68d3aea1d4b9be565c411..18afdca23163a1e0dce10a832b025874e2e6f647 100644 (file)
@@ -1042,6 +1042,60 @@ class BaseEventLoopTests(test_utils.TestCase):
         asyncio.create_task(iter_one())
         return status
 
+    def test_shutdown_asyncgens_reports_base_exceptions(self):
+        # gh-150866: shutdown_asyncgens silently swallowed exceptions that
+        # don't inherit from Exception raised during aclose() because the
+        # check was isinstance(result, Exception), but CancelledError inherits
+        # from BaseException.
+        self.loop._process_events = mock.Mock()
+        self.loop._write_to_self = mock.Mock()
+
+        class MyBaseException(BaseException):
+            pass
+
+        async def agen_cancel():
+            try:
+                yield 1
+            finally:
+                raise asyncio.CancelledError("agen got cancelled during cleanup")
+
+        async def agen_base():
+            try:
+                yield 1
+            finally:
+                raise MyBaseException("base exc during cleanup")
+
+        async def agen_value_error():
+            try:
+                yield 1
+            finally:
+                raise ValueError("agen failed during cleanup")
+
+        caught = []
+
+        def handler(loop, context):
+            caught.append(context['exception'])
+
+        async def main():
+            loop = asyncio.get_running_loop()
+            loop.set_exception_handler(handler)
+
+            g1 = agen_cancel()
+            g2 = agen_base()
+            g3 = agen_value_error()
+            await g1.__anext__()
+            await g2.__anext__()
+            await g3.__anext__()
+
+            await loop.shutdown_asyncgens()
+
+        self.loop.run_until_complete(main())
+        self.assertEqual(len(caught), 3)
+        self.assertEqual(
+            {type(exc) for exc in caught},
+            {asyncio.CancelledError, MyBaseException, ValueError},
+        )
+
     def test_asyncgen_finalization_by_gc(self):
         # Async generators should be finalized when garbage collected.
         self.loop._process_events = mock.Mock()
diff --git a/Misc/NEWS.d/next/Library/2026-06-03-18-41-42.gh-issue-150866.gXGyX-.rst b/Misc/NEWS.d/next/Library/2026-06-03-18-41-42.gh-issue-150866.gXGyX-.rst
new file mode 100644 (file)
index 0000000..57af6e9
--- /dev/null
@@ -0,0 +1,2 @@
+Fix :meth:`asyncio.loop.shutdown_asyncgens` to report any
+:exc:`BaseException` raised during asynchronous generator cleanup.