Skip to content

fix(google): release the genai client when the realtime session closes - #6643

Merged
davidzhao merged 1 commit into
livekit:mainfrom
biztex:fix/google-realtime-client-leak
Aug 2, 2026
Merged

fix(google): release the genai client when the realtime session closes#6643
davidzhao merged 1 commit into
livekit:mainfrom
biztex:fix/google-realtime-client-leak

Conversation

@biztex

@biztex biztex commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Problem

google.realtime.RealtimeSession constructs its own genai.Client (realtime_api.py:488), but aclose() only tears down the websocket and the pending futures — the client is never closed. Its http clients therefore stay alive until the garbage collector reaches them, and both google/genai/client.py:186 and google/genai/_api_client.py:2234 implement __del__ as:

def __del__(self) -> None:
    try:
        asyncio.get_running_loop().create_task(self.aclose())
    except Exception:
        pass

So the close is scheduled as a task on whatever event loop happens to be running when the collector fires — not necessarily the loop the client was created on, and possibly during shutdown.

The visible symptom is in CI: the unit-tests job intermittently fails the fail_on_leaked_tasks check with pending BaseApiClient.aclose / AsyncClient.aclose / httpx AsyncClient.aclose tasks, attributed to whichever test happened to be finishing when the collector ran (I hit it on an unrelated AMD-only PR, #6639).

Fix

RealtimeSession.aclose() now awaits self._client.aio.aclose() after the rest of its teardown, so the session releases what it allocated. Failures there are logged rather than raised, since aclose is a teardown path.

tests/test_plugin_google_realtime.py builds sessions through an async context manager that closes them, instead of leaving them to the collector — otherwise the tests keep leaking clients regardless of the plugin fix. Added a test asserting the close actually reaches the genai client.

Verification

ruff format --check, ruff check, check_types.py (mypy strict) and the full pytest --unit suite all pass locally.

RealtimeSession creates its own genai Client but aclose() only tore down the
websocket, so the http clients stayed alive until the collector ran
AsyncClient.__del__ - which does
asyncio.get_running_loop().create_task(self.aclose()), creating pending
tasks on whatever event loop happens to be running at that moment. In CI
this surfaces as unrelated tests failing the leaked-task check.
@biztex
biztex requested a review from a team as a code owner July 31, 2026 08:30

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

@davidzhao davidzhao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lg

@davidzhao
davidzhao merged commit c74fa1c into livekit:main Aug 2, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants