Current section

Files

Jump to
backoff src backoff.erl
Raw

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()}).
-opaque backoff() :: #backoff{}.
-export_type([backoff/0]).
-ifdef(OLD_RANDOM).
-define(random, random).
-else.
-define(random, rand).
-endif.
%% Just do the increments by hand!
-spec increment(pos_integer()) -> pos_integer().
increment(N) when is_integer(N) -> N bsl 1.
-spec increment(N, Max) -> pos_integer() when
N :: pos_integer(),
Max :: pos_integer().
increment(N, Max) -> min(increment(N), Max).
%% 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().
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.
-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
%% init function 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).
%% 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}.
%% 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).
%% Reads the current backoff value
-spec get(backoff()) -> pos_integer().
get(#backoff{current=Delay}) -> Delay.
%% Swaps between the states of the backoff.
-spec type(backoff(), normal | jitter) -> backoff().
type(#backoff{}=B, jitter) ->
maybe_seed(),
B#backoff{type=jitter};
type(#backoff{}=B, normal) ->
B#backoff{type=normal}.
-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}}.
-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.