_exponential_backoff.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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 asyncio
  15. import random
  16. import time
  17. from google.auth import exceptions
  18. # The default amount of retry attempts
  19. _DEFAULT_RETRY_TOTAL_ATTEMPTS = 3
  20. # The default initial backoff period (1.0 second).
  21. _DEFAULT_INITIAL_INTERVAL_SECONDS = 1.0
  22. # The default randomization factor (0.1 which results in a random period ranging
  23. # between 10% below and 10% above the retry interval).
  24. _DEFAULT_RANDOMIZATION_FACTOR = 0.1
  25. # The default multiplier value (2 which is 100% increase per back off).
  26. _DEFAULT_MULTIPLIER = 2.0
  27. """Exponential Backoff Utility
  28. This is a private module that implements the exponential back off algorithm.
  29. It can be used as a utility for code that needs to retry on failure, for example
  30. an HTTP request.
  31. """
  32. class _BaseExponentialBackoff:
  33. """An exponential backoff iterator base class.
  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. @property
  72. def total_attempts(self):
  73. """The total amount of backoff attempts that will be made."""
  74. return self._total_attempts
  75. @property
  76. def backoff_count(self):
  77. """The current amount of backoff attempts that have been made."""
  78. return self._backoff_count
  79. def _reset(self):
  80. self._backoff_count = 0
  81. self._current_wait_in_seconds = self._initial_wait_seconds
  82. def _calculate_jitter(self):
  83. jitter_variance = self._current_wait_in_seconds * self._randomization_factor
  84. jitter = random.uniform(
  85. self._current_wait_in_seconds - jitter_variance,
  86. self._current_wait_in_seconds + jitter_variance,
  87. )
  88. return jitter
  89. class ExponentialBackoff(_BaseExponentialBackoff):
  90. """An exponential backoff iterator. This can be used in a for loop to
  91. perform requests with exponential backoff.
  92. """
  93. def __init__(self, *args, **kwargs):
  94. super(ExponentialBackoff, self).__init__(*args, **kwargs)
  95. def __iter__(self):
  96. self._reset()
  97. return self
  98. def __next__(self):
  99. if self._backoff_count >= self._total_attempts:
  100. raise StopIteration
  101. self._backoff_count += 1
  102. if self._backoff_count <= 1:
  103. return self._backoff_count
  104. jitter = self._calculate_jitter()
  105. time.sleep(jitter)
  106. self._current_wait_in_seconds *= self._multiplier
  107. return self._backoff_count
  108. class AsyncExponentialBackoff(_BaseExponentialBackoff):
  109. """An async exponential backoff iterator. This can be used in a for loop to
  110. perform async requests with exponential backoff.
  111. """
  112. def __init__(self, *args, **kwargs):
  113. super(AsyncExponentialBackoff, self).__init__(*args, **kwargs)
  114. def __aiter__(self):
  115. self._reset()
  116. return self
  117. async def __anext__(self):
  118. if self._backoff_count >= self._total_attempts:
  119. raise StopAsyncIteration
  120. self._backoff_count += 1
  121. if self._backoff_count <= 1:
  122. return self._backoff_count
  123. jitter = self._calculate_jitter()
  124. await asyncio.sleep(jitter)
  125. self._current_wait_in_seconds *= self._multiplier
  126. return self._backoff_count