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

Commit 9d63963

Browse files
committed
record last attempt in exception factory
1 parent c1baf81 commit 9d63963

3 files changed

Lines changed: 9 additions & 4 deletions

File tree

google/cloud/bigtable/data/_helpers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def _retry_exception_factory(
103103
tuple[Exception, Exception|None]:
104104
tuple of the exception to raise, and a cause exception if applicable
105105
"""
106+
exc_list = exc_list.copy()
106107
if reason == RetryFailureReason.TIMEOUT:
107108
timeout_val_str = f"of {timeout_val:0.1f}s " if timeout_val is not None else ""
108109
# if failed due to timeout, raise deadline exceeded as primary exception

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from grpc.aio import AioRpcError
3030

3131
from google.api_core.exceptions import GoogleAPICallError
32+
from google.api_core.retry import RetryFailureReason
3233
import google.cloud.bigtable.data.exceptions as bt_exceptions
3334
from google.cloud.bigtable_v2.types.response_params import ResponseParams
3435
from google.cloud.bigtable.data._helpers import TrackedBackoffGenerator
@@ -38,7 +39,6 @@
3839

3940
if TYPE_CHECKING:
4041
from google.cloud.bigtable.data._metrics.handlers._base import MetricsHandler
41-
from google.api_core.retry import RetryFailureReason
4242

4343

4444
LOGGER = logging.getLogger(__name__)
@@ -455,6 +455,10 @@ def wrapper(
455455
except Exception:
456456
# ignore errors in metadata collection
457457
pass
458+
if reason == RetryFailureReason.TIMEOUT and self.state == OperationState.ACTIVE_ATTEMPT and exc_list:
459+
# record ending attempt for timeout failures
460+
attempt_exc = exc_list[-1]
461+
self.track_retryable_error()(attempt_exc)
458462
self.end_with_status(source_exc)
459463
return source_exc, cause_exc
460464
return wrapper

tests/system/data/test_metrics_async.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ async def test_read_rows_stream_failure_unauthorized_with_retries(
605605
generator = await authorized_view.read_rows_stream(
606606
ReadRowsQuery(row_filter=FamilyNameRegexFilter("unauthorized")),
607607
retryable_errors=[PermissionDenied],
608-
operation_timeout=0.1,
608+
operation_timeout=0.5,
609609
)
610610
[_ async for _ in generator]
611611
assert e.value.grpc_status_code.name == "DEADLINE_EXCEEDED"
@@ -1257,7 +1257,7 @@ async def test_bulk_mutate_rows_failure_unauthorized_with_retries(
12571257

12581258
handler.clear()
12591259
with pytest.raises(MutationsExceptionGroup) as e:
1260-
await authorized_view.bulk_mutate_rows([entry], retryable_errors=[PermissionDenied], operation_timeout=25)
1260+
await authorized_view.bulk_mutate_rows([entry], retryable_errors=[PermissionDenied], operation_timeout=0.5)
12611261
assert len(e.value.exceptions) == 1
12621262
# validate counts
12631263
assert len(handler.completed_operations) == 1
@@ -1656,7 +1656,7 @@ async def test_mutate_row_failure_unauthorized_with_retries(
16561656
mutation = SetCell("unauthorized", b"q", b"v")
16571657

16581658
with pytest.raises(GoogleAPICallError) as e:
1659-
await authorized_view.mutate_row(row_key, [mutation], retryable_errors=[PermissionDenied], operation_timeout=0.1)
1659+
await authorized_view.mutate_row(row_key, [mutation], retryable_errors=[PermissionDenied], operation_timeout=0.5)
16601660
assert e.value.grpc_status_code.name == "DEADLINE_EXCEEDED"
16611661
# validate counts
16621662
assert len(handler.completed_operations) == 1

0 commit comments

Comments
 (0)