Foundation.RateLimit.BackoffWindow provides shared backoff windows for
rate-limited APIs. When an API returns a rate-limit signal (e.g., HTTP 429),
you set a backoff window. All callers sharing that limiter will wait until
the window passes.
Creating a Limiter
Limiters are keyed by an arbitrary term and stored in an ETS-backed registry:
alias Foundation.RateLimit.BackoffWindow
# Get or create a limiter for a key (uses default registry)
limiter = BackoffWindow.for_key(:openai_api)
# Multiple calls with the same key return the same limiter
limiter2 = BackoffWindow.for_key(:openai_api)
# limiter and limiter2 reference the same underlying atomics refSetting Backoff
When you receive a rate-limit response, set a backoff window:
# Back off for 30 seconds
BackoffWindow.set(limiter, 30_000)
# Check if backoff is active
BackoffWindow.should_backoff?(limiter) # true
# Wait for the backoff to clear
BackoffWindow.wait(limiter)Setting a new backoff while one is active extends the deadline (it never shortens an active window):
BackoffWindow.set(limiter, 30_000)
BackoffWindow.set(limiter, 5_000) # ignored, original 30s window is longer
BackoffWindow.set(limiter, 60_000) # extends to 60s from nowClearing Backoff
BackoffWindow.clear(limiter)
BackoffWindow.should_backoff?(limiter) # falseCustom Registries
Use separate registries to isolate different groups of limiters:
registry = BackoffWindow.new_registry(name: :my_rate_limiters)
limiter = BackoffWindow.for_key(registry, :stripe_api)Usage Pattern
A typical pattern for HTTP clients:
alias Foundation.RateLimit.BackoffWindow
limiter = BackoffWindow.for_key(:my_api)
def call_api(limiter, request) do
# Wait for any active backoff to clear
BackoffWindow.wait(limiter)
case HTTPClient.request(request) do
{:ok, %{status: 429, headers: headers} = response} ->
retry_after = Foundation.Retry.HTTP.parse_retry_after(headers)
BackoffWindow.set(limiter, retry_after)
{:error, {:rate_limited, response}}
{:ok, %{status: status} = response} when status in 200..299 ->
{:ok, response}
error ->
error
end
end