diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index a431b45a19489d5..c38a266ba9692de 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -116,6 +116,7 @@ async def _aexit(self, et, exc): # can be cancelled multiple times if our parent task # is being cancelled repeatedly (or even once, when # our own cancellation is already in progress) + pending_cancellation_error = None while self._tasks: if self._on_completed_fut is None: self._on_completed_fut = self._loop.create_future() @@ -123,6 +124,7 @@ async def _aexit(self, et, exc): try: await self._on_completed_fut except exceptions.CancelledError as ex: + pending_cancellation_error = ex if not self._aborting: # Our parent task is being cancelled: # @@ -151,6 +153,9 @@ async def _aexit(self, et, exc): # If there are no pending cancellations left, # don't propagate CancelledError. propagate_cancellation_error = None + elif propagate_cancellation_error is None: + # gh-155433: the remaining cancellation is not ours, don't drop it + propagate_cancellation_error = pending_cancellation_error # 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..a2b6c8f6507d7b4 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1154,6 +1154,27 @@ 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('message') + with self.assertRaises(asyncio.CancelledError) as cm: + await task + self.assertEqual('message', cm.exception.args[0]) + 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()``.