AsyncClient.aclose() task destroyed during cleanup - Missing LLM client cleanup in Runner.close()
- Ngôn ngữ chính
- Python
- Star
- 21.5k
- Fork
- 4k
- Merge trung bình
- 1 ngày 14 giờ
- Pull request đã merge (30 ngày)
- 37
Mô tả
**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
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.