tests/test_plugin_openai_stt_context.py::test_use_realtime_defaults_to_the_only_transport_a_model_has[gpt-realtime-whisper-True] intermittently fails with:
Failed: Test leaked tasks:
Coroutine : BaseApiClient.aclose google/genai/_api_client.py:2213
Coroutine : AsyncClient.aclose google/genai/client.py:152
That test is synchronous and never touches google. The tasks come from tests/test_plugin_google_realtime.py — the only module in the unit suite that constructs genai clients.
Cause. AsyncClient.__del__ and BaseApiClient.__del__ both do this unconditionally, with no check for a client that was already closed:
# google/genai/client.py:186, _api_client.py:2248
def __del__(self) -> None:
try:
asyncio.get_running_loop().create_task(self.aclose())
except Exception:
pass
RealtimeSession.aclose() closes client.aio (realtime_api.py:898), but that does not disarm the finalizer — the object stays garbage until the collector reaches it, which can be several modules later. fail_on_leaked_tasks diffs asyncio.all_tasks() around each test, so the task is charged to whatever test happens to be running then.
Why it surfaces now. It reproduces only on the CPython build CI uses — 3.12.13 fails every run, 3.12.3 passes every run, on the same machine with the same locked dependencies. Which test gets blamed depends on GC timing, so anything that shifts allocation can move it. It currently lands on #6340, which adds a test module and nothing else related.
Fix. Settle the finalizers inside the test that owns them, so they cannot escape into a later module.
tests/test_plugin_openai_stt_context.py::test_use_realtime_defaults_to_the_only_transport_a_model_has[gpt-realtime-whisper-True]intermittently fails with:That test is synchronous and never touches google. The tasks come from
tests/test_plugin_google_realtime.py— the only module in the unit suite that constructs genai clients.Cause.
AsyncClient.__del__andBaseApiClient.__del__both do this unconditionally, with no check for a client that was already closed:RealtimeSession.aclose()closesclient.aio(realtime_api.py:898), but that does not disarm the finalizer — the object stays garbage until the collector reaches it, which can be several modules later.fail_on_leaked_tasksdiffsasyncio.all_tasks()around each test, so the task is charged to whatever test happens to be running then.Why it surfaces now. It reproduces only on the CPython build CI uses — 3.12.13 fails every run, 3.12.3 passes every run, on the same machine with the same locked dependencies. Which test gets blamed depends on GC timing, so anything that shifts allocation can move it. It currently lands on #6340, which adds a test module and nothing else related.
Fix. Settle the finalizers inside the test that owns them, so they cannot escape into a later module.