123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- # Copyright 2022 Google LLC
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- import asyncio
- import random
- import time
- from google.auth import exceptions
- # The default amount of retry attempts
- _DEFAULT_RETRY_TOTAL_ATTEMPTS = 3
- # The default initial backoff period (1.0 second).
- _DEFAULT_INITIAL_INTERVAL_SECONDS = 1.0
- # The default randomization factor (0.1 which results in a random period ranging
- # between 10% below and 10% above the retry interval).
- _DEFAULT_RANDOMIZATION_FACTOR = 0.1
- # The default multiplier value (2 which is 100% increase per back off).
- _DEFAULT_MULTIPLIER = 2.0
- """Exponential Backoff Utility
- This is a private module that implements the exponential back off algorithm.
- It can be used as a utility for code that needs to retry on failure, for example
- an HTTP request.
- """
- class _BaseExponentialBackoff:
- """An exponential backoff iterator base class.
- Args:
- total_attempts Optional[int]:
- The maximum amount of retries that should happen.
- The default value is 3 attempts.
- initial_wait_seconds Optional[int]:
- The amount of time to sleep in the first backoff. This parameter
- should be in seconds.
- The default value is 1 second.
- randomization_factor Optional[float]:
- The amount of jitter that should be in each backoff. For example,
- a value of 0.1 will introduce a jitter range of 10% to the
- current backoff period.
- The default value is 0.1.
- multiplier Optional[float]:
- The backoff multipler. This adjusts how much each backoff will
- increase. For example a value of 2.0 leads to a 200% backoff
- on each attempt. If the initial_wait is 1.0 it would look like
- this sequence [1.0, 2.0, 4.0, 8.0].
- The default value is 2.0.
- """
- def __init__(
- self,
- total_attempts=_DEFAULT_RETRY_TOTAL_ATTEMPTS,
- initial_wait_seconds=_DEFAULT_INITIAL_INTERVAL_SECONDS,
- randomization_factor=_DEFAULT_RANDOMIZATION_FACTOR,
- multiplier=_DEFAULT_MULTIPLIER,
- ):
- if total_attempts < 1:
- raise exceptions.InvalidValue(
- f"total_attempts must be greater than or equal to 1 but was {total_attempts}"
- )
- self._total_attempts = total_attempts
- self._initial_wait_seconds = initial_wait_seconds
- self._current_wait_in_seconds = self._initial_wait_seconds
- self._randomization_factor = randomization_factor
- self._multiplier = multiplier
- self._backoff_count = 0
- @property
- def total_attempts(self):
- """The total amount of backoff attempts that will be made."""
- return self._total_attempts
- @property
- def backoff_count(self):
- """The current amount of backoff attempts that have been made."""
- return self._backoff_count
- def _reset(self):
- self._backoff_count = 0
- self._current_wait_in_seconds = self._initial_wait_seconds
- def _calculate_jitter(self):
- jitter_variance = self._current_wait_in_seconds * self._randomization_factor
- jitter = random.uniform(
- self._current_wait_in_seconds - jitter_variance,
- self._current_wait_in_seconds + jitter_variance,
- )
- return jitter
- class ExponentialBackoff(_BaseExponentialBackoff):
- """An exponential backoff iterator. This can be used in a for loop to
- perform requests with exponential backoff.
- """
- def __init__(self, *args, **kwargs):
- super(ExponentialBackoff, self).__init__(*args, **kwargs)
- def __iter__(self):
- self._reset()
- return self
- def __next__(self):
- if self._backoff_count >= self._total_attempts:
- raise StopIteration
- self._backoff_count += 1
- if self._backoff_count <= 1:
- return self._backoff_count
- jitter = self._calculate_jitter()
- time.sleep(jitter)
- self._current_wait_in_seconds *= self._multiplier
- return self._backoff_count
- class AsyncExponentialBackoff(_BaseExponentialBackoff):
- """An async exponential backoff iterator. This can be used in a for loop to
- perform async requests with exponential backoff.
- """
- def __init__(self, *args, **kwargs):
- super(AsyncExponentialBackoff, self).__init__(*args, **kwargs)
- def __aiter__(self):
- self._reset()
- return self
- async def __anext__(self):
- if self._backoff_count >= self._total_attempts:
- raise StopAsyncIteration
- self._backoff_count += 1
- if self._backoff_count <= 1:
- return self._backoff_count
- jitter = self._calculate_jitter()
- await asyncio.sleep(jitter)
- self._current_wait_in_seconds *= self._multiplier
- return self._backoff_count
|