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

Commit fb84b4b

Browse files
committed
refacotred tracked exception factory
1 parent 2a42b90 commit fb84b4b

4 files changed

Lines changed: 38 additions & 11 deletions

File tree

google/cloud/bigtable/data/_async/_mutate_rows.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import google.cloud.bigtable_v2.types.bigtable as types_pb
2424
import google.cloud.bigtable.data.exceptions as bt_exceptions
2525
from google.cloud.bigtable.data._helpers import _attempt_timeout_generator
26-
from google.cloud.bigtable.data._helpers import _retry_exception_factory
26+
from google.cloud.bigtable.data._helpers import _tracked_exception_factory
2727

2828
# mutate_rows requests are limited to this number of mutations
2929
from google.cloud.bigtable.data.mutations import _MUTATE_ROWS_REQUEST_MUTATION_LIMIT
@@ -108,9 +108,7 @@ def __init__(
108108
self.is_retryable,
109109
metric.backoff_generator,
110110
operation_timeout,
111-
exception_factory=partial(
112-
_retry_exception_factory, operation=metric
113-
),
111+
exception_factory=_tracked_exception_factory(self._operation_metric),
114112
)
115113
# initialize state
116114
self.timeout_generator = _attempt_timeout_generator(

google/cloud/bigtable/data/_async/_read_rows.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from google.cloud.bigtable.data.exceptions import _RowSetComplete
3232
from google.cloud.bigtable.data.exceptions import _ResetRow
3333
from google.cloud.bigtable.data._helpers import _attempt_timeout_generator
34-
from google.cloud.bigtable.data._helpers import _retry_exception_factory
34+
from google.cloud.bigtable.data._helpers import _tracked_exception_factory
3535

3636
from google.api_core import retry as retries
3737

@@ -122,7 +122,7 @@ def start_operation(self) -> CrossSync.Iterable[Row]:
122122
self._predicate,
123123
self._operation_metric.backoff_generator,
124124
self.operation_timeout,
125-
exception_factory=partial(_retry_exception_factory, operation=self._operation_metric),
125+
exception_factory=_tracked_exception_factory(self._operation_metric),
126126
)
127127

128128
def _read_rows_attempt(self) -> CrossSync.Iterable[Row]:

google/cloud/bigtable/data/_async/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
from google.cloud.bigtable.data._helpers import _WarmedInstanceKey
7474
from google.cloud.bigtable.data._helpers import _CONCURRENCY_LIMIT
7575
from google.cloud.bigtable.data._helpers import _retry_exception_factory
76+
from google.cloud.bigtable.data._helpers import _tracked_exception_factory
7677
from google.cloud.bigtable.data._helpers import _validate_timeouts
7778
from google.cloud.bigtable.data._helpers import _get_error_type
7879
from google.cloud.bigtable.data._helpers import _get_retryable_errors
@@ -1391,7 +1392,7 @@ async def execute_rpc():
13911392
predicate,
13921393
operation_metric.backoff_generator,
13931394
operation_timeout,
1394-
exception_factory=partial(_retry_exception_factory, operation=operation_metric)
1395+
exception_factory=_tracked_exception_factory(operation_metric),
13951396
)
13961397

13971398
@CrossSync.convert(replace_symbols={"MutationsBatcherAsync": "MutationsBatcher"})

google/cloud/bigtable/data/_helpers.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"""
1717
from __future__ import annotations
1818

19-
from typing import Sequence, List, Tuple, TYPE_CHECKING, Union
19+
from typing import Callable, Sequence, List, Tuple, TYPE_CHECKING, Union
2020
import time
2121
import enum
2222
from collections import namedtuple
@@ -31,6 +31,7 @@
3131
import grpc
3232
from google.cloud.bigtable.data._async.client import _DataApiTargetAsync
3333
from google.cloud.bigtable.data._sync_autogen.client import _DataApiTarget
34+
from google.cloud.bigtable.data._metrics.data_model import ActiveOperationMetric
3435

3536
"""
3637
Helper functions used in various places in the library.
@@ -90,7 +91,6 @@ def _retry_exception_factory(
9091
exc_list: list[Exception],
9192
reason: RetryFailureReason,
9293
timeout_val: float | None,
93-
operation: "ActiveOperationMetric" | None=None,
9494
) -> tuple[Exception, Exception | None]:
9595
"""
9696
Build retry error based on exceptions encountered during operation
@@ -118,8 +118,35 @@ def _retry_exception_factory(
118118
# use the retry exception group as the cause of the exception
119119
cause_exc: Exception | None = RetryExceptionGroup(exc_list) if exc_list else None
120120
source_exc.__cause__ = cause_exc
121-
if operation:
121+
return source_exc, cause_exc
122+
123+
124+
def _tracked_exception_factory(
125+
operation: "ActiveOperationMetric",
126+
) -> Callable[[list[Exception], RetryFailureReason, float | None], tuple[Exception, Exception | None]]:
127+
"""
128+
wraps and extends _retry_exception_factory to add client-side metrics tracking.
129+
130+
When the rpc raises a terminal error, record any discovered metadata and finalize
131+
the associated operation
132+
133+
Used by streaming rpcs, which can't always be perfectly captured by context managers
134+
for operation termination.
135+
136+
Args:
137+
exc_list: list of exceptions encountered during operation
138+
is_timeout: whether the operation failed due to timeout
139+
timeout_val: the operation timeout value in seconds, for constructing
140+
the error message
141+
operation: the operation to finalize when an exception is built
142+
Returns:
143+
tuple[Exception, Exception|None]:
144+
tuple of the exception to raise, and a cause exception if applicabl
145+
"""
146+
def wrapper(exc_list: list[Exception], reason: RetryFailureReason, timeout_val: float | None) -> tuple[Exception, Exception | None]:
147+
source_exc, cause_exc = _retry_exception_factory(exc_list, reason, timeout_val)
122148
try:
149+
# record metadata from failed rpc
123150
if isinstance(source_exc, core_exceptions.GoogleAPICallError) and source_exc.errors:
124151
rpc_error = source_exc.errors[-1]
125152
metadata = list(rpc_error.trailing_metadata()) + list(rpc_error.initial_metadata())
@@ -128,7 +155,8 @@ def _retry_exception_factory(
128155
# ignore errors in metadata collection
129156
pass
130157
operation.end_with_status(source_exc)
131-
return source_exc, cause_exc
158+
return source_exc, cause_exc
159+
return wrapper
132160

133161

134162
def _get_timeouts(

0 commit comments

Comments
 (0)