google / google/adk-python

AsyncClient.aclose() task destroyed during cleanup - Missing LLM client cleanup in Runner.close()

Abierto
#3,550 4 comentarios 0 reacciones 2 asignados Reclamado por @junseon-yoo Ver en GitHub
core needs review planned
Lenguaje dominante
Python
Estrellas
21.5k
Forks
4k
Merge medio
1 d 14 h
PR fusionados (30 d)
37

Descripción

**Describe the bug**

When running ADK agents in production with `google-genai>=1.50.1`, I'm seeing recurring asyncio errors about tasks being destroyed while pending during cleanup. The error happens when the genai `AsyncClient.aclose()` method is called during garbage collection.

This occurs because the `Gemini` class uses `@cached_property` for `api_client` and `_live_api_client`, but these clients are never explicitly closed in `Runner.close()`. Starting from genai 1.50.1, custom httpx clients won't be closed automatically, so when the Gemini instance is garbage collected, the aclose() task starts but gets destroyed before completion.

**To Reproduce**

Steps to reproduce the behavior:

1. Install `google-adk==1.18.0` and `google-genai==1.50.1`
2. Run the following minimal code:

```python
"""
Minimal reproduction for ADK Issue #3550

Bug: AsyncClient.__del__ creates orphaned asyncio tasks when Runner.close()
is not called before event loop shutdown.

Setup environment variables:
export GOOGLE_GENAI_USE_VERTEXAI=true
export GOOGLE_CLOUD_PROJECT="your-project-id"
export GOOGLE_CLOUD_LOCATION="your-location"

"""
import asyncio
import gc

from google.adk.agents import LlmAgent
from google.adk.models.google_llm import Gemini
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types

async def test():
agent = LlmAgent(
model=Gemini(model="gemini-2.0-flash-exp"),
name="test"
)

runner = Runner(
app_name="test",
agent=agent,
session_service=InMemorySessionService()
)

session = await runner.session_service.create_session(
app_name="test",
user_id="user1"
)

content = types.Content(
role='user',
parts=[types.Part(text='hello')]
)

async for event in runner.run_async(
user_id="user1",
session_id=session.id,
new_message=content
):
pass

# Bug: NOT calling await runner.close()
del runner
gc.collect()

if __name__ == '__main__':
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.set_debug(True)

loop.run_until_complete(test())

pending = asyncio.all_tasks(loop)
print(f"\nOrphaned tasks: {len(pending)}")

loop.close()
```

3. Run the script
4. Error stacktrace:

```
Orphaned tasks: 2
Task was destroyed but it is pending!
source_traceback: Object created at (most recent call last):
```

**Expected behavior**

`Runner.close()` should clean up all resources including LLM client connections, and no asyncio warnings should appear during cleanup.

**Screenshots**

N/A

**Desktop (please complete the following information):**

- OS: Linux (production), macOS (local)
- Python version: Python 3.11
- ADK version: 1.18.0

**Model Information:**

- Are you using LiteLLM: No
- Which model is being used: gemini-2.5-flash

**Additional context**

The issue is in `runners.py:1298-1300` where `Runner.close()` only cleans up toolsets:

```python
async def close(self):
"""Closes the runner."""
await self._cleanup_toolsets(self._collect_toolset(self.agent))
# Missing: LLM client cleanup
```

Root cause in `google/adk/models/google_llm.py`:
- Line 196-208: `api_client` cached property is never closed
- Line 240-246: `_live_api_client` cached property is never closed

Related to #1112 and #2804 but specifically affects LLM clients rather than MCP toolsets.

Workaround attempts that didn't work:
- Upgrading genai 1.49.0 → 1.50.1
- Adding shutdown cleanup in FastAPI (only fixes app shutdown, not per-request cleanup)
- Log filtering (masks the symptom)

Proposed fix following the existing `_cleanup_toolsets` pattern:
1. Add `BaseLlm.aclose()` method (default no-op for backward compatibility)
2. Implement `Gemini.aclose()` to close cached clients
3. Add `Runner._collect_llm_models()` to gather LLM instances
4. Add `Runner._cleanup_llm_models()` to close them
5. Update `Runner.close()` to cleanup both toolsets and LLM clients

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.