Skip to content

Commit dff7a46

Browse files
authored
[3.15] gh-155433: Fix TaskGroup losing outside cancellation after cancel() (#157329)
1 parent 340cfba commit dff7a46

3 files changed

Lines changed: 35 additions & 0 deletions

File tree

Lib/asyncio/taskgroups.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,15 @@ async def _aexit(self, et, exc):
116116
# can be cancelled multiple times if our parent task
117117
# is being cancelled repeatedly (or even once, when
118118
# our own cancellation is already in progress)
119+
pending_cancellation_error = None
119120
while self._tasks:
120121
if self._on_completed_fut is None:
121122
self._on_completed_fut = self._loop.create_future()
122123

123124
try:
124125
await self._on_completed_fut
125126
except exceptions.CancelledError as ex:
127+
pending_cancellation_error = ex
126128
if not self._aborting:
127129
# Our parent task is being cancelled:
128130
#
@@ -151,6 +153,9 @@ async def _aexit(self, et, exc):
151153
# If there are no pending cancellations left,
152154
# don't propagate CancelledError.
153155
propagate_cancellation_error = None
156+
elif propagate_cancellation_error is None:
157+
# gh-155433: the remaining cancellation is not ours, don't drop it
158+
propagate_cancellation_error = pending_cancellation_error
154159

155160
# Propagate CancelledError if there is one, except if there
156161
# are other errors -- those have priority.

Lib/test/test_asyncio/test_taskgroups.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,34 @@ async def child(tg):
11651165
task = tg.create_task(child(tg))
11661166
self.assertTrue(task.cancelled())
11671167

1168+
async def test_taskgroup_cancel_keeps_outer_cancellation(self):
1169+
# gh-155433: any cancellation from outside the group must propagate.
1170+
cancelling = asyncio.Event()
1171+
release = asyncio.Event()
1172+
1173+
async def child():
1174+
try:
1175+
await asyncio.sleep(10)
1176+
finally:
1177+
# The group is cancelling: it has cancelled its parent task
1178+
# and is waiting for this task to finish.
1179+
cancelling.set()
1180+
await release.wait()
1181+
1182+
async def body():
1183+
async with asyncio.TaskGroup() as tg:
1184+
tg.create_task(child())
1185+
await asyncio.sleep(0)
1186+
tg.cancel()
1187+
1188+
task = asyncio.create_task(body())
1189+
await cancelling.wait()
1190+
task.cancel('message')
1191+
release.set()
1192+
with self.assertRaises(asyncio.CancelledError) as cm:
1193+
await task
1194+
self.assertEqual('message', cm.exception.args[0])
1195+
11681196
async def test_taskgroup_cancel_before_exception(self):
11691197
async def raise_exc(parent_tg: asyncio.TaskGroup):
11701198
parent_tg.cancel()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :class:`asyncio.TaskGroup` losing outside cancellation after
2+
``cancel()``.

0 commit comments

Comments
 (0)