From 81f1ba80029c16cd8b533314c2179d4233ce0788 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sat, 4 Jul 2026 21:28:06 +0530 Subject: [PATCH] test(telegram): cancel leaked conflict-retry task before fatal assertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The conflict-retry ladder schedules a background recovery task via loop.create_task(self._handle_polling_conflict(...)) on each failed start_polling. test_polling_conflict_becomes_fatal_after_retries never cancelled the last one, so under a loaded scheduler a leaked task could get a turn, re-drive the counter into the fatal branch, and fire _notify_fatal_error a second time — breaking assert_awaited_once() non-deterministically. The bounded updater.stop() guard added in this salvage introduced an extra await/scheduling yield that surfaced the latent leak in CI slice 3. Cancel the leaked task before the fatal assertions so the test is deterministic regardless of scheduler timing. --- tests/gateway/test_telegram_conflict.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/gateway/test_telegram_conflict.py b/tests/gateway/test_telegram_conflict.py index 697356f66a0..5868a0c56ff 100644 --- a/tests/gateway/test_telegram_conflict.py +++ b/tests/gateway/test_telegram_conflict.py @@ -220,6 +220,20 @@ async def test_polling_conflict_becomes_fatal_after_retries(monkeypatch): conflict("Conflict: terminated by other getUpdates request") ) + # Retries 1-4 each schedule a background recovery task via + # loop.create_task(self._handle_polling_conflict(...)) that this test + # never awaits. Cancel the last one so a leaked task can't get a + # scheduler turn under load and re-drive the counter into the fatal + # branch a second time — which would fire _notify_fatal_error twice and + # break assert_awaited_once() non-deterministically. + leaked = adapter._polling_error_task + if leaked is not None and not leaked.done(): + leaked.cancel() + try: + await leaked + except (asyncio.CancelledError, Exception): + pass + # After 5 failed retries (count 1-5 each enter the retry branch but # start_polling raises), the 6th conflict pushes count to 6 which # exceeds MAX_CONFLICT_RETRIES (5), entering the fatal branch.