Packages
launchdarkly_server_sdk
2.1.2
3.11.0
3.10.1
3.9.0
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.0
3.5.0
3.4.0
3.3.1
3.3.0
3.2.0
3.1.0
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.1.2
2.1.1
2.1.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.6.0
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.3
1.1.2
1.1.1
retired
1.1.0
retired
1.0.1
retired
1.0.0
retired
1.0.0-beta4
retired
1.0.0-beta3
retired
1.0.0-beta2
retired
LaunchDarkly SDK for Erlang
Current section
Files
Jump to
Current section
Files
src/ldclient_backoff.erl
%%-------------------------------------------------------------------
%% @doc
%% @private
%% @end
%%-------------------------------------------------------------------
-module(ldclient_backoff).
%% API
-export([init/4, fail/1, succeed/1, fire/1]).
%% Types
-type backoff() :: #{
initial => non_neg_integer(),
current => non_neg_integer(),
max => non_neg_integer(),
attempt => non_neg_integer(),
active_since => integer() | undefined,
destination => pid(),
value => term()
}.
-define(JITTER_RATIO, 0.5).
%% Reset interval in seconds.
-define(RESET_INTERVAL, 60).
-export_type([backoff/0]).
%% Expose non-exported methods for tests.
-ifdef(TEST).
-compile(export_all).
-endif.
-spec init(Initial :: non_neg_integer(), Max :: non_neg_integer(), Destination :: pid(), Value :: term()) -> backoff().
init(Initial, Max, Destination, Value) ->
#{
initial => Initial,
current => Initial,
max => Max,
attempt => 0,
active_since => undefined,
destination => Destination,
value => Value
}.
%% @doc Get an updated backoff with updated delay. Does not start a timer automatically.
%% Use fire/1 to start a timer with the updated backoff.
%% @end
-spec fail(Backoff :: backoff()) -> backoff().
fail(#{active_since := undefined} = Backoff) ->
%% ActiveSince is undefined, so we have not had a successful connection since the last attempt.
update_backoff(Backoff, undefined);
fail(#{active_since := ActiveSince} = Backoff) ->
ActiveDuration = ldclient_time:time_seconds() - ActiveSince,
update_backoff(Backoff, ActiveDuration).
%% @doc Get an updated backoff with information about the successful connection attempt.
%% Does not clear any timers. Use the timer handle from fire/1 to cancel pending timers.
%% @end
-spec succeed(Backoff :: backoff()) -> backoff().
succeed(Backoff) ->
Backoff#{active_since => ldclient_time:time_seconds()}.
%% @doc Start a timer with the current delay.
%% @end
-spec fire(backoff()) -> Timer :: reference().
fire(#{current := Current, destination := Destination, value := Value}) ->
ldclient_time:start_timer(Current, Destination, Value).
%%===================================================================
%% Internal functions
%%===================================================================
-spec update_backoff(Backoff :: backoff(), ActiveSince :: integer() | undefined) -> backoff().
update_backoff(#{attempt := Attempt} = Backoff, undefined = _ActiveDuration) ->
%% There has not been a successful connection.
NewAttempt = Attempt + 1,
Backoff#{current => delay(NewAttempt, Backoff), attempt => NewAttempt};
update_backoff(Backoff, ActiveDuration) when ActiveDuration > ?RESET_INTERVAL ->
%% The last successful connection was active for more than 1 minute.
NewAttempt = 1,
Backoff#{current => delay(NewAttempt, Backoff), attempt => NewAttempt, active_since => undefined};
update_backoff(#{attempt := Attempt} = Backoff, _ActiveDuration) ->
%% The last successful connection was less than 1 minute,
NewAttempt = Attempt + 1,
%% Resetting ActiveSince here, otherwise after being disconnected for a minute it would
%% reset the the AttemptCount.
Backoff#{current => delay(NewAttempt, Backoff), attempt => NewAttempt, active_since => undefined}.
-spec delay(Attempt :: non_neg_integer(), Backoff :: backoff()) -> non_neg_integer().
delay(Attempt, #{initial := Initial, max := Max} = _Backoff) ->
jitter(min(backoff(Initial, Attempt), Max)).
-spec backoff(Initial :: non_neg_integer(), Attempt :: non_neg_integer()) -> non_neg_integer().
backoff(Initial, Attempt) ->
trunc(Initial * (math:pow(2, Attempt - 1))).
-spec jitter(Value :: non_neg_integer()) -> non_neg_integer().
jitter(Value) ->
trunc(Value - (rand:uniform() * ?JITTER_RATIO * Value)).