27
27
from google .protobuf .internal .enum_type_wrapper import EnumTypeWrapper
28
28
29
29
from google .api_core import datetime_helpers
30
+ from google .api_core .exceptions import Aborted
30
31
from google .cloud ._helpers import _date_from_iso8601_date
31
32
from google .cloud .spanner_v1 import TypeCode
32
33
from google .cloud .spanner_v1 import ExecuteSqlRequest
33
34
from google .cloud .spanner_v1 import JsonObject
34
35
from google .cloud .spanner_v1 .request_id_header import with_request_id
36
+ from google .rpc .error_details_pb2 import RetryInfo
37
+
38
+ import random
35
39
36
40
# Validation error messages
37
41
NUMERIC_MAX_SCALE_ERR_MSG = (
@@ -466,13 +470,19 @@ def _retry(
466
470
delay = 2 ,
467
471
allowed_exceptions = None ,
468
472
beforeNextRetry = None ,
473
+ deadline = None ,
469
474
):
470
475
"""
471
- Retry a function with a specified number of retries, delay between retries, and list of allowed exceptions.
476
+ Retry a specified function with different logic based on the type of exception raised.
477
+
478
+ If the exception is of type google.api_core.exceptions.Aborted,
479
+ apply an alternate retry strategy that relies on the provided deadline value instead of a fixed number of retries.
480
+ For all other exceptions, retry the function up to a specified number of times.
472
481
473
482
Args:
474
483
func: The function to be retried.
475
484
retry_count: The maximum number of times to retry the function.
485
+ deadline: This will be used in case of Aborted transactions.
476
486
delay: The delay in seconds between retries.
477
487
allowed_exceptions: A tuple of exceptions that are allowed to occur without triggering a retry.
478
488
Passing allowed_exceptions as None will lead to retrying for all exceptions.
@@ -481,13 +491,21 @@ def _retry(
481
491
The result of the function if it is successful, or raises the last exception if all retries fail.
482
492
"""
483
493
retries = 0
484
- while retries <= retry_count :
494
+ while True :
485
495
if retries > 0 and beforeNextRetry :
486
496
beforeNextRetry (retries , delay )
487
497
488
498
try :
489
499
return func ()
490
500
except Exception as exc :
501
+ if isinstance (exc , Aborted ) and deadline is not None :
502
+ if (
503
+ allowed_exceptions is not None
504
+ and allowed_exceptions .get (exc .__class__ ) is not None
505
+ ):
506
+ retries += 1
507
+ _delay_until_retry (exc , deadline = deadline , attempts = retries )
508
+ continue
491
509
if (
492
510
allowed_exceptions is None or exc .__class__ in allowed_exceptions
493
511
) and retries < retry_count :
@@ -529,6 +547,61 @@ def _metadata_with_leader_aware_routing(value, **kw):
529
547
return ("x-goog-spanner-route-to-leader" , str (value ).lower ())
530
548
531
549
550
+ def _delay_until_retry (exc , deadline , attempts ):
551
+ """Helper for :meth:`Session.run_in_transaction`.
552
+
553
+ Detect retryable abort, and impose server-supplied delay.
554
+
555
+ :type exc: :class:`google.api_core.exceptions.Aborted`
556
+ :param exc: exception for aborted transaction
557
+
558
+ :type deadline: float
559
+ :param deadline: maximum timestamp to continue retrying the transaction.
560
+
561
+ :type attempts: int
562
+ :param attempts: number of call retries
563
+ """
564
+
565
+ cause = exc .errors [0 ]
566
+ now = time .time ()
567
+ if now >= deadline :
568
+ raise
569
+
570
+ delay = _get_retry_delay (cause , attempts )
571
+ print (now , delay , deadline )
572
+ if delay is not None :
573
+ if now + delay > deadline :
574
+ raise
575
+
576
+ time .sleep (delay )
577
+
578
+
579
+ def _get_retry_delay (cause , attempts ):
580
+ """Helper for :func:`_delay_until_retry`.
581
+
582
+ :type exc: :class:`grpc.Call`
583
+ :param exc: exception for aborted transaction
584
+
585
+ :rtype: float
586
+ :returns: seconds to wait before retrying the transaction.
587
+
588
+ :type attempts: int
589
+ :param attempts: number of call retries
590
+ """
591
+ if hasattr (cause , "trailing_metadata" ):
592
+ metadata = dict (cause .trailing_metadata ())
593
+ else :
594
+ metadata = {}
595
+ retry_info_pb = metadata .get ("google.rpc.retryinfo-bin" )
596
+ if retry_info_pb is not None :
597
+ retry_info = RetryInfo ()
598
+ retry_info .ParseFromString (retry_info_pb )
599
+ nanos = retry_info .retry_delay .nanos
600
+ return retry_info .retry_delay .seconds + nanos / 1.0e9
601
+
602
+ return 2 ** attempts + random .random ()
603
+
604
+
532
605
class AtomicCounter :
533
606
def __init__ (self , start_value = 0 ):
534
607
self .__lock = threading .Lock ()
0 commit comments