Retry Backoff Planner
Plan retry delays with capped exponential backoff and explicit retryable error classes.
Implement deterministic retry-backoff logic for transient failures.
Requirements
- Define
RetryPolicy(max_attempts, base_delay, max_delay, retryable_errors). next_delay(attempt, error_code)returns the delay before the next try.attemptis the failed attempt number, starting at1for the first failure.- Return
Noneif the error isn't retryable. - Return
Noneifattempt >= max_attempts. - Otherwise return exponential backoff:
base_delay * 2 ** max(0, attempt - 1). Themax(0, …)clamp keeps the exponent non-negative ifattempt < 1(e.g.attempt=0uses exponent0and returnsbase_delaybefore the cap). - Cap the delay at
max_delay. - Constructor inputs are valid:
max_attemptsis a positive integer; delays are finite nonnegative numbers in seconds;attemptis an integer. Zero delay means retry immediately, whileNonemeans stop. - The policy only plans a delay. It doesn't sleep, count failures, or perform the retry; the caller supplies the failed-attempt number each time.
Example
Four total attempts allow three retry delays. After the fourth failure, the budget is exhausted:
policy = RetryPolicy(4, 0.5, 5, {"timeout"})
assert policy.next_delay(1, "timeout") == 0.5
assert policy.next_delay(4, "timeout") is None| Failed attempt | Delay before another attempt | Next attempt |
|---|---|---|
| 1 | 0.5 seconds | 2 |
| 2 | 1 second | 3 |
| 3 | 2 seconds | 4 |
| 4 | None | Stop |
The waiting time is 3.5 seconds if all four attempts fail, excluding time spent on requests. max_attempts counts the initial call too; it isn't a budget of four additional retries. For compatibility with this exercise, attempt=0 or a negative attempt uses the base-delay exponent, then still applies the retryability, budget and delay-cap checks.
Cap before exponential overflow
With a very large allowed attempt count, the mathematical delay can dwarf the cap. Computing 2 ** 9999 and converting it to a Python float can raise OverflowError before min gets to apply the cap. The solution uses floating-point exponent scaling, handles overflow by returning the cap, and handles a zero base delay separately. A zero base stays zero even on attempt 10,000.
Constraints
- Don't use randomness.
- Keep decisions deterministic and testable.
- Treat unknown errors as non-retryable.
- Production clients should add jitter, honor
Retry-After, and check idempotency; those safeguards are follow-ups, not part of this deterministic contract.