Skip to content

feat: Added rst_stream exception handling for ReadRows.#1298

Merged
gkevinzheng merged 4 commits intov3_stagingfrom
rst-stream
Feb 25, 2026
Merged

feat: Added rst_stream exception handling for ReadRows.#1298
gkevinzheng merged 4 commits intov3_stagingfrom
rst-stream

Conversation

@gkevinzheng
Copy link
Contributor

According to go/rst_stream, INTERNAL errors with error messages related to an rst_stream error should be interpreted as UNAVAILABLE errors instead of internal errors. This PR creates a custom retry predicate to allow retrying of INTERNAL errors with rst_stream specific error messages if the ServiceUnavailable exception is allowed to be retried.

@gkevinzheng gkevinzheng requested a review from a team as a code owner February 24, 2026 19:10
@gkevinzheng gkevinzheng requested a review from a team February 24, 2026 19:10
@product-auto-label product-auto-label bot added the size: m Pull request size is medium. label Feb 24, 2026
@product-auto-label product-auto-label bot added the api: bigtable Issues related to the googleapis/python-bigtable API. label Feb 24, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @gkevinzheng, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the resilience of Bigtable ReadRows operations by implementing a specialized error handling mechanism. It addresses a known issue where certain INTERNAL errors, specifically those indicating an rst_stream problem, should be treated as UNAVAILABLE errors to allow for proper retries. By introducing a custom retry predicate, the system can now intelligently reclassify these specific internal errors, improving the robustness of data retrieval processes without broadly retrying all INTERNAL errors.

Highlights

  • Custom Retry Predicate for ReadRows: Introduced a specialized retry predicate for ReadRows operations to correctly reclassify specific INTERNAL errors, particularly those related to rst_stream issues, as UNAVAILABLE for retry purposes.
  • Defined Retryable Internal Error Messages: A new constant, _RETRYABLE_INTERNAL_ERROR_MESSAGES, was added to list specific rst_stream related error strings that should trigger this reclassification.
  • Integration into ReadRows Implementations: The new custom predicate has been integrated into both the asynchronous (_async/_read_rows.py) and synchronous (_sync_autogen/_read_rows.py) ReadRows implementations.
  • Expanded Unit Test Coverage: Unit tests were updated and expanded to validate the behavior of the new custom retry predicate, ensuring it functions as expected across various error scenarios.
Changelog
  • google/cloud/bigtable/data/_async/_read_rows.py
    • Imported _read_rows_predicate_with_exceptions and core_exceptions.
    • Updated the _predicate assignment in __init__ to utilize the new custom predicate.
  • google/cloud/bigtable/data/_helpers.py
    • Imported Callable and retries for type hinting and retry functionality.
    • Defined _RETRYABLE_INTERNAL_ERROR_MESSAGES tuple containing rst_stream related error strings.
    • Added _read_rows_predicate_with_exceptions function to generate a retry predicate that checks for specific InternalServerError messages.
  • google/cloud/bigtable/data/_sync_autogen/_read_rows.py
    • Imported _read_rows_predicate_with_exceptions.
    • Updated the _predicate assignment in __init__ to utilize the new custom predicate.
  • tests/unit/data/_async/test_client.py
    • Modified the parametrize decorator for test_customizable_retryable_errors to include an is_read_rows_fn flag.
    • Updated the mock patch target for predicate_builder to dynamically select between if_exception_type and _read_rows_predicate_with_exceptions based on is_read_rows_fn.
    • Adjusted a comment for clarity regarding the retry_call_args assertion.
  • tests/unit/data/_sync_autogen/test_client.py
    • Modified the parametrize decorator for test_customizable_retryable_errors to include an is_read_rows_fn flag.
    • Updated the mock patch target for predicate_builder to dynamically select between if_exception_type and _read_rows_predicate_with_exceptions based on is_read_rows_fn.
  • tests/unit/data/test__helpers.py
    • Added a new test class TestReadRowsPredicateWithException.
    • Included a parameterized test within TestReadRowsPredicateWithException to verify the behavior of _read_rows_predicate_with_exceptions under various exception types and messages.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces custom exception handling for ReadRows to treat certain INTERNAL errors related to rst_stream as UNAVAILABLE errors, making them retryable. The implementation adds a new retry predicate _read_rows_predicate_with_exceptions and updates the _ReadRowsOperation to use it. The changes are well-tested, with new unit tests for the predicate and updates to existing tests to accommodate the new logic.

