_exponential_backoff.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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. # The default amount of retry attempts
  17. _DEFAULT_RETRY_TOTAL_ATTEMPTS = 3
  18. # The default initial backoff period (1.0 second).
  19. _DEFAULT_INITIAL_INTERVAL_SECONDS = 1.0
  20. # The default randomization factor (0.1 which results in a random period ranging
  21. # between 10% below and 10% above the retry interval).
  22. _DEFAULT_RANDOMIZATION_FACTOR = 0.1
  23. # The default multiplier value (2 which is 100% increase per back off).
  24. _DEFAULT_MULTIPLIER = 2.0
  25. """Exponential Backoff Utility
  26. This is a private module that implements the exponential back off algorithm.
  27. It can be used as a utility for code that needs to retry on failure, for example
  28. an HTTP request.
  29. """
  30. class ExponentialBackoff:
  31. """An exponential backoff iterator. This can be used in a for loop to
  32. perform requests with exponential backoff.
  33. Args:
  34. total_attempts Optional[int]:
  35. The maximum amount of retries that should happen.
  36. The default value is 3 attempts.
  37. initial_wait_seconds Optional[int]:
  38. The amount of time to sleep in the first backoff. This parameter
  39. should be in seconds.
  40. The default value is 1 second.
  41. randomization_factor Optional[float]:
  42. The amount of jitter that should be in each backoff. For example,
  43. a value of 0.1 will introduce a jitter range of 10% to the
  44. current backoff period.
  45. The default value is 0.1.
  46. multiplier Optional[float]:
  47. The backoff multipler. This adjusts how much each backoff will
  48. increase. For example a value of 2.0 leads to a 200% backoff
  49. on each attempt. If the initial_wait is 1.0 it would look like
  50. this sequence [1.0, 2.0, 4.0, 8.0].
  51. The default value is 2.0.
  52. """
  53. def __init__(
  54. self,
  55. total_attempts=_DEFAULT_RETRY_TOTAL_ATTEMPTS,
  56. initial_wait_seconds=_DEFAULT_INITIAL_INTERVAL_SECONDS,
  57. randomization_factor=_DEFAULT_RANDOMIZATION_FACTOR,
  58. multiplier=_DEFAULT_MULTIPLIER,
  59. ):
  60. self._total_attempts = total_attempts
  61. self._initial_wait_seconds = initial_wait_seconds
  62. self._current_wait_in_seconds = self._initial_wait_seconds
  63. self._randomization_factor = randomization_factor
  64. self._multiplier = multiplier
  65. self._backoff_count = 0
  66. def __iter__(self):
  67. self._backoff_count = 0
  68. self._current_wait_in_seconds = self._initial_wait_seconds
  69. return self
  70. def __next__(self):
  71. if self._backoff_count >= self._total_attempts:
  72. raise StopIteration
  73. self._backoff_count += 1
  74. jitter_variance = self._current_wait_in_seconds * self._randomization_factor
  75. jitter = random.uniform(
  76. self._current_wait_in_seconds - jitter_variance,
  77. self._current_wait_in_seconds + jitter_variance,
  78. )
  79. time.sleep(jitter)
  80. self._current_wait_in_seconds *= self._multiplier
  81. return self._backoff_count
  82. @property
  83. def total_attempts(self):
  84. """The total amount of backoff attempts that will be made."""
  85. return self._total_attempts
  86. @property
  87. def backoff_count(self):
  88. """The current amount of backoff attempts that have been made."""
  89. return self._backoff_count