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]