diff --git a/redis/backoff.py b/redis/backoff.py index f612d60704..e236764d71 100644 --- a/redis/backoff.py +++ b/redis/backoff.py @@ -110,5 +110,20 @@ def compute(self, failures: int) -> float: 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 compute(self, failures: int) -> float: + return min(self._cap, random.random() * self._base * 2**failures) + + def default_backoff(): return EqualJitterBackoff() diff --git a/tests/test_backoff.py b/tests/test_backoff.py new file mode 100644 index 0000000000..0a491276ff --- /dev/null +++ b/tests/test_backoff.py @@ -0,0 +1,18 @@ +from unittest.mock import Mock + +import pytest + +from redis.backoff import ExponentialWithJitterBackoff + + +def test_exponential_with_jitter_backoff(monkeypatch: pytest.MonkeyPatch) -> None: + mock_random = Mock(side_effect=[0.25, 0.5, 0.75, 1.0, 0.9]) + monkeypatch.setattr("random.random", mock_random) + + bo = ExponentialWithJitterBackoff(cap=5, base=1) + + assert bo.compute(0) == 0.25 # min(5, 0.25*2^0) + assert bo.compute(1) == 1.0 # min(5, 0.5*2^1) + assert bo.compute(2) == 3.0 # min(5, 0.75*2^2) + assert bo.compute(3) == 5.0 # min(5, 1*2^3) + assert bo.compute(4) == 5.0 # min(5, 0.9*2^4)