Skip to content

Commit 08671e7

Browse files
miss-islingtonTimofei Ivankov
andauthored
[3.14] gh-158239: Fix asyncio.gather() performance regression (GH-158240) (#158656)
gh-158239: Fix `asyncio.gather()` performance regression (GH-158240) (cherry picked from commit b06dd22) Co-authored-by: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com>
1 parent c542ecd commit 08671e7

3 files changed

Lines changed: 36 additions & 5 deletions

File tree

‎Lib/asyncio/tasks.py‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ def cancel(self, msg=None):
775775
return ret
776776

777777

778-
def _discard_awaited_by(children, waiter, outer):
778+
def _discard_awaited_by(children, waiter):
779779
for fut in children:
780780
futures.future_discard_from_awaited_by(fut, waiter)
781781

@@ -841,11 +841,14 @@ def _done_callback(fut, cur_task=cur_task):
841841
# 'fut.exception()' will *raise* a CancelledError
842842
# instead of returning it.
843843
exc = fut._make_cancelled_error()
844+
# gh-157213: children outliving gather() must lose the edge
845+
_discard_awaited_by(children, cur_task)
844846
outer.set_exception(exc)
845847
return
846848
else:
847849
exc = fut.exception()
848850
if exc is not None:
851+
_discard_awaited_by(children, cur_task)
849852
outer.set_exception(exc)
850853
return
851854

@@ -913,10 +916,6 @@ def _done_callback(fut, cur_task=cur_task):
913916
children.append(fut)
914917

915918
outer = _GatheringFuture(children, loop=loop)
916-
if cur_task is not None:
917-
# gh-157213: a child outliving gather() must lose the awaited-by edge
918-
outer.add_done_callback(
919-
functools.partial(_discard_awaited_by, children, cur_task))
920919
# Run done callbacks after GatheringFuture created so any post-processing
921920
# can be performed at this point
922921
# optimization: in the special case that *all* futures finished eagerly,

‎Lib/test/test_asyncio/test_tasks.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,37 @@ async def coro():
12521252

12531253
self.loop.run_until_complete(self.new_task(self.loop, coro()))
12541254

1255+
def test_gather_discards_awaited_by_for_cancelled_sibling(self):
1256+
# gh-157213: same, when gather() is ended by a cancelled child
1257+
async def survivor():
1258+
await asyncio.Future()
1259+
1260+
async def coro():
1261+
t = self.new_task(self.loop, survivor())
1262+
victim = self.new_task(self.loop, asyncio.sleep(10))
1263+
victim.cancel()
1264+
with self.assertRaises(asyncio.CancelledError):
1265+
await asyncio.gather(t, victim)
1266+
self.assertFalse(t._asyncio_awaited_by)
1267+
t.cancel()
1268+
with self.assertRaises(asyncio.CancelledError):
1269+
await t
1270+
1271+
self.loop.run_until_complete(self.new_task(self.loop, coro()))
1272+
1273+
def test_gather_does_not_add_callback_to_outer(self):
1274+
# gh-158239: gather() must not add an internal done callback to
1275+
# the outer future just to maintain the await graph.
1276+
async def child():
1277+
await asyncio.sleep(0)
1278+
1279+
async def coro():
1280+
outer = asyncio.gather(child(), child())
1281+
self.assertFalse(outer._callbacks)
1282+
await outer
1283+
1284+
self.loop.run_until_complete(self.new_task(self.loop, coro()))
1285+
12551286
def test_wait_really_done(self):
12561287
# there is possibility that some tasks in the pending list
12571288
# became done but their callbacks haven't all been called yet

‎Misc/ACKS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ Dean Inwood
870870
Bob Ippolito
871871
Roger Irwin
872872
Atsuo Ishimoto
873+
Timofei Ivankov
873874
Alexey Izbyshev
874875
Kasia Jachim
875876
Adam Jackson

0 commit comments

Comments
 (0)