Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 5bbef60

Browse files
committed
feat: respect RetryInfo metadata from retryable exceptions
1 parent fd176f0 commit 5bbef60

2 files changed

Lines changed: 25 additions & 3 deletions

File tree

google/cloud/bigtable/data/_helpers.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from google.api_core import exceptions as core_exceptions
2626
from google.api_core.retry import exponential_sleep_generator
2727
from google.api_core.retry import RetryFailureReason
28+
from google.rpc.error_details_pb2 import RetryInfo
2829
from google.cloud.bigtable.data.exceptions import RetryExceptionGroup
2930

3031
if TYPE_CHECKING:
@@ -288,11 +289,10 @@ def set_next(self, next_value: float):
288289
self._next_override = next_value
289290

290291
def __next__(self) -> float:
292+
next_backoff = next(self.subgenerator)
291293
if self._next_override is not None:
292294
next_backoff = self._next_override
293295
self._next_override = None
294-
else:
295-
next_backoff = next(self.subgenerator)
296296
self.history.append(next_backoff)
297297
return next_backoff
298298

@@ -308,3 +308,15 @@ def get_attempt_backoff(self, attempt_idx) -> float:
308308
if attempt_idx < 0:
309309
raise IndexError("received negative attempt number")
310310
return self.history[attempt_idx]
311+
312+
def set_from_exception_info(self, retry_info: RetryInfo):
313+
"""
314+
Use a RetryInfo object to set the next sleep time.
315+
316+
If a problem is encountered, this method does nothing.
317+
"""
318+
try:
319+
retry_seconds = retry_info.retry_delay.ToTimedelta().total_seconds()
320+
self.set_next(retry_seconds)
321+
except Exception:
322+
pass

google/cloud/bigtable/data/_metrics/tracked_retry.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from grpc import StatusCode
2727
from google.api_core.exceptions import GoogleAPICallError
2828
from google.api_core.retry import RetryFailureReason
29+
from google.rpc.error_details_pb2 import RetryInfo
2930
from google.cloud.bigtable.data.exceptions import _MutateRowsIncomplete
3031
from google.cloud.bigtable.data._helpers import _retry_exception_factory
3132
from google.cloud.bigtable.data._metrics import ActiveOperationMetric
@@ -43,10 +44,14 @@
4344

4445
def _track_retryable_error(
4546
operation: ActiveOperationMetric,
47+
backoff_generator: TrackedBackoffGenerator,
4648
) -> Callable[[Exception], None]:
4749
"""
4850
Used as input to api_core.Retry classes, to track when retryable errors are encountered
4951
52+
If an excemption is encountered with Retryinfo set, it will inform the backoff generator
53+
to give it a chance to override the next backoff value
54+
5055
Should be passed as on_error callback
5156
"""
5257

@@ -59,6 +64,11 @@ def wrapper(exc: Exception) -> None:
5964
rpc_error.initial_metadata()
6065
)
6166
operation.add_response_metadata({k: v for k, v in metadata})
67+
# check for RetryInfo:
68+
if exc.details:
69+
info_matches = [field for field in exc.details if isinstance(field, RetryInfo)]
70+
if info_matches:
71+
backoff_generator.set_from_exception_info(info_matches[0])
6272
except Exception:
6373
# ignore errors in metadata collection
6474
pass
@@ -127,7 +137,7 @@ def tracked_retry(
127137
kwargs.pop("sleep_generator", None)
128138
return retry_fn(
129139
sleep_generator=operation.backoff_generator,
130-
on_error=_track_retryable_error(operation),
140+
on_error=_track_retryable_error(operation, operation.backoff_generator),
131141
exception_factory=_track_terminal_error(operation, in_exception_factory),
132142
**kwargs,
133143
)

0 commit comments

Comments
 (0)