Skip to content

Commit b06dd22

Browse files
authored
gh-158239: Fix asyncio.gather() performance regression (#158240)
1 parent d6d1ebc commit b06dd22

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
@@ -786,7 +786,7 @@ def cancel(self, msg=None):
786786
return ret
787787

788788

789-
def _discard_awaited_by(children, waiter, outer):
789+
def _discard_awaited_by(children, waiter):
790790
for fut in children:
791791
futures.future_discard_from_awaited_by(fut, waiter)
792792

@@ -852,11 +852,14 @@ def _done_callback(fut, cur_task=cur_task):
852852
# 'fut.exception()' will *raise* a CancelledError
853853
# instead of returning it.
854854
exc = fut._make_cancelled_error()
855+
# gh-157213: children outliving gather() must lose the edge
856+
_discard_awaited_by(children, cur_task)
855857
outer.set_exception(exc)
856858
return
857859
else:
858860
exc = fut.exception()
859861
if exc is not None:
862+
_discard_awaited_by(children, cur_task)
860863
outer.set_exception(exc)
861864
return
862865

@@ -924,10 +927,6 @@ def _done_callback(fut, cur_task=cur_task):
924927
children.append(fut)
925928

926929
outer = _GatheringFuture(children, loop=loop)
927-
if cur_task is not None:
928-
# gh-157213: a child outliving gather() must lose the awaited-by edge
929-
outer.add_done_callback(
930-
functools.partial(_discard_awaited_by, children, cur_task))
931930
# Run done callbacks after GatheringFuture created so any post-processing
932931
# can be performed at this point
933932
# 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
@@ -1250,6 +1250,37 @@ async def coro():
12501250

12511251
self.loop.run_until_complete(self.new_task(self.loop, coro()))
12521252

1253+
def test_gather_discards_awaited_by_for_cancelled_sibling(self):
1254+
# gh-157213: same, when gather() is ended by a cancelled child
1255+
async def survivor():
1256+
await asyncio.Future()
1257+
1258+
async def coro():
1259+
t = self.new_task(self.loop, survivor())
1260+
victim = self.new_task(self.loop, asyncio.sleep(10))
1261+
victim.cancel()
1262+
with self.assertRaises(asyncio.CancelledError):
1263+
await asyncio.gather(t, victim)
1264+
self.assertFalse(t._asyncio_awaited_by)
1265+
t.cancel()
1266+
with self.assertRaises(asyncio.CancelledError):
1267+
await t
1268+
1269+
self.loop.run_until_complete(self.new_task(self.loop, coro()))
1270+
1271+
def test_gather_does_not_add_callback_to_outer(self):
1272+
# gh-158239: gather() must not add an internal done callback to
1273+
# the outer future just to maintain the await graph.
1274+
async def child():
1275+
await asyncio.sleep(0)
1276+
1277+
async def coro():
1278+
outer = asyncio.gather(child(), child())
1279+
self.assertFalse(outer._callbacks)
1280+
await outer
1281+
1282+
self.loop.run_until_complete(self.new_task(self.loop, coro()))
1283+
12531284
def test_wait_really_done(self):
12541285
# there is possibility that some tasks in the pending list
12551286
# 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
@@ -884,6 +884,7 @@ Dean Inwood
884884
Bob Ippolito
885885
Roger Irwin
886886
Atsuo Ishimoto
887+
Timofei Ivankov
887888
Alexey Izbyshev
888889
Kasia Jachim
889890
Adam Jackson

0 commit comments

Comments
 (0)