From 4fc3d313a860e19f279d2c9c93e4e2564fe9e74b Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 26 Sep 2026 17:30:03 +0300 Subject: [PATCH 1/2] gh-158239: Fix asyncio.gather performance regression --- Lib/asyncio/tasks.py | 9 ++++----- Lib/test/test_asyncio/test_tasks.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index cf4787db1730597..2d7a802bb44961c 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -786,7 +786,7 @@ def cancel(self, msg=None): return ret -def _discard_awaited_by(children, waiter, outer): +def _discard_awaited_by(children, waiter): for fut in children: futures.future_discard_from_awaited_by(fut, waiter) @@ -852,11 +852,14 @@ def _done_callback(fut, cur_task=cur_task): # 'fut.exception()' will *raise* a CancelledError # instead of returning it. exc = fut._make_cancelled_error() + # gh-157213: children outliving gather() must lose the edge + _discard_awaited_by(children, cur_task) outer.set_exception(exc) return else: exc = fut.exception() if exc is not None: + _discard_awaited_by(children, cur_task) outer.set_exception(exc) return @@ -924,10 +927,6 @@ def _done_callback(fut, cur_task=cur_task): children.append(fut) outer = _GatheringFuture(children, loop=loop) - if cur_task is not None: - # gh-157213: a child outliving gather() must lose the awaited-by edge - outer.add_done_callback( - functools.partial(_discard_awaited_by, children, cur_task)) # Run done callbacks after GatheringFuture created so any post-processing # can be performed at this point # optimization: in the special case that *all* futures finished eagerly, diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 570810a231b48d2..d1a8045b79d597c 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1250,6 +1250,24 @@ async def coro(): self.loop.run_until_complete(self.new_task(self.loop, coro())) + def test_gather_discards_awaited_by_for_cancelled_sibling(self): + # gh-157213: same, when gather() is ended by a cancelled child + async def survivor(): + await asyncio.Future() + + async def coro(): + t = self.new_task(self.loop, survivor()) + victim = self.new_task(self.loop, asyncio.sleep(10)) + victim.cancel() + with self.assertRaises(asyncio.CancelledError): + await asyncio.gather(t, victim) + self.assertFalse(t._asyncio_awaited_by) + t.cancel() + with self.assertRaises(asyncio.CancelledError): + await t + + self.loop.run_until_complete(self.new_task(self.loop, coro())) + def test_wait_really_done(self): # there is possibility that some tasks in the pending list # became done but their callbacks haven't all been called yet From af8a20cb08d59c6f361059e6ef9c4e1f8ca49b6f Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 27 Sep 2026 18:14:53 +0300 Subject: [PATCH 2/2] Add myself to Misc/ACKS --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 4a3f6482294e087..ad1b1119aa243a8 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -884,6 +884,7 @@ Dean Inwood Bob Ippolito Roger Irwin Atsuo Ishimoto +Timofei Ivankov Alexey Izbyshev Kasia Jachim Adam Jackson