Skip to content

fix: prevent RuntimeError: Event loop is closed for Gemini.api_client (#5538)#5543

Open
weiguangli-io wants to merge 1 commit intogoogle:mainfrom
weiguangli-io:fix/event-loop-closed-api-client
Open

fix: prevent RuntimeError: Event loop is closed for Gemini.api_client (#5538)#5543
weiguangli-io wants to merge 1 commit intogoogle:mainfrom
weiguangli-io:fix/event-loop-closed-api-client

Conversation

@weiguangli-io
Copy link
Copy Markdown
Contributor

Problem

In multi-threaded deployments (e.g. Vertex AI Agent Engine), each incoming
request runs in a fresh OS thread with its own asyncio event loop. When the
same Gemini instance is reused across requests, the previously
@cached_property api_client (a google.genai.Client) is bound to the
old, now-closed event loop via its underlying aiohttp / httpx session:

RuntimeError: Event loop is closed

Root cause: @cached_property stores the first Client created permanently on
the instance, regardless of which event loop is currently running.

Fixes #5538.

Solution

Replace @cached_property on api_client and _live_api_client with a
regular @property that stores a (loop, client) pair in __dict__. On each
access:

  • If the running event loop matches the cached loop → return the cached client
    (no extra allocation on the hot path).
  • Otherwise → create a fresh Client and cache it for the current loop.

This means each OS thread's event loop gets an isolated Client instance with
its own HTTP session, while calls within the same loop still share one client.

No public API is changed. Subclasses that override api_client with
@cached_property continue to work; the docstring hint to use @property
instead has been removed as the base class now handles this automatically.

Testing

  • Added test_api_client_cached_per_event_loop: two threads each run their own
    event loop and share a single Gemini instance; asserts they receive distinct
    Client objects.
  • Added test_api_client_cached_within_same_event_loop: asserts a single
    Client is reused for repeated calls within one event loop.
  • Existing tests continue to pass.

In multi-threaded deployments (e.g. Vertex AI Agent Engine) each incoming
request runs in a fresh OS thread with its own asyncio event loop. When the
same Gemini instance is reused across requests the previously cached
google.genai.Client (and its underlying aiohttp / httpx session) is bound to
the old, now-closed event loop, causing:

  RuntimeError: Event loop is closed

Replace @cached_property on api_client and _live_api_client with a regular
@Property that caches the Client keyed by the current running event loop.
Each distinct event loop (i.e. each new thread) receives a fresh Client with
a fresh HTTP session, while calls within the same loop still reuse the cached
instance. No public API is changed.

Fixes: google#5538
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.

RuntimeError: Event loop is closed caused by Gemini.api_client @cached_property

1 participant