From f74c5910e186ed9a5f642325ce74f0e5564d1a3a Mon Sep 17 00:00:00 2001 From: abhinav-phi Date: Wed, 30 Sep 2026 07:36:34 +0530 Subject: [PATCH 1/3] gh-158097: Make test_staggered_race_with_eager_tasks deterministic staggered_race() awaits the coroutines it is given inline, so the "await asyncio.sleep(0)" in the test's fail() helper suspended the run_one_coro() task running it and left a scheduled resumption behind. If the asyncio.sleep(1) winner (started 0.25s late by the stagger delay) completes before that resumption runs, the winner's cancel() sets _must_cancel and the resumption throws CancelledError into fail(), so excs[2] reports a CancelledError instead of a ValueError. Pending timers are only moved onto the ready queue at the top of BaseEventLoop._run_once() and are appended after the handles already queued, so the t=0.50s stagger timer and the t=1.25s sleep(1) timer run in due-time order. When both are due in the same _run_once() - which is what a busy CI machine causes - the stagger timer starts fail(), which immediately suspends again, and the very next handle is the winner. Raise straight away instead, so the ValueError is stored in excs[2] in the same step that starts the coroutine and there is no longer a scheduled resumption for a cancellation to overtake. The outcome no longer depends on the event loop getting another iteration. --- Lib/test/test_asyncio/test_eager_task_factory.py | 7 ++++++- .../Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst diff --git a/Lib/test/test_asyncio/test_eager_task_factory.py b/Lib/test/test_asyncio/test_eager_task_factory.py index 594a48baa32e522..fd9ce08d43ee340 100644 --- a/Lib/test/test_asyncio/test_eager_task_factory.py +++ b/Lib/test/test_asyncio/test_eager_task_factory.py @@ -217,7 +217,12 @@ def test_staggered_race_with_eager_tasks(self): # See https://github.com/python/cpython/issues/124309 async def fail(): - await asyncio.sleep(0) + # Fail without suspending first. staggered_race() awaits the + # coroutines inline and cancels every one of them that has not + # finished once another one wins, so a coroutine that yields before + # raising may be cancelled instead, and excs[2] would report a + # CancelledError. Raising right away keeps the outcome + # independent of how busy the event loop is. raise ValueError("no good") async def blocked(): diff --git a/Misc/NEWS.d/next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst b/Misc/NEWS.d/next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst new file mode 100644 index 000000000000000..ccf4ecfcfdc1901 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst @@ -0,0 +1,2 @@ +Fix a rare flakiness in ``test_staggered_race_with_eager_tasks`` in +``test_asyncio``. From 729b614564e92442bb102fb3762e8082a0a038f6 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Mon, 2 Nov 2026 23:27:30 +0100 Subject: [PATCH 2/3] Update Lib/test/test_asyncio/test_eager_task_factory.py --- Lib/test/test_asyncio/test_eager_task_factory.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_asyncio/test_eager_task_factory.py b/Lib/test/test_asyncio/test_eager_task_factory.py index fd9ce08d43ee340..73496de81a58efe 100644 --- a/Lib/test/test_asyncio/test_eager_task_factory.py +++ b/Lib/test/test_asyncio/test_eager_task_factory.py @@ -217,12 +217,9 @@ def test_staggered_race_with_eager_tasks(self): # See https://github.com/python/cpython/issues/124309 async def fail(): - # Fail without suspending first. staggered_race() awaits the - # coroutines inline and cancels every one of them that has not - # finished once another one wins, so a coroutine that yields before - # raising may be cancelled instead, and excs[2] would report a - # CancelledError. Raising right away keeps the outcome - # independent of how busy the event loop is. + # Raise without suspending: staggered_race() cancels unfinished + # coroutines once another one wins, so yielding first could turn + # this into a CancelledError when the event loop is busy. raise ValueError("no good") async def blocked(): From bedaaca980af8dee544de321337526e03f8670eb Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Mon, 2 Nov 2026 23:28:07 +0100 Subject: [PATCH 3/3] Delete Misc/NEWS.d/next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst --- .../next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 Misc/NEWS.d/next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst diff --git a/Misc/NEWS.d/next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst b/Misc/NEWS.d/next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst deleted file mode 100644 index ccf4ecfcfdc1901..000000000000000 --- a/Misc/NEWS.d/next/Tests/2026-09-30-00-00-00.gh-issue-158097.pcB3mO.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix a rare flakiness in ``test_staggered_race_with_eager_tasks`` in -``test_asyncio``.