Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
features:
- |
Add ``min`` parameter to ``wait_exponential_jitter`` to set a minimum wait
time floor, consistent with ``wait_exponential``. Also accept ``timedelta``
for ``max``, ``jitter``, and ``min`` parameters.
16 changes: 9 additions & 7 deletions tenacity/wait.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,26 +240,28 @@ def __call__(self, retry_state: "RetryCallState") -> float:
class wait_exponential_jitter(wait_base):
"""Wait strategy that applies exponential backoff and jitter.

It allows for a customized initial wait, maximum wait and jitter.
It allows for a customized initial wait, maximum wait, jitter and minimum.

This implements the strategy described here:
https://cloud.google.com/storage/docs/retry-strategy

The wait time is min(initial * 2**n + random.uniform(0, jitter), maximum)
The wait time is max(min, min(initial * 2**n + random.uniform(0, jitter), maximum))
where n is the retry count.
"""

def __init__(
self,
initial: float = 1,
max: float = _utils.MAX_WAIT,
max: _utils.time_unit_type = _utils.MAX_WAIT,
exp_base: float = 2,
jitter: float = 1,
jitter: _utils.time_unit_type = 1,
min: _utils.time_unit_type = 0,
) -> None:
self.initial = initial
self.max = max
self.max = _utils.to_seconds(max)
self.exp_base = exp_base
self.jitter = jitter
self.jitter = _utils.to_seconds(jitter)
self.min = _utils.to_seconds(min)

def __call__(self, retry_state: "RetryCallState") -> float:
jitter = random.uniform(0, self.jitter)
Expand All @@ -268,4 +270,4 @@ def __call__(self, retry_state: "RetryCallState") -> float:
result = self.initial * exp + jitter
except OverflowError:
result = self.max
return max(0, min(result, self.max))
return max(max(0, self.min), min(result, self.max))
23 changes: 23 additions & 0 deletions tests/test_tenacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,29 @@ def test_wait_exponential_jitter(self) -> None:
fn = tenacity.wait_exponential_jitter()
fn(make_retry_state(0, 0))

def test_wait_exponential_jitter_min(self) -> None:
fn = tenacity.wait_exponential_jitter(initial=1, max=60, jitter=1, min=5)
for _ in range(1000):
# Even for attempt 1 (base wait=1 + jitter 0..1 = 1..2), min=5 applies
self._assert_inclusive_range(fn(make_retry_state(1, 0)), 5, 5)
self._assert_inclusive_range(fn(make_retry_state(2, 0)), 5, 5)
self._assert_inclusive_range(fn(make_retry_state(3, 0)), 5, 5)
# For attempt 4, base wait=8 + jitter 0..1 = 8..9, above min
self._assert_inclusive_range(fn(make_retry_state(4, 0)), 8, 9)

def test_wait_exponential_jitter_timedelta(self) -> None:
from datetime import timedelta

fn = tenacity.wait_exponential_jitter(
max=timedelta(seconds=60),
jitter=timedelta(seconds=1),
min=timedelta(seconds=5),
)
for _ in range(1000):
self._assert_inclusive_range(fn(make_retry_state(1, 0)), 5, 5)
self._assert_inclusive_range(fn(make_retry_state(5, 0)), 16, 17)
self.assertEqual(fn(make_retry_state(7, 0)), 60)

def test_wait_retry_state_attributes(self) -> None:
class ExtractCallState(Exception):
pass
Expand Down
Loading