gh-155433: Fix TaskGroup losing outside cancellation after cancel() (#155439)

kumaraditya303 <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/ee23046d64a4bd78a5a02a4a1b3afa80b60a64fa
commit: ee23046d64a4bd78a5a02a4a1b3afa80b60a64fa
branch: main
author: Timofei Ivankov <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-23T15:37:35+05:30
summary:

gh-155433: Fix TaskGroup losing outside cancellation after cancel() (#155439)

files:
A Misc/NEWS.d/next/Library/2026-08-09-16-14-38.gh-issue-155433.KL7tHV.rst
M Lib/asyncio/taskgroups.py
M Lib/test/test_asyncio/test_taskgroups.py

diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py
index 1b77d6d317e937..955e8e677eac1a 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:
                     #
@@ -163,6 +165,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 f5b7daf9e2ce1d..983e1a7dc53e6f 100644
--- a/Lib/test/test_asyncio/test_taskgroups.py
+++ b/Lib/test/test_asyncio/test_taskgroups.py
@@ -1187,6 +1187,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 00000000000000..dc8961e976abbc
--- /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()``.

_______________________________________________
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.