https://github.com/python/cpython/commit/a683f704dca79f062705432f18347c929923d081
commit: a683f704dca79f062705432f18347c929923d081
branch: main
author: Andrew Svetlov <[email protected]>
committer: asvetlov <[email protected]>
date: 2026-10-30T10:18:32+01:00
summary:

gh-137549: Handle BaseExceptionGroup in asyncio.Timeout.__aexit__ (#160957)

asyncio.timeout() inserted TimeoutError into the context chain of
sub-exceptions only for ExceptionGroup; do the same for
BaseExceptionGroup.

Co-authored-by: Claude Opus 5.5 <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-10-30-12-00-00.gh-issue-137549.Tm0Bxg.rst
M Lib/asyncio/timeouts.py
M Lib/test/test_asyncio/test_timeouts.py

diff --git a/Lib/asyncio/timeouts.py b/Lib/asyncio/timeouts.py
index 65ddc285abd971..e425884a453e27 100644
--- a/Lib/asyncio/timeouts.py
+++ b/Lib/asyncio/timeouts.py
@@ -115,7 +115,7 @@ async def __aexit__(
                     raise TimeoutError from exc_val
                 elif exc_val is not None:
                     self._insert_timeout_error(exc_val)
-                    if isinstance(exc_val, ExceptionGroup):
+                    if isinstance(exc_val, BaseExceptionGroup):
                         for exc in exc_val.exceptions:
                             self._insert_timeout_error(exc)
         elif self._state is _State.ENTERED:
diff --git a/Lib/test/test_asyncio/test_timeouts.py 
b/Lib/test/test_asyncio/test_timeouts.py
index 69936aae5cc81d..43eebd3b56f150 100644
--- a/Lib/test/test_asyncio/test_timeouts.py
+++ b/Lib/test/test_asyncio/test_timeouts.py
@@ -406,6 +406,39 @@ async def task():
         self.assertIsNone(e3.__cause__)
         self.assertIs(e2.__context__, e3)
 
+    async def test_timeout_taskgroup_base_exception_group(self):
+        class MyBaseError(BaseException):
+            pass
+
+        async def task():
+            try:
+                await asyncio.sleep(2)  # Will be interrupted after 0.01 second
+            finally:
+                raise MyBaseError  # Crash in cleanup
+
+        with self.assertRaises(BaseExceptionGroup) as cm:
+            async with asyncio.timeout(0.01):
+                async with asyncio.TaskGroup() as tg:
+                    tg.create_task(task())
+                    await asyncio.sleep(1)
+        eg = cm.exception
+        self.assertNotIsInstance(eg, ExceptionGroup)
+        self.assertIsInstance(eg.__context__, TimeoutError)
+
+        self.assertEqual(len(eg.exceptions), 1, eg)
+        e1 = eg.exceptions[0]
+        # Expect MyBaseError raised during handling of TimeoutError
+        # caused by CancelledError.
+        self.assertIsInstance(e1, MyBaseError)
+        self.assertIsNone(e1.__cause__)
+        e2 = e1.__context__
+        self.assertIsInstance(e2, TimeoutError)
+        e3 = e2.__cause__
+        self.assertIsInstance(e3, asyncio.CancelledError)
+        self.assertIsNone(e3.__context__)
+        self.assertIsNone(e3.__cause__)
+        self.assertIs(e2.__context__, e3)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-30-12-00-00.gh-issue-137549.Tm0Bxg.rst 
b/Misc/NEWS.d/next/Library/2026-10-30-12-00-00.gh-issue-137549.Tm0Bxg.rst
new file mode 100644
index 00000000000000..32c27840f5b08b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-30-12-00-00.gh-issue-137549.Tm0Bxg.rst
@@ -0,0 +1,3 @@
+:func:`asyncio.timeout` now inserts :exc:`TimeoutError` into the context
+chain of sub-exceptions of a :exc:`BaseExceptionGroup`, as it already did for
+:exc:`ExceptionGroup`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to