graphql-python / gql

A GraphQL client in Python
https://gql.readthedocs.io
MIT License
1.56k stars 180 forks source link

execute_async calls inside an asyncio loop raises exception "TypeError: argument of type 'NoneType' is not iterable" #490

Closed andrebariani closed 4 months ago

andrebariani commented 4 months ago

Describe the bug I'm running multiple queries within an asynio loop inside a class injected by a fastapi route, and with its own Client as _graphql_client. This client was initialized through the cognite sdk. The number of queries depends on how many filters I've splitted to make the concurrent calls, for example if I have a large interval of time like 5 years, I split the filter to 5 filters of a 1 year interval. I've decided to go with this approach since Cognite has a limitation with the request size and I'm not able to retrieve large amounts of data in a single request.

The queries are run through the execute function from the session returned by connect_async(reconnecting=True). These queries also run through paged content which result in a large number of calls as well, and they run through these pages by the async_query_coroutine wrapper function. Inside this class I'm initializing a connection as the following:

tasks = [
    self._graphql_client.async_query_coroutine(
        query, queryName, filter
    )
    for filter in filters
]

session = await self._graphql_client.client.connect_async(reconnecting=True)
self._graphql_client._session = session
results = await asyncio.gather(*tasks)
await self._graphql_client.client.close_async()

But when I run a lot of requests through, it raises the following exception, logged from fastapi:

2024-07-12 15:18:26,963 - gql.transport.aiohttp - INFO - >>> {"query": "...", "variables": {...}}
2024-07-12 15:18:27,021 - gql.transport.aiohttp - INFO - <<< 
2024-07-12 15:18:27,022 - backoff - INFO - Backing off _execute_once(...) for 1.4s (TypeError: argument of type 'NoneType' is not iterable)

The problem is that it doesn't seem to retry, making the request return no data. I won't be able to provide the steps to reproduce since I can't provide the backend URL.

Expected behavior Since I'm using a backoff decorator with the execute call I expected the request to retry, but I'm not sure why it does not work for this specific exception. I'm also not sure if this is expected behaviour from the gql library. Let me know if there's any additional detail I can provide or how to reproduce the bug.

System info (please complete the following information):

leszekhanusz commented 4 months ago

I'm afraid that without more information, there is no way to know if your problem is coming from gql or your own code.

What I would do if I was you is to remove the backoff decorator to try to get a stack trace with the TypeError which might indicate where lies the problem.

Also, for your info, when you use asyncio.gather, it will return directly as soon as an Exception is received from one of the tasks, leaving the other running tasks unfinished. If you want all the remaining tasks to complete as well, you might be better setting return_exceptions=True

andrebariani commented 4 months ago

Setting the return_exceptions=True flag, together with the backoff decorator, seemed to have solved my issue! Now the backoff is able to get the exception and re-run the request just fine.

But I might keep testing the code before this edit as to understand what exactly was going on. If I find anything unusual that might relate to gql I'll open a new issue.

Thank you very much for the quick and helpful reply!