From f74c5910e186ed9a5f642325ce74f0e5564d1a3a Mon Sep 17 00:00:00 2001 From: abhinav-phi Date: Wed, 30 Sep 2026 07:36:34 +0530 Subject: [PATCH] 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``.