lib/nerves_hub_link/backoff.ex

defmodule NervesHubLink.Backoff do
  @moduledoc """
  Compute retry backoff intervals used by Slipstream
  """

  @doc """
  Produce a list of integer backoff delays with jitter

  The first two parameters are minimum and maximum value. These are expected to
  be milliseconds, but this function doesn't care. The returned list will start
  with the minimum value and then double it until it reaches the maximum value.

  The third parameter is the amount of jitter to add to each delay. The value
  should be between 0 and 1. Zero adds no jitter. A value like 0.25 will add up
  to 25% of the delay amount.
  """
  @spec delay_list(integer(), integer(), number()) :: [integer()]
  def delay_list(min, max, jitter) when min > 0 and max >= min and jitter >= 0 do
    seed_rand()
    calc(min, max, jitter)
  end

  defp calc(min, max, jitter) when min >= max do
    [add_jitter(max, jitter)]
  end

  defp calc(min, max, jitter) do
    delay = add_jitter(min, jitter)
    [delay | calc(min * 2, max, jitter)]
  end

  defp add_jitter(value, jitter) do
    round(value * (1 + jitter * :rand.uniform()))
  end

  defp seed_rand() do
    # `:rand` gets seeded with the system time and counters. To avoid concern
    # that the seed could be the same across many devices, pull from a pool of
    # cryptographically secure random numbers.
    <<x::32>> = :crypto.strong_rand_bytes(4)
    _ = :rand.seed(:exsss, x)
    :ok
  end
end