Skip to content

Instantly share code, notes, and snippets.

@keathley
Created April 16, 2019 14:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save keathley/2f4a4303734f5cdfa54157d654473ffa to your computer and use it in GitHub Desktop.
Save keathley/2f4a4303734f5cdfa54157d654473ffa to your computer and use it in GitHub Desktop.
Tesla Middleware with exponential backoff
defmodule Huddle.Tesla.Retry do
@moduledoc """
Adds exponential backoff + retry logic to tesla
"""
# Maximum times to retry
@max_retries 5
# Base delay retry value is 50ms
@delay 50
# Maximum sleep value in MS
@cap 2_000
def call(env, next, opts) do
opts = opts || []
context = %{
retries: 0,
max_retries: Keyword.get(opts, :max_retries, @max_retries),
delay: Keyword.get(opts, :delay, @delay),
max_delay: Keyword.get(opts, :max_delay, @cap)
}
retry(env, next, context)
end
# If we have max retries set to 0 don't retry
defp retry(env, next, %{max_retries: 0}), do: Tesla.run(env, next)
# If we're on our last retry then just run and don't handle the error
defp retry(env, next, %{max_retries: max, retries: max}) do
Tesla.run(env, next)
end
# Otherwise we retry if we get a retriable error
defp retry(env, next, context) do
case Tesla.run(env, next) do
{:ok, env} ->
{:ok, env}
{:error, reason} ->
if retriable?(reason) do
backoff(context.max_delay, context.delay, context.retries)
context = update_in(context, [:retries], &(&1 + 1))
retry(env, next, context)
else
{:error, reason}
end
end
end
# Exponential backoff with jitter
def backoff(cap, base, attempt) do
factor = :math.pow(2, attempt)
max_sleep = trunc(min(cap, base * factor))
delay = :rand.uniform(max_sleep)
:timer.sleep(delay)
end
# These define our retriable errors
defp retriable?(%{status: 503}), do: true
defp retriable?(:econnrefused), do: true
defp retriable?(_), do: false
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment