Current section

Files

Jump to
exbackoff lib exbackoff.ex
Raw

lib/exbackoff.ex

defmodule Exbackoff do
use Bitwise
@typep max :: pos_integer | :infinity
defstruct [start: nil,
max: nil,
current: nil,
type: :normal,
value: nil,
dest: nil]
@type backoff :: %__MODULE__{ start: pos_integer,
max: max,
current: pos_integer,
type: :normal | :jitter,
value: any,
dest: pid}
@doc """
Just do the increments by hand!
"""
@spec increment(pos_integer) :: pos_integer
def increment(n) when is_integer(n), do: n <<< 1
@spec increment(pos_integer, pos_integer) :: pos_integer
def increment(n, max), do: min(increment(n), max)
@doc """
Just do the random increments by hand!
Choose delay uniformly from [0.5 * Time, 1.5 * Time] as recommended in:
Sally Floyd and Van Jacobson, The Synchronization of Periodic Routing Messages,
April 1994 IEEE/ACM Transactions on Networking.
http://ee.lbl.gov/papers/sync_94.pdf
"""
@spec rand_increment(pos_integer) :: pos_integer
def rand_increment(n) do
# New delay chosen from [N, 3N], i.e. [0.5 * 2N, 1.5 * 2N]
width = n <<< 1
n + :random.uniform(width + 1) - 1
end
@spec rand_increment(pos_integer, pos_integer) :: pos_integer
def rand_increment(n, max) do
# The largest interval for [0.5 * Time, 1.5 * Time] with maximum Max is
# [Max div 3, Max].
max_min_delay = div max, 3
cond do
max_min_delay === 0 ->
:random.uniform(max)
n > max_min_delay ->
rand_increment(max_min_delay)
true ->
rand_increment(n)
end
end
# Increments + Timer support
@doc """
init function when the user doesn't feel like using a timer
provided by this library
"""
@spec init(pos_integer, max) :: backoff
def init(start, max), do: init(start, max, :nil, :nil)
@doc """
init function when the user feels like using a timer
provided by this library
"""
@spec init(pos_integer, max, pid | nil, any | nil) :: backoff
def init(start, max, dest, value) do
%Exbackoff{start: start, current: start, max: max, value: value, dest: dest}
end
@doc """
Starts a timer from the `backoff()' argument, using erlang:start_timer/3.
No reference tracking is done, and this is left to the user. This function
is purely a convenience function.
"""
@spec fire(backoff) :: reference
def fire(%Exbackoff{current: delay, value: value, dest: dest}) do
:erlang.start_timer(delay, dest, value)
end
@doc """
Reads the current backoff value.
"""
@spec get(backoff) :: pos_integer
def get(%Exbackoff{current: delay}), do: delay
@doc """
Swaps between the states of the backoff.
"""
@spec type(backoff, :normal | :jitter) :: backoff
def type(b = %Exbackoff{}, :jitter), do: %{b | type: :jitter}
def type(b = %Exbackoff{}, :normal), do: %{b | type: :normal}
@spec fail(backoff) :: {pos_integer, backoff}
def fail(b = %Exbackoff{current: delay, max: :infinity, type: :normal}) do
new_delay = increment(delay)
{new_delay, %{b | current: new_delay}}
end
def fail(b = %Exbackoff{current: delay, max: max, type: :normal}) do
new_delay = increment(delay, max)
{new_delay, %{b | current: new_delay}}
end
def fail(b = %Exbackoff{current: delay, max: :infinity, type: :jitter}) do
new_delay = rand_increment(delay)
{new_delay, %{b | current: new_delay}}
end
def fail(b = %Exbackoff{current: delay, max: max, type: :jitter}) do
new_delay = rand_increment(delay, max)
{new_delay, %{b | current: new_delay}}
end
@spec succeed(backoff) :: {pos_integer, backoff}
def succeed(b = %Exbackoff{start: start}) do
{start, %{b | current: start}}
end
end