RetryBudget

Retry budget: a system-wide guard against retry storms.

Per-call retry (see retryWithExponentialBackoff and friends) is unaware of how much total retry volume the rest of the system is currently generating. The well-known failure mode: a downstream outage causes every caller to retry simultaneously, multiplying load on an already-struggling service and making the outage worse instead of better.

A RetryBudget caps total retry volume as a fraction of primary request volume (the same token-bucket throttling strategy gRPC and Finagle use): every successful, non-retry call deposits a small fraction of a token; every retry attempt withdraws one whole token. Once the budget is exhausted, further retries are refused outright (failing fast) until enough successful traffic replenishes it — this is what makes it self-healing: the budget recovers on its own as soon as the system is healthy enough to produce the successes needed to refill it, with no manual intervention or reset required.

Basic usage, sharing one budget across every caller of a given downstream:

val budget = RetryBudget.create() // one instance per downstream, shared across calls

val result = retryWithBudget(budget, retries = 5) {
callFlakyService()
}

Using the primitive directly, to gate a custom retry loop:

if (budget.tryWithdraw()) {
// perform the retry
} else {
// budget exhausted -- fail fast instead of piling onto a struggling downstream
}

Types

Link copied to clipboard
object Companion

Factory for RetryBudget.

Functions

Link copied to clipboard
suspend fun availableTokens(): Double

Current token balance, from 0.0 to RetryBudgetConfig.maxTokens.

Link copied to clipboard
suspend fun recordSuccess()

Records a successful, non-retry call, depositing RetryBudgetConfig.tokenRatio tokens (capped at RetryBudgetConfig.maxTokens).

Link copied to clipboard
suspend fun tryWithdraw(): Boolean

Attempts to withdraw one token for a retry attempt.