Skip to content

Commit 8ea98fc

Browse files
miss-islingtonasvetlovclaude
authored
[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 a683f70) Co-authored-by: Andrew Svetlov <andrew.svetlov@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 38c3194 commit 8ea98fc

3 files changed

Lines changed: 37 additions & 1 deletion

File tree

‎Lib/asyncio/timeouts.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ async def __aexit__(
115115
raise TimeoutError from exc_val
116116
elif exc_val is not None:
117117
self._insert_timeout_error(exc_val)
118-
if isinstance(exc_val, ExceptionGroup):
118+
if isinstance(exc_val, BaseExceptionGroup):
119119
for exc in exc_val.exceptions:
120120
self._insert_timeout_error(exc)
121121
elif self._state is _State.ENTERED:

‎Lib/test/test_asyncio/test_timeouts.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,39 @@ async def task():
406406
self.assertIsNone(e3.__cause__)
407407
self.assertIs(e2.__context__, e3)
408408

409+
async def test_timeout_taskgroup_base_exception_group(self):
410+
class MyBaseError(BaseException):
411+
pass
412+
413+
async def task():
414+
try:
415+
await asyncio.sleep(2) # Will be interrupted after 0.01 second
416+
finally:
417+
raise MyBaseError # Crash in cleanup
418+
419+
with self.assertRaises(BaseExceptionGroup) as cm:
420+
async with asyncio.timeout(0.01):
421+
async with asyncio.TaskGroup() as tg:
422+
tg.create_task(task())
423+
await asyncio.sleep(1)
424+
eg = cm.exception
425+
self.assertNotIsInstance(eg, ExceptionGroup)
426+
self.assertIsInstance(eg.__context__, TimeoutError)
427+
428+
self.assertEqual(len(eg.exceptions), 1, eg)
429+
e1 = eg.exceptions[0]
430+
# Expect MyBaseError raised during handling of TimeoutError
431+
# caused by CancelledError.
432+
self.assertIsInstance(e1, MyBaseError)
433+
self.assertIsNone(e1.__cause__)
434+
e2 = e1.__context__
435+
self.assertIsInstance(e2, TimeoutError)
436+
e3 = e2.__cause__
437+
self.assertIsInstance(e3, asyncio.CancelledError)
438+
self.assertIsNone(e3.__context__)
439+
self.assertIsNone(e3.__cause__)
440+
self.assertIs(e2.__context__, e3)
441+
409442

410443
if __name__ == '__main__':
411444
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`asyncio.timeout` now inserts :exc:`TimeoutError` into the context
2+
chain of sub-exceptions of a :exc:`BaseExceptionGroup`, as it already did for
3+
:exc:`ExceptionGroup`.

0 commit comments

Comments
 (0)