diff --git a/releasenotes/notes/fix-exponential-max-computation-526.yaml b/releasenotes/notes/fix-exponential-max-computation-526.yaml new file mode 100644 index 00000000..7f2138b4 --- /dev/null +++ b/releasenotes/notes/fix-exponential-max-computation-526.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Avoid computing an increasingly large power after ``wait_exponential`` has + reached its configured maximum wait. diff --git a/tenacity/wait.py b/tenacity/wait.py index 3b53da24..a662eef9 100644 --- a/tenacity/wait.py +++ b/tenacity/wait.py @@ -15,6 +15,7 @@ # limitations under the License. import abc +import math import random import typing import warnings @@ -201,8 +202,17 @@ def __init__( self.exp_base = exp_base def __call__(self, retry_state: "RetryCallState") -> float: + exponent = retry_state.attempt_number - 1 + if ( + self.multiplier > 0 + and self.max > 0 + and self.exp_base > 1 + and math.isfinite(self.max) + and exponent > math.log(self.max / self.multiplier, self.exp_base) + ): + return max(max(0, self.min), self.max) try: - exp = self.exp_base ** (retry_state.attempt_number - 1) + exp = self.exp_base**exponent result = self.multiplier * exp except OverflowError: return self.max diff --git a/tests/test_tenacity.py b/tests/test_tenacity.py index b117061e..61fd8dbb 100644 --- a/tests/test_tenacity.py +++ b/tests/test_tenacity.py @@ -359,6 +359,14 @@ def test_exponential_with_max_wait(self) -> None: self.assertEqual(r.wait(make_retry_state(8, 0)), 40) self.assertEqual(r.wait(make_retry_state(50, 0)), 40) + def test_exponential_skips_power_after_reaching_max(self) -> None: + class ExplodingPower(float): + def __pow__(self, exponent: float, modulo: int | None = None) -> float: + raise AssertionError("power should not be calculated above the maximum") + + r = Retrying(wait=tenacity.wait_exponential(max=40, exp_base=ExplodingPower(2))) + self.assertEqual(r.wait(make_retry_state(50, 0)), 40) + def test_exponential_with_min_wait(self) -> None: r = Retrying(wait=tenacity.wait_exponential(min=20)) self.assertEqual(r.wait(make_retry_state(1, 0)), 20)