Bounded Retry Policy

Bounded Retry Policy

An AI feature calls a model provider that sometimes fails. Before the application retries a failed call, it asks a pure policy function what to do next. The function never sleeps, reads a clock, or generates random numbers: the caller passes the time left before the task's deadline and a random value for jitter, so every decision can be tested exactly.

Implement decideRetry(failure, completedAttempts, remainingMs, random, policy).

Inputs

  • failure: { transient, safeToRetry, retryAfterMs? }. transient is true when the failure is temporary, such as a rate limit or a brief outage. safeToRetry is true when the request can be replayed without repeating a side effect. retryAfterMs, when present, is the provider's minimum wait, already parsed into milliseconds.
  • completedAttempts: attempts made so far, including the one that just failed.
  • remainingMs: milliseconds left before the logical task's deadline.
  • random: a number in [0, 1) chosen by the caller.
  • policy: { maxAttempts, baseDelayMs, maxDelayMs, minimumAttemptMs }. maxAttempts counts total attempts, not retries. minimumAttemptMs is the time a new attempt needs to be worth starting.

Return { retry: true, delayMs } or { retry: false, reason }, with exactly those keys.

Rules, in order

  1. Validate first. Throw an Error when remainingMs, baseDelayMs, maxDelayMs, minimumAttemptMs, or a present retryAfterMs is not a finite number of at least 0; when completedAttempts or maxAttempts is not an integer of at least 1; when random is not a finite number in [0, 1); or when minimumAttemptMs is 0. Invalid input throws even when the failure would not be retried.
  2. If safeToRetry is false, return reason "unsafe".
  3. If transient is false, return reason "permanent".
  4. If completedAttempts >= maxAttempts, return reason "attempts".
  5. Compute the delay with capped exponential backoff, full jitter, and retry-after as a minimum:
    • ceiling = min(maxDelayMs, baseDelayMs * 2 ** (completedAttempts - 1))
    • jittered = floor(random * ceiling)
    • delayMs = max(jittered, retryAfterMs), where a missing retry-after counts as 0
  6. If delayMs + minimumAttemptMs > remainingMs, return reason "deadline". Otherwise return { retry: true, delayMs }.

Fixtures

Every row uses policy = { maxAttempts: 3, baseDelayMs: 200, maxDelayMs: 1000, minimumAttemptMs: 300 } and random = 0.5. A safe failure has safeToRetry: true.

Failure and stateRemaining timeExpected decision
Transient, safe, 1 completed attempt2,000 ms{ retry: true, delayMs: 100 }
Transient, safe, 2 completed attempts2,000 ms{ retry: true, delayMs: 200 }
Transient, safe, 3 completed attempts2,000 ms{ retry: false, reason: "attempts" }
Permanent, safe, 1 completed attempt2,000 ms{ retry: false, reason: "permanent" }
Transient, unsafe, 1 completed attempt2,000 ms{ retry: false, reason: "unsafe" }
Transient, safe, retry-after 700 ms, 1 completed attempt2,000 ms{ retry: true, delayMs: 700 }
Same retry-after case900 ms{ retry: false, reason: "deadline" }
Transient, safe, 1 completed attempt (needs 100 + 300 ms)399 ms{ retry: false, reason: "deadline" }

Notes

  • Retry-after can exceed maxDelayMs. The cap limits the randomized backoff, not the provider's minimum wait; the deadline still decides whether to wait at all.
  • Remaining time exactly equal to delayMs + minimumAttemptMs is enough to retry.
  • A random of 0 gives a jittered delay of 0.

In Python, implement decide_retry(failure, completed_attempts, remaining_ms, random_value, policy) with dict keys transient, safe_to_retry, and retry_after_ms (missing or None means no retry-after), and policy keys max_attempts, base_delay_ms, max_delay_ms, and minimum_attempt_ms. Return {"retry": True, "delay_ms": ...} or {"retry": False, "reason": ...}, and raise ValueError for invalid input.

MEDIUM
AI Engineering
Retry Policy
Fault Tolerance
Rate Limiting
Intermediate

0 views

Solution

Hints

Hint 1
Hint 2
Premium
Hint 3
Premium