From 62e2f3b1581bbb927f856cd84927b9a1747b4c59 Mon Sep 17 00:00:00 2001 From: lipengyu Date: Wed, 23 Sep 2026 22:49:53 +0800 Subject: [PATCH] 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.