backoff.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import random
  2. from abc import ABC, abstractmethod
  3. # Maximum backoff between each retry in seconds
  4. DEFAULT_CAP = 0.512
  5. # Minimum backoff between each retry in seconds
  6. DEFAULT_BASE = 0.008
  7. class AbstractBackoff(ABC):
  8. """Backoff interface"""
  9. def reset(self):
  10. """
  11. Reset internal state before an operation.
  12. `reset` is called once at the beginning of
  13. every call to `Retry.call_with_retry`
  14. """
  15. pass
  16. @abstractmethod
  17. def compute(self, failures: int) -> float:
  18. """Compute backoff in seconds upon failure"""
  19. pass
  20. class ConstantBackoff(AbstractBackoff):
  21. """Constant backoff upon failure"""
  22. def __init__(self, backoff: float) -> None:
  23. """`backoff`: backoff time in seconds"""
  24. self._backoff = backoff
  25. def __hash__(self) -> int:
  26. return hash((self._backoff,))
  27. def __eq__(self, other) -> bool:
  28. if not isinstance(other, ConstantBackoff):
  29. return NotImplemented
  30. return self._backoff == other._backoff
  31. def compute(self, failures: int) -> float:
  32. return self._backoff
  33. class NoBackoff(ConstantBackoff):
  34. """No backoff upon failure"""
  35. def __init__(self) -> None:
  36. super().__init__(0)
  37. class ExponentialBackoff(AbstractBackoff):
  38. """Exponential backoff upon failure"""
  39. def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE):
  40. """
  41. `cap`: maximum backoff time in seconds
  42. `base`: base backoff time in seconds
  43. """
  44. self._cap = cap
  45. self._base = base
  46. def __hash__(self) -> int:
  47. return hash((self._base, self._cap))
  48. def __eq__(self, other) -> bool:
  49. if not isinstance(other, ExponentialBackoff):
  50. return NotImplemented
  51. return self._base == other._base and self._cap == other._cap
  52. def compute(self, failures: int) -> float:
  53. return min(self._cap, self._base * 2**failures)
  54. class FullJitterBackoff(AbstractBackoff):
  55. """Full jitter backoff upon failure"""
  56. def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
  57. """
  58. `cap`: maximum backoff time in seconds
  59. `base`: base backoff time in seconds
  60. """
  61. self._cap = cap
  62. self._base = base
  63. def __hash__(self) -> int:
  64. return hash((self._base, self._cap))
  65. def __eq__(self, other) -> bool:
  66. if not isinstance(other, FullJitterBackoff):
  67. return NotImplemented
  68. return self._base == other._base and self._cap == other._cap
  69. def compute(self, failures: int) -> float:
  70. return random.uniform(0, min(self._cap, self._base * 2**failures))
  71. class EqualJitterBackoff(AbstractBackoff):
  72. """Equal jitter backoff upon failure"""
  73. def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
  74. """
  75. `cap`: maximum backoff time in seconds
  76. `base`: base backoff time in seconds
  77. """
  78. self._cap = cap
  79. self._base = base
  80. def __hash__(self) -> int:
  81. return hash((self._base, self._cap))
  82. def __eq__(self, other) -> bool:
  83. if not isinstance(other, EqualJitterBackoff):
  84. return NotImplemented
  85. return self._base == other._base and self._cap == other._cap
  86. def compute(self, failures: int) -> float:
  87. temp = min(self._cap, self._base * 2**failures) / 2
  88. return temp + random.uniform(0, temp)
  89. class DecorrelatedJitterBackoff(AbstractBackoff):
  90. """Decorrelated jitter backoff upon failure"""
  91. def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
  92. """
  93. `cap`: maximum backoff time in seconds
  94. `base`: base backoff time in seconds
  95. """
  96. self._cap = cap
  97. self._base = base
  98. self._previous_backoff = 0
  99. def __hash__(self) -> int:
  100. return hash((self._base, self._cap))
  101. def __eq__(self, other) -> bool:
  102. if not isinstance(other, DecorrelatedJitterBackoff):
  103. return NotImplemented
  104. return self._base == other._base and self._cap == other._cap
  105. def reset(self) -> None:
  106. self._previous_backoff = 0
  107. def compute(self, failures: int) -> float:
  108. max_backoff = max(self._base, self._previous_backoff * 3)
  109. temp = random.uniform(self._base, max_backoff)
  110. self._previous_backoff = min(self._cap, temp)
  111. return self._previous_backoff
  112. class ExponentialWithJitterBackoff(AbstractBackoff):
  113. """Exponential backoff upon failure, with jitter"""
  114. def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
  115. """
  116. `cap`: maximum backoff time in seconds
  117. `base`: base backoff time in seconds
  118. """
  119. self._cap = cap
  120. self._base = base
  121. def __hash__(self) -> int:
  122. return hash((self._base, self._cap))
  123. def __eq__(self, other) -> bool:
  124. if not isinstance(other, ExponentialWithJitterBackoff):
  125. return NotImplemented
  126. return self._base == other._base and self._cap == other._cap
  127. def compute(self, failures: int) -> float:
  128. return min(self._cap, random.random() * self._base * 2**failures)
  129. def default_backoff():
  130. return EqualJitterBackoff()