-
Notifications
You must be signed in to change notification settings - Fork 65
feat: Added rst_stream exception handling for ReadRows. #1298
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -16,13 +16,14 @@ | |||||
| """ | ||||||
| from __future__ import annotations | ||||||
|
|
||||||
| from typing import Sequence, List, Tuple, TYPE_CHECKING, Union | ||||||
| from typing import Callable, Sequence, List, Tuple, TYPE_CHECKING, Union | ||||||
| import time | ||||||
| import enum | ||||||
| from collections import namedtuple | ||||||
| from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery | ||||||
|
|
||||||
| from google.api_core import exceptions as core_exceptions | ||||||
| from google.api_core import retry as retries | ||||||
| from google.api_core.retry import RetryFailureReason | ||||||
| from google.cloud.bigtable.data.exceptions import RetryExceptionGroup | ||||||
|
|
||||||
|
|
@@ -47,6 +48,15 @@ | |||||
| # used by every data client as a default project name for testing on Bigtable emulator. | ||||||
| _DEFAULT_BIGTABLE_EMULATOR_CLIENT = "google-cloud-bigtable-emulator" | ||||||
|
|
||||||
| # Internal error messages that can be retried during ReadRows. Internal error messages with this error | ||||||
| # text should be streated as Unavailable error messages with the same error text, and will therefore be | ||||||
| # treated as Unavailable errors rather than Internal errors. | ||||||
| _RETRYABLE_INTERNAL_ERROR_MESSAGES = ( | ||||||
| "rst_stream", | ||||||
| "rst stream", | ||||||
| "received unexpected eos on data frame from server", | ||||||
| ) | ||||||
|
|
||||||
| # used to identify an active bigtable resource that needs to be warmed through PingAndWarm | ||||||
| # each instance/app_profile_id pair needs to be individually tracked | ||||||
| _WarmedInstanceKey = namedtuple( | ||||||
|
|
@@ -122,6 +132,33 @@ def _retry_exception_factory( | |||||
| return source_exc, cause_exc | ||||||
|
|
||||||
|
|
||||||
| def _read_rows_predicate_with_exceptions(*exception_types: type[Exception]) -> Callable[[Exception], bool]: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we call this something more specific, since it's not really tied to read_rows? Maybe _rst_stream_aware_predicate? |
||||||
| """A custom retry predicate for ReadRows. | ||||||
|
|
||||||
| This predicate treats Internal error messages with RST_STREAM errors as | ||||||
| ServiceUnavailable errors and will retry them if the Unavailable exception is retryable. | ||||||
|
|
||||||
| Args: | ||||||
| retryable_exceptions: tuple of Exception types to be retried during operation | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The argument name in the docstring (
Suggested change
|
||||||
|
|
||||||
| Returns: | ||||||
| Callable[[Exception], bool]: A retry predicate that takes in an exception and | ||||||
| returns whether or not that exception is retryable | ||||||
| """ | ||||||
| is_exception_type = retries.if_exception_type(*exception_types) | ||||||
|
|
||||||
| def predicate(exception: Exception) -> bool: | ||||||
| return (isinstance(exception, core_exceptions.InternalServerError) and exception.message.lower() in _RETRYABLE_INTERNAL_ERROR_MESSAGES) or is_exception_type(exception) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation checks if the entire error message is one of the strings in
Suggested change
|
||||||
|
|
||||||
| # Treating RST_STREAM internal errors as unavailable errors is only done if ServiceUnavailable is one of the | ||||||
| # given exception types. If InternalServerError is also a retryable exception, we don't necessarily need the | ||||||
| # custom predicate either. | ||||||
| if core_exceptions.ServiceUnavailable in exception_types and core_exceptions.InternalServerError not in exception_types: | ||||||
| return predicate | ||||||
|
|
||||||
| return is_exception_type | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find the logic a bit hard to follow, with the multiple predicate functions being passed around. Can we do something like this, to put all the cases in one place? or even this? |
||||||
|
|
||||||
|
|
||||||
| def _get_timeouts( | ||||||
| operation: float | TABLE_DEFAULT, | ||||||
| attempt: float | None | TABLE_DEFAULT, | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a typo in the comment.
streatedshould betreated.