diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index a431b45a19489d5..0c1d09ded2f956d 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -151,6 +151,9 @@ async def _aexit(self, et, exc): # If there are no pending cancellations left, # don't propagate CancelledError. propagate_cancellation_error = None + else: + # gh-155433: the remaining cancellation is not ours, don't drop it + propagate_cancellation_error = exceptions.CancelledError() # Propagate CancelledError if there is one, except if there # are other errors -- those have priority. diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index bc246400b83e9b2..88bb9510af367bf 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1154,6 +1154,26 @@ async def test_taskgroup_cancel_before_create_task(self): with self.assertRaises(RuntimeError): tg.create_task(asyncio.sleep(1)) + async def test_taskgroup_cancel_keeps_outer_cancellation(self): + # gh-155433: any cancellation from outside the group must propagate. + async def child(): + try: + await asyncio.sleep(10) + finally: + await asyncio.sleep(0.1) + + async def body(): + async with asyncio.TaskGroup() as tg: + tg.create_task(child()) + await asyncio.sleep(0) + tg.cancel() + + task = asyncio.create_task(body()) + await asyncio.sleep(0.01) + task.cancel() + with self.assertRaises(asyncio.CancelledError): + await task + async def test_taskgroup_cancel_before_exception(self): async def raise_exc(parent_tg: asyncio.TaskGroup): parent_tg.cancel() diff --git a/Misc/NEWS.d/next/Library/2026-08-09-16-14-38.gh-issue-155433.KL7tHV.rst b/Misc/NEWS.d/next/Library/2026-08-09-16-14-38.gh-issue-155433.KL7tHV.rst new file mode 100644 index 000000000000000..dc8961e976abbc2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-09-16-14-38.gh-issue-155433.KL7tHV.rst @@ -0,0 +1,2 @@ +Fix :class:`asyncio.TaskGroup` losing outside cancellation after +``cancel()``.