backoff.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  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 compute(self, failures: int) -> float:
  26. return self._backoff
  27. class NoBackoff(ConstantBackoff):
  28. """No backoff upon failure"""
  29. def __init__(self) -> None:
  30. super().__init__(0)
  31. class ExponentialBackoff(AbstractBackoff):
  32. """Exponential backoff upon failure"""
  33. def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE):
  34. """
  35. `cap`: maximum backoff time in seconds
  36. `base`: base backoff time in seconds
  37. """
  38. self._cap = cap
  39. self._base = base
  40. def compute(self, failures: int) -> float:
  41. return min(self._cap, self._base * 2**failures)
  42. class FullJitterBackoff(AbstractBackoff):
  43. """Full jitter backoff upon failure"""
  44. def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
  45. """
  46. `cap`: maximum backoff time in seconds
  47. `base`: base backoff time in seconds
  48. """
  49. self._cap = cap
  50. self._base = base
  51. def compute(self, failures: int) -> float:
  52. return random.uniform(0, min(self._cap, self._base * 2**failures))
  53. class EqualJitterBackoff(AbstractBackoff):
  54. """Equal jitter backoff upon failure"""
  55. def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
  56. """
  57. `cap`: maximum backoff time in seconds
  58. `base`: base backoff time in seconds
  59. """
  60. self._cap = cap
  61. self._base = base
  62. def compute(self, failures: int) -> float:
  63. temp = min(self._cap, self._base * 2**failures) / 2
  64. return temp + random.uniform(0, temp)
  65. class DecorrelatedJitterBackoff(AbstractBackoff):
  66. """Decorrelated jitter backoff upon failure"""
  67. def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
  68. """
  69. `cap`: maximum backoff time in seconds
  70. `base`: base backoff time in seconds
  71. """
  72. self._cap = cap
  73. self._base = base
  74. self._previous_backoff = 0
  75. def reset(self) -> None:
  76. self._previous_backoff = 0
  77. def compute(self, failures: int) -> float:
  78. max_backoff = max(self._base, self._previous_backoff * 3)
  79. temp = random.uniform(self._base, max_backoff)
  80. self._previous_backoff = min(self._cap, temp)
  81. return self._previous_backoff
  82. def default_backoff():
  83. return EqualJitterBackoff()