-
Notifications
You must be signed in to change notification settings - Fork 754
Open
Labels
priority: p2Moderately-important priority. Fix may not be included in next release.Moderately-important priority. Fix may not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.Error or flaw in code with unintended results or allowing sub-optimal usage patterns.
Description
If BaseApiClient fails initialisation (e.g. due to conflicting arguments like project and api_key), the _http_options attribute is never set. When the GC later cleans up this partially initialised object, del schedules aclose(), which crashes when trying to access the missing _http_options.
Environment details
- Programming language: Python
- OS: macOS
- Language runtime version: 3.12.0
- Package version: 1.59.0
Steps to reproduce
import asyncio
import gc
from google.genai._api_client import BaseApiClient
async def main():
try:
# trigger ValueError early in __init__
BaseApiClient(project="A", api_key="B")
except ValueError:
pass
# force GC to trigger __del__ and the subsequent crash
gc.collect()
if __name__ == "__main__":
asyncio.run(main())
>> Task exception was never retrieved
future: <Task finished name='Task-2' coro=<BaseApiClient.aclose() done, defined at .../google/genai/_api_client.py:1900> exception=AttributeError("'BaseApiClient' object has no attribute '_http_options'")>
Traceback (most recent call last):
File ".../google/genai/_api_client.py", line 1904, in aclose
if not self._http_options.httpx_async_client:
^^^^^^^^^^^^^^^^^^
AttributeError: 'BaseApiClient' object has no attribute '_http_options'Suggested changes
Handle partial initialization gracefully in close() and aclose()
def close(self) -> None:
"""Closes the API client."""
# Let users close the custom client explicitly by themselves. Otherwise,
# close the client when the object is garbage collected.
# Guard against partial initialization if __init__ failed.
try:
options, client = self._http_options, self._httpx_client
except AttributeError:
return
if not options.httpx_client:
client.close()
async def aclose(self) -> None:
"""Closes the API async client."""
# Let users close the custom client explicitly by themselves. Otherwise,
# close the client when the object is garbage collected.
# Guard against partial initialization if __init__ failed.
try:
options = self._http_options
except AttributeError:
return
try:
async_http_client = self._async_httpx_client
except AttributeError:
async_http_client = None
if async_http_client and not options.httpx_async_client:
await async_http_client.aclose()
try:
aio_http_session = self._aiohttp_session
except AttributeError:
aio_http_session = None
if aio_http_session and not options.aiohttp_client:
await aio_http_session.close()
def __del__(self) -> None:
"""Closes the API client when the object is garbage collected.
ADK uses this client so cannot rely on the genai.[Async]Client.__del__
for cleanup.
"""
try:
self.close()
except Exception: # pylint: disable=broad-except
pass
try:
asyncio.get_running_loop().create_task(self.aclose())
except Exception: # pylint: disable=broad-except
passMetadata
Metadata
Assignees
Labels
priority: p2Moderately-important priority. Fix may not be included in next release.Moderately-important priority. Fix may not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.Error or flaw in code with unintended results or allowing sub-optimal usage patterns.