From cd839eeeb2cade16fb6f1b2c545677d90102ecda Mon Sep 17 00:00:00 2001 From: "Tianlang (Jackie) Chen" <305351710+jackiectl2@users.noreply.github.com> Date: Sun, 8 Nov 2026 11:02:07 +0000 Subject: [PATCH 1/2] fix(resilience): avoid exponent overflow in capped retries Signed-off-by: Tianlang (Jackie) Chen <305351710+jackiectl2@users.noreply.github.com> --- raganything/resilience.py | 8 ++++++-- tests/test_resilience.py | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/raganything/resilience.py b/raganything/resilience.py index c42eed732..d41f752d2 100644 --- a/raganything/resilience.py +++ b/raganything/resilience.py @@ -104,6 +104,7 @@ def decorator(func: F) -> F: @functools.wraps(func) def wrapper(*args: Any, **kwargs: Any) -> Any: last_exception: BaseException | None = None + raw_delay = float(base_delay) for attempt in range(1, max_attempts + 1): try: return func(*args, **kwargs) @@ -117,7 +118,8 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: exc, ) raise - delay = base_delay * (exponential_base ** (attempt - 1)) + delay = raw_delay + raw_delay *= exponential_base if jitter: import random @@ -187,6 +189,7 @@ def decorator(func: F) -> F: @functools.wraps(func) async def wrapper(*args: Any, **kwargs: Any) -> Any: last_exception: BaseException | None = None + raw_delay = float(base_delay) for attempt in range(1, max_attempts + 1): try: return await func(*args, **kwargs) @@ -200,7 +203,8 @@ async def wrapper(*args: Any, **kwargs: Any) -> Any: exc, ) raise - delay = base_delay * (exponential_base ** (attempt - 1)) + delay = raw_delay + raw_delay *= exponential_base if jitter: import random diff --git a/tests/test_resilience.py b/tests/test_resilience.py index 29909e51a..203f57add 100644 --- a/tests/test_resilience.py +++ b/tests/test_resilience.py @@ -519,3 +519,44 @@ async def extra_call(): task.cancel() with pytest.raises(asyncio.CancelledError): await task + + +@pytest.mark.parametrize("base_delay, jitter", [(1.0, False), (1, True)]) +def test_capped_retry_does_not_overflow(monkeypatch, base_delay, jitter): + delays = [] + monkeypatch.setattr(resilience_module.time, "sleep", delays.append) + calls = 0 + + @retry(max_attempts=350, base_delay=base_delay, max_delay=0.0, exponential_base=10.0, jitter=jitter) + def operation(): + nonlocal calls + calls += 1 + if calls < 350: + raise ConnectionError("retry") + return "recovered" + + assert operation() == "recovered" + assert delays == [0.0] * 349 + + +@pytest.mark.asyncio +@pytest.mark.parametrize("base_delay, jitter", [(1.0, False), (1, True)]) +async def test_capped_async_retry_does_not_overflow(monkeypatch, base_delay, jitter): + delays = [] + + async def sleep(delay): + delays.append(delay) + + monkeypatch.setattr(resilience_module.asyncio, "sleep", sleep) + calls = 0 + + @async_retry(max_attempts=350, base_delay=base_delay, max_delay=0.0, exponential_base=10.0, jitter=jitter) + async def operation(): + nonlocal calls + calls += 1 + if calls < 350: + raise ConnectionError("retry") + return "recovered" + + assert await operation() == "recovered" + assert delays == [0.0] * 349 From 8157e49124e001348ce6052403c55e2753544e8e Mon Sep 17 00:00:00 2001 From: "Tianlang (Jackie) Chen" <305351710+jackiectl2@users.noreply.github.com> Date: Sun, 8 Nov 2026 11:09:20 +0000 Subject: [PATCH 2/2] Format long retry regression decorators Signed-off-by: Tianlang (Jackie) Chen <305351710+jackiectl2@users.noreply.github.com> --- tests/test_resilience.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_resilience.py b/tests/test_resilience.py index 203f57add..8d335f7fd 100644 --- a/tests/test_resilience.py +++ b/tests/test_resilience.py @@ -527,7 +527,13 @@ def test_capped_retry_does_not_overflow(monkeypatch, base_delay, jitter): monkeypatch.setattr(resilience_module.time, "sleep", delays.append) calls = 0 - @retry(max_attempts=350, base_delay=base_delay, max_delay=0.0, exponential_base=10.0, jitter=jitter) + @retry( + max_attempts=350, + base_delay=base_delay, + max_delay=0.0, + exponential_base=10.0, + jitter=jitter, + ) def operation(): nonlocal calls calls += 1 @@ -550,7 +556,13 @@ async def sleep(delay): monkeypatch.setattr(resilience_module.asyncio, "sleep", sleep) calls = 0 - @async_retry(max_attempts=350, base_delay=base_delay, max_delay=0.0, exponential_base=10.0, jitter=jitter) + @async_retry( + max_attempts=350, + base_delay=base_delay, + max_delay=0.0, + exponential_base=10.0, + jitter=jitter, + ) async def operation(): nonlocal calls calls += 1