_exponential_backoff.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. # Copyright 2022 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import random
  15. import time
  16. from google.auth import exceptions
  17. # The default amount of retry attempts
  18. _DEFAULT_RETRY_TOTAL_ATTEMPTS = 3
  19. # The default initial backoff period (1.0 second).
  20. _DEFAULT_INITIAL_INTERVAL_SECONDS = 1.0
  21. # The default randomization factor (0.1 which results in a random period ranging
  22. # between 10% below and 10% above the retry interval).
  23. _DEFAULT_RANDOMIZATION_FACTOR = 0.1
  24. # The default multiplier value (2 which is 100% increase per back off).
  25. _DEFAULT_MULTIPLIER = 2.0
  26. """Exponential Backoff Utility
  27. This is a private module that implements the exponential back off algorithm.
  28. It can be used as a utility for code that needs to retry on failure, for example
  29. an HTTP request.
  30. """
  31. class ExponentialBackoff:
  32. """An exponential backoff iterator. This can be used in a for loop to
  33. perform requests with exponential backoff.
  34. Args:
  35. total_attempts Optional[int]:
  36. The maximum amount of retries that should happen.
  37. The default value is 3 attempts.
  38. initial_wait_seconds Optional[int]:
  39. The amount of time to sleep in the first backoff. This parameter
  40. should be in seconds.
  41. The default value is 1 second.
  42. randomization_factor Optional[float]:
  43. The amount of jitter that should be in each backoff. For example,
  44. a value of 0.1 will introduce a jitter range of 10% to the
  45. current backoff period.
  46. The default value is 0.1.
  47. multiplier Optional[float]:
  48. The backoff multipler. This adjusts how much each backoff will
  49. increase. For example a value of 2.0 leads to a 200% backoff
  50. on each attempt. If the initial_wait is 1.0 it would look like
  51. this sequence [1.0, 2.0, 4.0, 8.0].
  52. The default value is 2.0.
  53. """
  54. def __init__(
  55. self,
  56. total_attempts=_DEFAULT_RETRY_TOTAL_ATTEMPTS,
  57. initial_wait_seconds=_DEFAULT_INITIAL_INTERVAL_SECONDS,
  58. randomization_factor=_DEFAULT_RANDOMIZATION_FACTOR,
  59. multiplier=_DEFAULT_MULTIPLIER,
  60. ):
  61. if total_attempts < 1:
  62. raise exceptions.InvalidValue(
  63. f"total_attempts must be greater than or equal to 1 but was {total_attempts}"
  64. )
  65. self._total_attempts = total_attempts
  66. self._initial_wait_seconds = initial_wait_seconds
  67. self._current_wait_in_seconds = self._initial_wait_seconds
  68. self._randomization_factor = randomization_factor
  69. self._multiplier = multiplier
  70. self._backoff_count = 0
  71. def __iter__(self):
  72. self._backoff_count = 0
  73. self._current_wait_in_seconds = self._initial_wait_seconds
  74. return self
  75. def __next__(self):
  76. if self._backoff_count >= self._total_attempts:
  77. raise StopIteration
  78. self._backoff_count += 1
  79. if self._backoff_count <= 1:
  80. return self._backoff_count
  81. jitter_variance = self._current_wait_in_seconds * self._randomization_factor
  82. jitter = random.uniform(
  83. self._current_wait_in_seconds - jitter_variance,
  84. self._current_wait_in_seconds + jitter_variance,
  85. )
  86. time.sleep(jitter)
  87. self._current_wait_in_seconds *= self._multiplier
  88. return self._backoff_count
  89. @property
  90. def total_attempts(self):
  91. """The total amount of backoff attempts that will be made."""
  92. return self._total_attempts
  93. @property
  94. def backoff_count(self):
  95. """The current amount of backoff attempts that have been made."""
  96. return self._backoff_count