Repository navigation
fix(firestore): resume streams after a retryable error instead of raising AttributeError - #18773
Open
abhishekacharya200 wants to merge 1 commit into
Open
abhishekacharya200 wants to merge 1 commit into
abhishekacharya200 wants to merge 1 commit into
Conversation
…sing AttributeError When a query or aggregation stream failed midway with a retryable error, _retry_query_after_exception looked for the default retry policy on transport.run_query (or run_aggregation_query). That is the raw stub, which has no _retry, so the stream raised AttributeError instead of restarting. The policy lives on the wrapped method the client registers in transport._wrapped_methods, which is where the generated client reads it too. With retry=None the helper called None._predicate(exc), so callers got an AttributeError instead of the original error. Retries are off in that case, so the error is now raised as is. The unit tests mocked run_query as a callable carrying _retry, which the real transport never does. They now model the real transport, and new cases cover retry=None.
Contributor
There was a problem hiding this comment.
Code Review
This pull request updates the retry logic in aggregation.py and query.py to retrieve the default retry policy from the wrapped methods (_wrapped_methods) on the transport object, aligning with the real gRPC transport behavior. It also adds handling for when retry is explicitly set to None to disable retries, and includes corresponding unit tests to verify this behavior. There are no review comments, so I have no feedback to provide.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18768
When a query or aggregation stream failed midway with a retryable error such as
UNAVAILABLE,_retry_query_after_exceptionlooked for the default retry policy ontransport.run_query(orrun_aggregation_query). On the real transports that is the raw stub, which has no_retry, so the stream raisedAttributeError: '_UnaryStreamMultiCallable' object has no attribute '_retry'instead of restarting. Withretry=Nonethe helper calledNone._predicate(exc), so callers got anAttributeErrorinstead of the original error. This is the traceback reported in googleapis/python-firestore#596 and airbytehq/airbyte#70837.This reads the policy from
transport._wrapped_methods[transport.run_query](andrun_aggregation_query), the same place the generated client reads it for every call, and raises the error as is whenretryisNone. I checked the lookup on both the gRPC and the REST transport, and both return theRetrythe client was built with. The async query and aggregation have no restart logic, so they are unchanged.One thing worth knowing: this lets the existing restart path run for the first time. A query restarts after the last snapshot it returned, so nothing is repeated. An aggregation reruns the whole query, which is what the existing unit test expects, so if a stream ever failed after its result had already been delivered, the restarted call would deliver it again. I kept that design as it is, but I'm happy to make aggregations raise instead once they have yielded a result, if you'd prefer.
With the script from the issue (the real client against a local gRPC stand in, no mocks):
The unit test helpers
_query_stream_w_retriable_exc_helperand_aggregation_query_stream_w_retriable_exc_helpermockedrun_queryas a callable that carries_retry, which the real transport never does, and the truthy mock returned by the predicate made every error look retryable. They now model the real transport: a raw stub without_retry, plus_wrapped_methodsholding a realRetry. Newtest_query_stream_w_retriable_exc_w_retry_noneandtest_aggregation_query_stream_w_retriable_exc_w_retry_nonecoverretry=None. Onmain, 6 of these tests fail (the defaults, read time and retry=None cases for both); with this changetests/unit/v1/test_query.pyandtests/unit/v1/test_aggregation.pypass (118 passed), and the noxfile'sruff==0.14.14import and format checks andflake8report nothing.