My review includes a few suggestions for improvement in google/cloud/bigtable/data/_helpers.py: fixing a typo in a comment, correcting a docstring, and making the error message check more robust by using a substring search. Overall, the changes look good and align with the goal described in the pull request.

_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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a typo in the comment. streated should be treated.

Suggested change
# text should be streated as Unavailable error messages with the same error text, and will therefore be
# text should be treated as Unavailable error messages with the same error text, and will therefore be

ServiceUnavailable errors and will retry them if the Unavailable exception is retryable.

Args:
retryable_exceptions: tuple of Exception types to be retried during operation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The argument name in the docstring (retryable_exceptions) does not match the function signature (exception_types). Please correct it for consistency.

Suggested change
retryable_exceptions: tuple of Exception types to be retried during operation
exception_types: tuple of Exception types to be retried during operation

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation checks if the entire error message is one of the strings in _RETRYABLE_INTERNAL_ERROR_MESSAGES. This might be too strict, as the actual error message could contain additional details. Using a substring check would be more robust to handle variations in the error message. For example, an error message like 'rst_stream with code 7' would not be caught by the current implementation.

Suggested change
return (isinstance(exception, core_exceptions.InternalServerError) and exception.message.lower() in _RETRYABLE_INTERNAL_ERROR_MESSAGES) or is_exception_type(exception)
return (isinstance(exception, core_exceptions.InternalServerError) and any(m in exception.message.lower() for m in _RETRYABLE_INTERNAL_ERROR_MESSAGES)) or is_exception_type(exception)

return source_exc, cause_exc


def _read_rows_predicate_with_exceptions(*exception_types: type[Exception]) -> Callable[[Exception], bool]:
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

if core_exceptions.ServiceUnavailable in exception_types and core_exceptions.InternalServerError not in exception_types:
return predicate

return is_exception_type
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

def _read_rows_predicate_with_exceptions(*exception_types: type[Exception]) -> Callable[[Exception], bool]:
    def predicate(exception: Exception)  -> bool:
        if isinstance(exception, exception_types):
           # handle standard retryable errors
           return True
        elif core_exceptions.ServiceUnavailable in exception_types and isinstance(exception, InternalServerError) and any(m in exception.message.lower() for m in _RETRYABLE_INTERNAL_ERROR_MESSAGES)):
            # special case: treat InternalServerError with specific message as ServiceUnavailable
            return True
        else:
            return False
    return predicate

or even this?

def _read_rows_predicate_with_exceptions(*exception_types: type[Exception]) -> Callable[[Exception], bool]:
    # predicate to check for retryable error types
    if_exception_type = retries.if_exception_type(*exception_types)
    # special case: treat InternalServerError with specific message as ServiceUnavailable
    rst_check = lambda e: core_exceptions.ServiceUnavailable in exception_types and isinstance(e, InternalServerError) and any(m in e.message.lower() for m in _RETRYABLE_INTERNAL_ERROR_MESSAGES))
  
   return lambda e: if_exception_type(e) or rst_check(e)

daniel-sanche and others added 2 commits February 25, 2026 16:26
3.9 has been removed from the kokoro base image, so we should remove the
tests from our configs

3.9 is still tested in GitHub Actions
@gkevinzheng gkevinzheng merged commit 3426dbf into v3_staging Feb 25, 2026
8 of 9 checks passed
@gkevinzheng gkevinzheng deleted the rst-stream branch February 25, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: bigtable Issues related to the googleapis/python-bigtable API. size: m Pull request size is medium.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants