Current section
Files
Jump to
Current section
Files
src/backoff.erl
-module(backoff).
-export([increment/1, increment/2]).
-export([rand_increment/1, rand_increment/2]).
-export([type/2]).
-export([init/2, init/4,
fire/1, get/1, succeed/1, fail/1]).
-record(backoff, {start :: pos_integer(),
max :: pos_integer() | infinity,
current :: pos_integer(),
type=normal :: normal | jitter,
value :: term(),
dest :: pid() | undefined }).
-opaque backoff() :: #backoff{}.
-export_type([backoff/0]).
-ifdef(OLD_RANDOM).
-define(random, random).
-else.
-define(random, rand).
-endif.
%% @doc Increment an integer exponentially
-spec increment(pos_integer()) -> pos_integer().
increment(N) when is_integer(N) -> N bsl 1.
%% @doc Increment an integer exponentially within a range
-spec increment(N, Max) -> pos_integer() when
N :: pos_integer(),
Max :: pos_integer().
increment(N, Max) -> min(increment(N), Max).
%% @doc Increment an integer exponentially with randomness or jitter
%% Chooses a 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().
rand_increment(N) ->
%% New delay chosen from [N, 3N], i.e. [0.5 * 2N, 1.5 * 2N]
Width = N bsl 1,
N + ?random:uniform(Width + 1) - 1.
%% @doc Increment an integer with exponentially randomness
%% or jitter within a range
%% Chooses a 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(N, Max) -> pos_integer() when
N :: pos_integer(),
Max :: pos_integer().
rand_increment(N, Max) ->
%% The largest interval for [0.5 * Time, 1.5 * Time] with maximum Max is
%% [Max div 3, Max].
MaxMinDelay = Max div 3,
if
MaxMinDelay =:= 0 ->
?random:uniform(Max);
N > MaxMinDelay ->
rand_increment(MaxMinDelay);
true ->
rand_increment(N)
end.
%% Increments + Timer support
%% @doc init function to be used when the user doesn't feel like using a timer
%% provided by this library
-spec init(Start, Max) -> backoff() when
Start :: pos_integer(),
Max :: pos_integer() | infinity.
init(Start,Max) ->
init(Start, Max, undefined, undefined).
%% @doc init function when the user feels like using a timer
%% provided by this library
-spec init(Start, Max, Dest, Value) -> backoff() when
Start :: pos_integer(),
Max :: pos_integer() | infinity,
Value :: term() | undefined,
Dest :: pid() | undefined.
init(Start, Max, Dest, Value) ->
#backoff{start=Start, current=Start, max=Max, value=Value, dest=Dest}.
%% @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()) -> Timer::reference().
fire(#backoff{current=Delay, value=Value, dest=Dest}) ->
erlang:start_timer(Delay, Dest, Value).
%% @doc Reads the current backoff value
-spec get(backoff()) -> pos_integer().
get(#backoff{current=Delay}) -> Delay.
%% @doc Swaps between the states of the backoff, going between either
%% `normal' or `jitter' modes.
-spec type(backoff(), normal | jitter) -> backoff().
type(#backoff{}=B, jitter) ->
maybe_seed(),
B#backoff{type=jitter};
type(#backoff{}=B, normal) ->
B#backoff{type=normal}.
%% @doc Mark an attempt as failed, which increments the backoff value
%% for the next round.
-spec fail(backoff()) -> {New::pos_integer(), backoff()}.
fail(B=#backoff{current=Delay, max=infinity, type=normal}) ->
NewDelay = increment(Delay),
{NewDelay, B#backoff{current=NewDelay}};
fail(B=#backoff{current=Delay, max=Max, type=normal}) ->
NewDelay = increment(Delay, Max),
{NewDelay, B#backoff{current=NewDelay}};
fail(B=#backoff{current=Delay, max=infinity, type=jitter}) ->
NewDelay = rand_increment(Delay),
{NewDelay, B#backoff{current=NewDelay}};
fail(B=#backoff{current=Delay, max=Max, type=jitter}) ->
NewDelay = rand_increment(Delay, Max),
{NewDelay, B#backoff{current=NewDelay}}.
%% @doc Mark an attempt as successful, which resets the backoff
%% value for the next round.
-spec succeed(backoff()) -> {New::pos_integer(), backoff()}.
succeed(B=#backoff{start=Start}) ->
{Start, B#backoff{current=Start}}.
-ifdef(OLD_RANDOM).
maybe_seed() ->
case erlang:get(random_seed) of
undefined -> random:seed(erlang:now());
{X,X,X} -> random:seed(erlang:now());
_ -> ok
end.
-else.
maybe_seed() -> ok.
-endif.