https://github.com/python/cpython/commit/8ea98fc1b4fdc44409b4e1d9387bbdd09eecef50 commit: 8ea98fc1b4fdc44409b4e1d9387bbdd09eecef50 branch: 3.15 author: Miss Islington (bot) <[email protected]> committer: asvetlov <[email protected]> date: 2026-10-30T10:53:32+01:00 summary:
[3.15] gh-137549: Handle BaseExceptionGroup in asyncio.Timeout.__aexit__ (GH-160957) (#160960) gh-137549: Handle BaseExceptionGroup in asyncio.Timeout.__aexit__ (GH-160957) asyncio.timeout() inserted TimeoutError into the context chain of sub-exceptions only for ExceptionGroup; do the same for BaseExceptionGroup. (cherry picked from commit a683f704dca79f062705432f18347c929923d081) Co-authored-by: Andrew Svetlov <[email protected]> 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 f60722c48b7617..083d4db001b723 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]
