From 62e2f3b1581bbb927f856cd84927b9a1747b4c59 Mon Sep 17 00:00:00 2001 From: lipengyu Date: Wed, 23 Sep 2026 22:49:53 +0800 Subject: [PATCH 1/4] Preserve as_completed items after anext cancellation Restore the iterator's remaining-item count when a pending asynchronous iteration step is cancelled. --- Lib/asyncio/tasks.py | 6 +++++- Lib/test/test_asyncio/test_tasks.py | 20 +++++++++++++++++++ ...-09-23-19-59-03.gh-issue-158004.p68swW.rst | 2 ++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-23-19-59-03.gh-issue-158004.p68swW.rst diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index cf4787db173059..259a23acebd7af 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -601,7 +601,11 @@ async def __anext__(self): raise StopAsyncIteration assert self._todo_left > 0 self._todo_left -= 1 - return await self._wait_for_one() + try: + return await self._wait_for_one() + except exceptions.CancelledError: + self._todo_left += 1 + raise def __next__(self): if not self._todo_left: diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 570810a231b48d..60c3ecd2c953c7 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1669,6 +1669,26 @@ async def coro(i): ) self.assertCountEqual(results, (0, 1, 2, 3)) + def test_as_completed_async_iterator_cancelled_anext(self): + async def main(): + loop = asyncio.get_running_loop() + a = loop.create_future() + b = loop.create_future() + iterator = asyncio.as_completed([a, b]) + + waiter = asyncio.create_task(anext(iterator)) + await asyncio.sleep(0) + waiter.cancel() + with self.assertRaises(asyncio.CancelledError): + await waiter + + a.set_result('a') + b.set_result('b') + return [await f async for f in iterator] + + results = self.loop.run_until_complete(main()) + self.assertCountEqual(results, ['a', 'b']) + def test_as_completed_reverse_wait(self): # Tests the plain iterator style of as_completed iteration to # ensure that the first future awaited resolves to the first diff --git a/Misc/NEWS.d/next/Library/2026-09-23-19-59-03.gh-issue-158004.p68swW.rst b/Misc/NEWS.d/next/Library/2026-09-23-19-59-03.gh-issue-158004.p68swW.rst new file mode 100644 index 00000000000000..532fc2330c0208 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-23-19-59-03.gh-issue-158004.p68swW.rst @@ -0,0 +1,2 @@ +Fix :func:`asyncio.as_completed` to continue yielding every awaitable after +a pending asynchronous iteration step is cancelled. From 73b2bb58e8ca5c55270d2107823164ed986e859e Mon Sep 17 00:00:00 2001 From: lipengyu Date: Mon, 19 Oct 2026 19:31:01 +0800 Subject: [PATCH 2/4] update --- Lib/asyncio/tasks.py | 14 +++++++------ Lib/test/test_asyncio/test_tasks.py | 20 +++++++++++++++++++ ...-09-23-19-59-03.gh-issue-158004.p68swW.rst | 2 +- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 259a23acebd7af..9bb54ccf931f64 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -601,11 +601,7 @@ async def __anext__(self): raise StopAsyncIteration assert self._todo_left > 0 self._todo_left -= 1 - try: - return await self._wait_for_one() - except exceptions.CancelledError: - self._todo_left += 1 - raise + return await self._wait_for_one() def __next__(self): if not self._todo_left: @@ -634,7 +630,13 @@ async def _wait_for_one(self, resolve=False): # Wait for the next future to be done and return it unless resolve is # set, in which case return either the result of the future or raise # an exception. - f = await self._done.get() + try: + f = await self._done.get() + except exceptions.CancelledError: + # Nothing was consumed; give the slot back so that a later + # iteration step can still wait for the remaining future. + self._todo_left += 1 + raise if f is None: # Dummy value from _handle_timeout(). raise exceptions.TimeoutError diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 60c3ecd2c953c7..b302efb2fe6572 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1689,6 +1689,26 @@ async def main(): results = self.loop.run_until_complete(main()) self.assertCountEqual(results, ['a', 'b']) + def test_as_completed_plain_iterator_cancelled_wait(self): + async def main(): + loop = asyncio.get_running_loop() + a = loop.create_future() + b = loop.create_future() + iterator = asyncio.as_completed([a, b]) + + waiter = asyncio.create_task(next(iterator)) + await asyncio.sleep(0) + waiter.cancel() + with self.assertRaises(asyncio.CancelledError): + await waiter + + a.set_result('a') + b.set_result('b') + return [await c for c in iterator] + + results = self.loop.run_until_complete(main()) + self.assertCountEqual(results, ['a', 'b']) + def test_as_completed_reverse_wait(self): # Tests the plain iterator style of as_completed iteration to # ensure that the first future awaited resolves to the first diff --git a/Misc/NEWS.d/next/Library/2026-09-23-19-59-03.gh-issue-158004.p68swW.rst b/Misc/NEWS.d/next/Library/2026-09-23-19-59-03.gh-issue-158004.p68swW.rst index 532fc2330c0208..0db4a3d4ca4c4a 100644 --- a/Misc/NEWS.d/next/Library/2026-09-23-19-59-03.gh-issue-158004.p68swW.rst +++ b/Misc/NEWS.d/next/Library/2026-09-23-19-59-03.gh-issue-158004.p68swW.rst @@ -1,2 +1,2 @@ Fix :func:`asyncio.as_completed` to continue yielding every awaitable after -a pending asynchronous iteration step is cancelled. +a pending iteration step is cancelled. From 4b29b6946ac7f4d5268b6982726ec843f389035f Mon Sep 17 00:00:00 2001 From: lipengyu Date: Mon, 19 Oct 2026 19:39:39 +0800 Subject: [PATCH 3/4] update --- Lib/asyncio/tasks.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 9bb54ccf931f64..ea91ae3c6a4bb5 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -633,8 +633,6 @@ async def _wait_for_one(self, resolve=False): try: f = await self._done.get() except exceptions.CancelledError: - # Nothing was consumed; give the slot back so that a later - # iteration step can still wait for the remaining future. self._todo_left += 1 raise if f is None: From 704a0b1d26f4b3224239492a69a2dc3fa2d86f9c Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Tue, 20 Oct 2026 16:35:16 +0530 Subject: [PATCH 4/4] Update Lib/asyncio/tasks.py --- Lib/asyncio/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index ea91ae3c6a4bb5..d10d485a9a1146 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -632,7 +632,7 @@ async def _wait_for_one(self, resolve=False): # an exception. try: f = await self._done.get() - except exceptions.CancelledError: + except: self._todo_left += 1 raise if f is None: