| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- import random
- from abc import ABC, abstractmethod
- # Maximum backoff between each retry in seconds
- DEFAULT_CAP = 0.512
- # Minimum backoff between each retry in seconds
- DEFAULT_BASE = 0.008
- class AbstractBackoff(ABC):
- """Backoff interface"""
- def reset(self):
- """
- Reset internal state before an operation.
- `reset` is called once at the beginning of
- every call to `Retry.call_with_retry`
- """
- pass
- @abstractmethod
- def compute(self, failures: int) -> float:
- """Compute backoff in seconds upon failure"""
- pass
- class ConstantBackoff(AbstractBackoff):
- """Constant backoff upon failure"""
- def __init__(self, backoff: float) -> None:
- """`backoff`: backoff time in seconds"""
- self._backoff = backoff
- def __hash__(self) -> int:
- return hash((self._backoff,))
- def __eq__(self, other) -> bool:
- if not isinstance(other, ConstantBackoff):
- return NotImplemented
- return self._backoff == other._backoff
- def compute(self, failures: int) -> float:
- return self._backoff
- class NoBackoff(ConstantBackoff):
- """No backoff upon failure"""
- def __init__(self) -> None:
- super().__init__(0)
- class ExponentialBackoff(AbstractBackoff):
- """Exponential backoff upon failure"""
- def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE):
- """
- `cap`: maximum backoff time in seconds
- `base`: base backoff time in seconds
- """
- self._cap = cap
- self._base = base
- def __hash__(self) -> int:
- return hash((self._base, self._cap))
- def __eq__(self, other) -> bool:
- if not isinstance(other, ExponentialBackoff):
- return NotImplemented
- return self._base == other._base and self._cap == other._cap
- def compute(self, failures: int) -> float:
- return min(self._cap, self._base * 2**failures)
- class FullJitterBackoff(AbstractBackoff):
- """Full jitter backoff upon failure"""
- def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
- """
- `cap`: maximum backoff time in seconds
- `base`: base backoff time in seconds
- """
- self._cap = cap
- self._base = base
- def __hash__(self) -> int:
- return hash((self._base, self._cap))
- def __eq__(self, other) -> bool:
- if not isinstance(other, FullJitterBackoff):
- return NotImplemented
- return self._base == other._base and self._cap == other._cap
- def compute(self, failures: int) -> float:
- return random.uniform(0, min(self._cap, self._base * 2**failures))
- class EqualJitterBackoff(AbstractBackoff):
- """Equal jitter backoff upon failure"""
- def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
- """
- `cap`: maximum backoff time in seconds
- `base`: base backoff time in seconds
- """
- self._cap = cap
- self._base = base
- def __hash__(self) -> int:
- return hash((self._base, self._cap))
- def __eq__(self, other) -> bool:
- if not isinstance(other, EqualJitterBackoff):
- return NotImplemented
- return self._base == other._base and self._cap == other._cap
- def compute(self, failures: int) -> float:
- temp = min(self._cap, self._base * 2**failures) / 2
- return temp + random.uniform(0, temp)
- class DecorrelatedJitterBackoff(AbstractBackoff):
- """Decorrelated jitter backoff upon failure"""
- def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
- """
- `cap`: maximum backoff time in seconds
- `base`: base backoff time in seconds
- """
- self._cap = cap
- self._base = base
- self._previous_backoff = 0
- def __hash__(self) -> int:
- return hash((self._base, self._cap))
- def __eq__(self, other) -> bool:
- if not isinstance(other, DecorrelatedJitterBackoff):
- return NotImplemented
- return self._base == other._base and self._cap == other._cap
- def reset(self) -> None:
- self._previous_backoff = 0
- def compute(self, failures: int) -> float:
- max_backoff = max(self._base, self._previous_backoff * 3)
- temp = random.uniform(self._base, max_backoff)
- self._previous_backoff = min(self._cap, temp)
- return self._previous_backoff
- class ExponentialWithJitterBackoff(AbstractBackoff):
- """Exponential backoff upon failure, with jitter"""
- def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
- """
- `cap`: maximum backoff time in seconds
- `base`: base backoff time in seconds
- """
- self._cap = cap
- self._base = base
- def __hash__(self) -> int:
- return hash((self._base, self._cap))
- def __eq__(self, other) -> bool:
- if not isinstance(other, ExponentialWithJitterBackoff):
- return NotImplemented
- return self._base == other._base and self._cap == other._cap
- def compute(self, failures: int) -> float:
- return min(self._cap, random.random() * self._base * 2**failures)
- def default_backoff():
- return EqualJitterBackoff()
|