From 476b1f73dccb70bdd638e6ff803b997ff1b0fddb Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Mon, 26 Oct 2026 08:27:28 -0700 Subject: [PATCH] test(images): let the settle loops yield so a finished walk leaves the set (#348) On CI (ubuntu and macOS py3.12) the runner's watchdog dumped test_a_root_that_recovers_warns_again_on_its_next_failure spinning in _settle: asyncio.gather over tasks that are already done returns without yielding, so the set's discard callback, queued when the walk finished, never ran and the loop never ended (wait_for could not fire either). Delaying discard by one loop turn reproduces it locally; both settle loops now yield one turn after each gather. --- tests/test_api_images.py | 3 +++ tests/test_api_workspace_images.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/test_api_images.py b/tests/test_api_images.py index f9357f94..7c561f1f 100644 --- a/tests/test_api_images.py +++ b/tests/test_api_images.py @@ -2431,6 +2431,9 @@ class TestSharedListing(ImageAPITestCase): async def settle(): while self.api._listing_tasks: await asyncio.gather(*list(self.api._listing_tasks)) + # A finished walk leaves the set in a done callback, which needs + # a loop turn; gather over finished tasks does not yield one. + await asyncio.sleep(0) await asyncio.wait_for(settle(), _HANG_LIMIT) diff --git a/tests/test_api_workspace_images.py b/tests/test_api_workspace_images.py index cd8fe396..dceae1f6 100644 --- a/tests/test_api_workspace_images.py +++ b/tests/test_api_workspace_images.py @@ -196,6 +196,9 @@ class TestWorkspaceImages(ImageAPITestCase): await self.get() # served at once; one background walk runs while self.api._listing_tasks: await asyncio.gather(*list(self.api._listing_tasks)) + # A finished walk leaves the set in a done callback, which needs + # a loop turn; gather over finished tasks does not yield one. + await asyncio.sleep(0) _, again = await self.get() self.assertEqual(scan.call_count, 1) self.assertIs(record.files, files)