Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Async clients do not document and implement retries correctly (and hence retries don't work) #11405

Closed
@lucistanescu

Description

@lucistanescu

I had only looked at the artifactregistry, functions and resourcemanager packages, but I suspected the issue may be applicable to other packages. In fact, it seems to be applicable to all of them.

The *AsyncClient objects must use a google.api_core.retry_async.AsyncRetry object, rather than a google.api_core.retry.Retry object. However, all methods document use of the latter, which is incorrect. Some even use a default google.api_core.retry.Retry object, which would have no effect whatsoever for async code. For example, paclages/google-cloud-resource-manager/google/cloud/resourcemanager_v3/services/projects/async_client.py has:

class ProjectsAsyncClient:
    async def get_project(
        self,
        request: Optional[Union[projects.GetProjectRequest, dict]] = None,
        *,
        name: Optional[str] = None,
        retry: OptionalRetry = gapic_v1.method.DEFAULT,
        timeout: Union[float, object] = gapic_v1.method.DEFAULT,
        metadata: Sequence[Tuple[str, str]] = (),
    ) -> projects.Project:
        [...]
        rpc = gapic_v1.method_async.wrap_method(
            self._client._transport.get_project,
            default_retry=retries.Retry(
                initial=0.1,
                maximum=60.0,
                multiplier=1.3,
                predicate=retries.if_exception_type(
                    core_exceptions.ServiceUnavailable,
                ),
                deadline=60.0,
            ),
            default_timeout=60.0,
            client_info=DEFAULT_CLIENT_INFO,
        )

The same issue is with google.cloud.functions_v1.CloudFunctionsServiceAsyncClient and with google.cloud.artifactregistry_v1beta2.ArtifactRegistryAsyncClient. Others, like google.cloud.functions_v2.FunctionServiceAsyncClient do not pass a default_retry at all, which effectively means no retry by default, but they incorrectly document that a google.api_core.retry.Retry instance should be used, both via the docstrings (and hence generated documention), as well as via the annotation:

try:
    OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault]
except AttributeError:  # pragma: NO COVER
    OptionalRetry = Union[retries.Retry, object]  # type: ignore

The reason it doesn't work is that the google.api_core.retry.Retry decorator will effectively wrap the call that returns a coroutine, without awaiting on it, so it doesn't have a chance to actually catch any exceptions, silently not doing anything useful.

Searching in the codebase for packages affected by passing an incorrect object to default_retry with

grep -r default_retry=retries.Retry packages/ | grep async_client.py | cut -d/ -f2 | sort -u

yields:

google-cloud-access-approval
google-cloud-advisorynotifications
google-cloud-alloydb
google-cloud-api-gateway
google-cloud-apigee-connect
google-cloud-apigee-registry
google-cloud-artifact-registry
google-cloud-bigquery-datapolicies
google-cloud-billing
google-cloud-billing-budgets
google-cloud-certificate-manager
google-cloud-confidentialcomputing
google-cloud-contentwarehouse
google-cloud-datacatalog
google-cloud-datalabeling
google-cloud-dataplex
google-cloud-deploy
google-cloud-discoveryengine
google-cloud-documentai
google-cloud-edgecontainer
google-cloud-enterpriseknowledgegraph
google-cloud-essential-contacts
google-cloud-functions
google-cloud-gke-multicloud
google-cloud-ids
google-cloud-rapidmigrationassessment
google-cloud-recommendations-ai
google-cloud-recommender
google-cloud-resource-manager
google-cloud-resource-settings
google-cloud-secret-manager
google-cloud-securitycenter
google-cloud-service-control
google-cloud-shell
google-cloud-storageinsights
google-cloud-support
google-cloud-talent
google-cloud-vmwareengine
google-cloud-webrisk
google-cloud-websecurityscanner
google-cloud-workstations
google-maps-mapsplatformdatasets

Apart from fixing the documentation and the code, I would also like to suggest raising a warning in the google.api_core.retry.retry_target function (also used by the Retry object) if the return value of target() is awaitable (inspect.isawaitable()). Given that the documentation has been incorrect, it is possible that people have been using it incorrectly and they wouldn't know about it, until reliability is affected.

Metadata

Metadata

Assignees

Labels

priority: p2Moderately-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.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions