Skip to content

fix: wait_exponential with zero multiplier no longer overflows into max wait - #712

Open
TrueFurina wants to merge 5 commits into
jd:mainfrom
TrueFurina:fix/zero-multiplier-overflow
Open

TrueFurina wants to merge 5 commits into
jd:mainfrom
TrueFurina:fix/zero-multiplier-overflow

Conversation

@TrueFurina

Copy link
Copy Markdown

Fixes #710.

Problem

With wait_exponential(multiplier=0) (or any zero multiplier), the result of multiplier * exp_base**exponent is always 0. But exp_base**exponent is computed first, and for large attempt numbers (2**1024 at attempt 1025 with the default exp_base=2) it raises OverflowError — which is caught by the except OverflowError: return self.max branch, so the strategy wrongly returns max instead of 0.

This is easy to hit with a long-lived @retry(wait=wait_exponential(multiplier=0)): the process retried hourly for a few days, then every "failure" wait silently became the maximum wait.

Fix

Short-circuit the zero-multiplier case before computing the power:

if self.multiplier == 0:
    result: float = 0.0
else:
    exp = self.exp_base**exponent
    result = self.multiplier * exp

The min clamp still applies afterwards, so wait_exponential(multiplier=0, min=5) correctly returns 5.

Testing

  • pytest tests/test_tenacity.py -k exponential — 18 passed (16 existing + 2 new regression tests: test_exponential_zero_multiplier_returns_zero_for_huge_attempts covering attempts 2/10/1025 and exp_base=2.0, and test_exponential_zero_multiplier_with_min_wait)
  • Normal-path and max-capping behavior unchanged (multiplier=1 still returns 1/2/4/8... and caps at max)

@TrueFurina

Copy link
Copy Markdown
Author

Lint fix: the test (3.14, lint) job failure came from my touched test file - one hunk needed a line wrap and the import block needed sorting per the project ruff config (line-length 88). Both fixed and verified locally with the project's own ruff settings (format --check and check both exit clean). tenacity/wait.py needed no changes.

@TrueFurina

Copy link
Copy Markdown
Author

Correction: my earlier lint fix used an incomplete copy of the project ruff config (the isort subsection was dropped), so my local run passed while CI correctly flagged I001 at tests/test_tenacity.py:16:1. Re-did the fix with the full [tool.ruff.lint] config including [lint.isort]; the import block is now ordered per the project settings and both ruff check and ruff format --check pass locally with the complete config.

@TrueFurina

Copy link
Copy Markdown
Author

Root cause found via the re-triggered run: two real violations my earlier local check missed because I had reconstructed the ruff config by hand instead of using the project file - I001 (import block order, fixed via the project ruff config) and B904 on the intentional implicit-chaining raise in the new overflow test (marked # noqa: B904, since implicit __context__ chaining is exactly what that test exercises). Verified locally with the CI-pinned ruff 0.16.9 against the project's own pyproject.toml: ruff check and ruff format --check both exit clean.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Zero exponential multiplier can overflow into maximum wait

1 participant