aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/src/base
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diameter/src/base')
-rw-r--r--lib/diameter/src/base/diameter.erl1
-rw-r--r--lib/diameter/src/base/diameter_codec.erl34
-rw-r--r--lib/diameter/src/base/diameter_config.erl6
-rw-r--r--lib/diameter/src/base/diameter_lib.erl103
-rw-r--r--lib/diameter/src/base/diameter_peer.erl20
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl7
-rw-r--r--lib/diameter/src/base/diameter_reg.erl3
-rw-r--r--lib/diameter/src/base/diameter_service.erl9
-rw-r--r--lib/diameter/src/base/diameter_session.erl4
-rw-r--r--lib/diameter/src/base/diameter_stats.erl16
-rw-r--r--lib/diameter/src/base/diameter_sync.erl3
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl7
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl58
13 files changed, 105 insertions, 166 deletions
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index 010f977b97..9d71bcbbf8 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -311,6 +311,7 @@ call(SvcName, App, Message) ->
| {sequence, sequence() | evaluable()}
| {share_peers, remotes()}
| {string_decode, boolean()}
+ | {strict_mbit, boolean()}
| {incoming_maxlen, message_length()}
| {use_shared_peers, remotes()}
| {spawn_opt, list()}.
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index bf2fe8e7ca..aab8b5887e 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -76,9 +76,10 @@ setopts(Opts)
when is_list(Opts) ->
lists:foreach(fun setopt/1, Opts).
-%% Decode stringish types to string()? The default true is for
-%% backwards compatibility.
-setopt({string_decode = K, false = B}) ->
+%% The default string_decode true is for backwards compatibility.
+setopt({K, false = B})
+ when K == string_decode;
+ K == strict_mbit ->
setopt(K, B);
%% Regard anything but the generated RFC 3588 dictionary as modern.
@@ -96,7 +97,8 @@ setopt(Key, Value) ->
getopt(Key) ->
case get({diameter, Key}) of
- undefined when Key == string_decode ->
+ undefined when Key == string_decode;
+ Key == strict_mbit ->
true;
undefined when Key == rfc ->
6733;
@@ -590,6 +592,7 @@ split_head(<<Code:32, 0:1, M:1, P:1, _:5, Len:24, _/binary>>) ->
%% Header is truncated.
split_head(Bin) ->
?THROW({5014, #diameter_avp{data = Bin}}).
+%% Note that pack_avp/1 will pad this at encode if sent in a Failed-AVP.
%% 3588:
%%
@@ -619,7 +622,7 @@ split_head(Bin) ->
%% AVP header with zero up to the minimum AVP header length.
%%
%% The underlined clause must be in error since (1) a header less than
-%% the minimum value mean we don't know the identity of the AVP and
+%% the minimum value mean we might not know the identity of the AVP and
%% (2) the last sentence covers this case.
%% split_data/3
@@ -655,16 +658,23 @@ split_data(Bin, Len) ->
%% The normal case here is data as an #diameter_avp{} list or an
%% iolist, which are the cases that generated codec modules use. The
-%% other case is as a convenience in the relay case in which the
+%% other cases are a convenience in the relay case in which the
%% dictionary doesn't know about specific AVP's.
-%% Grouped AVP whose components need packing ...
-pack_avp([#diameter_avp{} = A | Avps]) ->
- pack_avp(A#diameter_avp{data = Avps});
-pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Avps} = A) ->
- pack_avp(A#diameter_avp{data = encode_avps(Avps)});
+%% Decoded Grouped AVP with decoded components: ignore components
+%% since they're already encoded in the Grouped AVP.
+pack_avp([#diameter_avp{} = Grouped | _Components]) ->
+ pack_avp(Grouped);
-%% ... data as a type/value tuple ...
+%% Grouped AVP whose components need packing. It's intentional that
+%% this isn't equivalent to [Grouped | Components]: here the
+%% components need to be encoded before wrapping with the Grouped AVP,
+%% and the list is flat, nesting being accomplished in the data
+%% fields.
+pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Components} = Grouped) ->
+ pack_avp(Grouped#diameter_avp{data = encode_avps(Components)});
+
+%% Data as a type/value tuple ...
pack_avp(#diameter_avp{data = {Type, Value}} = A)
when is_atom(Type) ->
pack_avp(A#diameter_avp{data = diameter_types:Type(encode, Value)});
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 8ac3b9d6ca..242b6b4d08 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -37,8 +37,7 @@
-module(diameter_config).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2, now/0]}).
--import(diameter_lib, [now/0]).
+-compile({no_auto_import, [monitor/2]}).
-export([start_service/2,
stop_service/1,
@@ -647,6 +646,7 @@ make_config(SvcName, Opts) ->
{?NOMASK, sequence},
{nodes, restrict_connections},
{16#FFFFFF, incoming_maxlen},
+ {true, strict_mbit},
{true, string_decode},
{[], spawn_opt}]),
@@ -685,12 +685,14 @@ opt(K, false = B)
K == use_shared_peers;
K == monitor;
K == restrict_connections;
+ K == strict_mbit;
K == string_decode ->
B;
opt(K, true = B)
when K == share_peers;
K == use_shared_peers;
+ K == strict_mbit;
K == string_decode ->
B;
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index 3f327f3653..26cc6137a2 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -18,18 +18,12 @@
%%
-module(diameter_lib).
--compile({no_auto_import, [now/0]}).
-export([info_report/2,
error_report/2,
warning_report/2,
- now/0,
- timestamp/1,
now_diff/1,
- micro_diff/1,
- micro_diff/2,
time/1,
- seed/0,
eval/1,
eval_name/1,
get_stacktrace/0,
@@ -98,49 +92,10 @@ fmt(T) ->
end.
%% ---------------------------------------------------------------------------
-%% # now/0
-%% ---------------------------------------------------------------------------
-
--type timestamp() :: {non_neg_integer(), 0..999999, 0..999999}.
--type now() :: integer() %% monotonic time
- | timestamp().
-
--spec now()
- -> now().
-
-%% Use monotonic time if it exists, fall back to erlang:now()
-%% otherwise.
-
-now() ->
- try
- erlang:monotonic_time()
- catch
- error: undef -> erlang:now()
- end.
-
-%% ---------------------------------------------------------------------------
-%% # timestamp/1
-%% ---------------------------------------------------------------------------
-
--spec timestamp(NowT :: now())
- -> timestamp().
-
-timestamp({_,_,_} = T) -> %% erlang:now()
- T;
-
-timestamp(MonoT) -> %% monotonic time
- MicroSecs = monotonic_to_microseconds(MonoT + erlang:time_offset()),
- Secs = MicroSecs div 1000000,
- {Secs div 1000000, Secs rem 1000000, MicroSecs rem 1000000}.
-
-monotonic_to_microseconds(MonoT) ->
- erlang:convert_time_unit(MonoT, native, micro_seconds).
-
-%% ---------------------------------------------------------------------------
%% # now_diff/1
%% ---------------------------------------------------------------------------
--spec now_diff(NowT :: now())
+-spec now_diff(NowT :: erlang:timestamp())
-> {Hours, Mins, Secs, MicroSecs}
when Hours :: non_neg_integer(),
Mins :: 0..59,
@@ -151,36 +106,7 @@ monotonic_to_microseconds(MonoT) ->
%% instead of as integer microseconds.
now_diff(Time) ->
- time(micro_diff(Time)).
-
-%% ---------------------------------------------------------------------------
-%% # micro_diff/1
-%% ---------------------------------------------------------------------------
-
--spec micro_diff(NowT :: now())
- -> MicroSecs
- when MicroSecs :: non_neg_integer().
-
-micro_diff({_,_,_} = T0) ->
- timer:now_diff(erlang:now(), T0);
-
-micro_diff(T0) -> %% monotonic time
- monotonic_to_microseconds(erlang:monotonic_time() - T0).
-
-%% ---------------------------------------------------------------------------
-%% # micro_diff/2
-%% ---------------------------------------------------------------------------
-
--spec micro_diff(T1 :: now(), T0 :: now())
- -> MicroSecs
- when MicroSecs :: non_neg_integer().
-
-micro_diff(T1, T0)
- when is_integer(T1), is_integer(T0) -> %% monotonic time
- monotonic_to_microseconds(T1 - T0);
-
-micro_diff(T1, T0) -> %% at least one erlang:now()
- timer:now_diff(timestamp(T1), timestamp(T0)).
+ time(timer:now_diff(now(), Time)).
%% ---------------------------------------------------------------------------
%% # time/1
@@ -190,7 +116,7 @@ micro_diff(T1, T0) -> %% at least one erlang:now()
-spec time(NowT | Diff)
-> {Hours, Mins, Secs, MicroSecs}
- when NowT :: timestamp(),
+ when NowT :: erlang:timestamp(),
Diff :: non_neg_integer(),
Hours :: non_neg_integer(),
Mins :: 0..59,
@@ -209,27 +135,6 @@ time(Micro) -> %% elapsed time
{H, M, S, Micro rem 1000000}.
%% ---------------------------------------------------------------------------
-%% # seed/0
-%% ---------------------------------------------------------------------------
-
--spec seed()
- -> {timestamp(), {integer(), integer(), integer()}}.
-
-%% Return an argument for random:seed/1.
-
-seed() ->
- T = now(),
- {timestamp(T), seed(T)}.
-
-%% seed/1
-
-seed({_,_,_} = T) ->
- T;
-
-seed(T) -> %% monotonic time
- {erlang:phash2(node()), T, erlang:unique_integer()}.
-
-%% ---------------------------------------------------------------------------
%% # eval/1
%%
%% Evaluate a function in various forms.
@@ -321,7 +226,7 @@ ip(T)
%% Or not: convert from '.'/':'-separated decimal/hex.
ip(Addr) ->
- {ok, A} = inet_parse:address(Addr), %% documented in inet(3)
+ {ok, A} = inet:parse_address(Addr),
A.
%% ---------------------------------------------------------------------------
diff --git a/lib/diameter/src/base/diameter_peer.erl b/lib/diameter/src/base/diameter_peer.erl
index 89b63c8a92..1ae8b567b1 100644
--- a/lib/diameter/src/base/diameter_peer.erl
+++ b/lib/diameter/src/base/diameter_peer.erl
@@ -20,9 +20,6 @@
-module(diameter_peer).
-behaviour(gen_server).
--compile({no_auto_import, [now/0]}).
--import(diameter_lib, [now/0]).
-
%% Interface towards transport modules ...
-export([recv/2,
up/1,
@@ -121,7 +118,7 @@ pair([{transport_module, M} | Rest], Mods, Acc) ->
pair([{transport_config = T, C} | Rest], Mods, Acc) ->
pair([{T, C, ?DEFAULT_TTMO} | Rest], Mods, Acc);
pair([{transport_config, C, Tmo} | Rest], Mods, Acc) ->
- pair(Rest, [], acc({Mods, C, Tmo}, Acc));
+ pair(Rest, [], acc({lists:reverse(Mods), C, Tmo}, Acc));
pair([_ | Rest], Mods, Acc) ->
pair(Rest, Mods, Acc);
@@ -130,13 +127,16 @@ pair([_ | Rest], Mods, Acc) ->
pair([], [], []) ->
[{[?DEFAULT_TMOD], ?DEFAULT_TCFG, ?DEFAULT_TTMO}];
-%% One transport_module, one transport_config.
-pair([], [M], [{[], Cfg, Tmo}]) ->
- [{[M], Cfg, Tmo}];
+%% One transport_module, one transport_config: ignore option order.
+%% That is, interpret [{transport_config, _}, {transport_module, _}]
+%% as if the order was reversed, not as config with default module and
+%% module with default config.
+pair([], [_] = Mods, [{[], Cfg, Tmo}]) ->
+ [{Mods, Cfg, Tmo}];
%% Trailing transport_module: default transport_config.
pair([], [_|_] = Mods, Acc) ->
- lists:reverse(acc({Mods, ?DEFAULT_TCFG, ?DEFAULT_TTMO}, Acc));
+ pair([{transport_config, ?DEFAULT_TCFG}], Mods, Acc);
pair([], [], Acc) ->
lists:reverse(def(Acc)).
@@ -201,10 +201,10 @@ match1(Addr, Match) ->
match(Addr, {ok, A}, _) ->
Addr == A;
match(Addr, {error, _}, RE) ->
- match == re:run(inet_parse:ntoa(Addr), RE, [{capture, none}]).
+ match == re:run(inet:ntoa(Addr), RE, [{capture, none}, caseless]).
addr([_|_] = A) ->
- inet_parse:address(A);
+ inet:parse_address(A);
addr(A) ->
{ok, A}.
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index 2255d0a76b..a9ee4940a3 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -319,7 +319,7 @@ handle_info(T, #state{} = State) ->
?LOG(stop, Reason),
{stop, {shutdown, Reason}, State};
stop ->
- ?LOG(stop, T),
+ ?LOG(stop, truncate(T)),
{stop, {shutdown, T}, State}
catch
exit: {diameter_codec, encode, T} = Reason ->
@@ -355,6 +355,11 @@ code_change(_, State, _) ->
%% ---------------------------------------------------------------------------
%% ---------------------------------------------------------------------------
+truncate({'DOWN' = T, _, process, Pid, _}) ->
+ {T, Pid};
+truncate(T) ->
+ T.
+
putr(Key, Val) ->
put({?MODULE, Key}, Val).
diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl
index f785777874..6bb4710e63 100644
--- a/lib/diameter/src/base/diameter_reg.erl
+++ b/lib/diameter/src/base/diameter_reg.erl
@@ -24,8 +24,7 @@
-module(diameter_reg).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2, now/0]}).
--import(diameter_lib, [now/0]).
+-compile({no_auto_import, [monitor/2]}).
-export([add/1,
add_new/1,
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 86e744dfbe..7d4a24f7a0 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -24,9 +24,6 @@
-module(diameter_service).
-behaviour(gen_server).
--compile({no_auto_import, [now/0]}).
--import(diameter_lib, [now/0]).
-
%% towards diameter_service_sup
-export([start_link/1]).
@@ -131,6 +128,7 @@
| {share_peers, diameter:remotes()} %% broadcast to
| {use_shared_peers, diameter:remotes()} %% use from
| {restrict_connections, diameter:restriction()}
+ | {strict_mbit, boolean()}
| {string_decode, boolean()}
| {incoming_maxlen, diameter:message_length()}]}).
%% shared_peers reflects the peers broadcast from remote nodes.
@@ -700,7 +698,8 @@ service_options(Opts) ->
?RESTRICT)},
{spawn_opt, proplists:get_value(spawn_opt, Opts, [])},
{string_decode, proplists:get_value(string_decode, Opts, true)},
- {incoming_maxlen, proplists:get_value(incoming_maxlen, Opts, 16#FFFFFF)}].
+ {incoming_maxlen, proplists:get_value(incoming_maxlen, Opts, 16#FFFFFF)},
+ {strict_mbit, proplists:get_value(strict_mbit, Opts, true)}].
%% The order of options is significant since we match against the list.
mref(false = No) ->
@@ -1218,7 +1217,7 @@ connect_timer(Opts, Def0) ->
%% continuous restarted in case of faulty config or other problems.
tc(Time, Tc) ->
choose(Tc > ?RESTART_TC
- orelse diameter_lib:micro_diff(Time) > 1000*?RESTART_TC,
+ orelse timer:now_diff(now(), Time) > 1000*?RESTART_TC,
Tc,
?RESTART_TC).
diff --git a/lib/diameter/src/base/diameter_session.erl b/lib/diameter/src/base/diameter_session.erl
index c5ea0428b5..3b236f109a 100644
--- a/lib/diameter/src/base/diameter_session.erl
+++ b/lib/diameter/src/base/diameter_session.erl
@@ -157,8 +157,8 @@ session_id(Host) ->
%% ---------------------------------------------------------------------------
init() ->
- {Now, Seed} = diameter_lib:seed(),
- random:seed(Seed),
+ Now = now(),
+ random:seed(Now),
Time = time32(Now),
Seq = (?INT32 band (Time bsl 20)) bor (random:uniform(1 bsl 20) - 1),
ets:insert(diameter_sequence, [{origin_state_id, Time},
diff --git a/lib/diameter/src/base/diameter_stats.erl b/lib/diameter/src/base/diameter_stats.erl
index 64ea082be0..c4526d3a08 100644
--- a/lib/diameter/src/base/diameter_stats.erl
+++ b/lib/diameter/src/base/diameter_stats.erl
@@ -24,9 +24,6 @@
-module(diameter_stats).
-behaviour(gen_server).
--compile({no_auto_import, [now/0]}).
--import(diameter_lib, [now/0]).
-
-export([reg/2, reg/1,
incr/3, incr/1,
read/1,
@@ -142,9 +139,14 @@ read(Refs, B) ->
L.
to_refdict(L) ->
- lists:foldl(fun({{C,R}, N}, D) -> orddict:append(R, {C,N}, D) end,
- orddict:new(),
- L).
+ lists:foldl(fun append/2, orddict:new(), L).
+
+%% Order both references and counters in the returned list.
+append({{Ctr, Ref}, N}, Dict) ->
+ orddict:update(Ref,
+ fun(D) -> orddict:store(Ctr, N, D) end,
+ [{Ctr, N}],
+ Dict).
%% ---------------------------------------------------------------------------
%% # sum(Refs)
@@ -220,7 +222,7 @@ uptime() ->
%% ----------------------------------------------------------
init([]) ->
- ets:new(?TABLE, [named_table, ordered_set, public]),
+ ets:new(?TABLE, [named_table, set, public, {write_concurrency, true}]),
{ok, #state{}}.
%% ----------------------------------------------------------
diff --git a/lib/diameter/src/base/diameter_sync.erl b/lib/diameter/src/base/diameter_sync.erl
index 90eabece3d..cee06b9e96 100644
--- a/lib/diameter/src/base/diameter_sync.erl
+++ b/lib/diameter/src/base/diameter_sync.erl
@@ -27,9 +27,6 @@
-module(diameter_sync).
-behaviour(gen_server).
--compile({no_auto_import, [now/0]}).
--import(diameter_lib, [now/0]).
-
-export([call/4, call/5,
cast/4, cast/5,
carp/1, carp/2]).
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 61ea5e69ba..004c1e80d5 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -79,6 +79,7 @@
apps :: [#diameter_app{}],
sequence :: diameter:sequence(),
codec :: [{string_decode, boolean()}
+ | {strict_mbit, boolean()}
| {incoming_maxlen, diameter:message_length()}]}).
%% Note that incoming_maxlen is currently handled in diameter_peer_fsm,
%% so that any message exceeding the maximum is discarded. Retain the
@@ -108,7 +109,8 @@ make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) ->
sequence = Mask,
codec = [T || {K,_} = T <- SvcOpts,
lists:member(K, [string_decode,
- incoming_maxlen])]}.
+ incoming_maxlen,
+ strict_mbit])]}.
%% ---------------------------------------------------------------------------
%% peer_up/1
@@ -261,7 +263,8 @@ recv(false, #request{ref = Ref, handler = Pid} = Req, _, Pkt, Dict0, _) ->
%% any others are discarded.
%% ... or not.
-recv(false, false, TPid, _, _, _) ->
+recv(false, false, TPid, Pkt, _, _) ->
+ ?LOG(discarded, Pkt#diameter_packet.header),
incr(TPid, {{unknown, 0}, recv, discarded}),
ok.
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index de9c4bca33..26bca7a5bc 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -65,7 +65,9 @@
%% end PCB
parent = self() :: pid(), %% service process
transport :: pid() | undefined, %% peer_fsm process
- tref :: reference(), %% reference for current watchdog timer
+ tref :: reference() %% reference for current watchdog timer
+ | tuple() %% now()
+ | undefined,
dictionary :: module(), %% common dictionary
receive_data :: term(),
%% term passed into diameter_service with incoming message
@@ -93,7 +95,7 @@ start({_,_} = Type, T) ->
Ack = make_ref(),
{ok, Pid} = diameter_watchdog_sup:start_child({Ack, Type, self(), T}),
try
- {erlang:monitor(process, Pid), Pid}
+ {monitor(process, Pid), Pid}
after
send(Pid, Ack)
end.
@@ -120,10 +122,9 @@ i({Ack, T, Pid, {RecvData,
#diameter_service{applications = Apps,
capabilities = Caps}
= Svc}}) ->
- erlang:monitor(process, Pid),
+ monitor(process, Pid),
wait(Ack, Pid),
- {_, Seed} = diameter_lib:seed(),
- random:seed(Seed),
+ random:seed(now()),
putr(restart, {T, Opts, Svc, SvcOpts}), %% save seeing it in trace
putr(dwr, dwr(Caps)), %%
{_,_} = Mask = proplists:get_value(sequence, SvcOpts),
@@ -245,11 +246,16 @@ handle_info(T, #watchdog{} = State) ->
event(T, State, S), %% before 'watchdog'
{noreply, S};
stop ->
- ?LOG(stop, T),
+ ?LOG(stop, truncate(T)),
event(T, State, State#watchdog{status = down}),
{stop, {shutdown, T}, State}
end.
+truncate({'DOWN' = T, _, process, Pid, _}) ->
+ {T, Pid};
+truncate(T) ->
+ T.
+
close({'DOWN', _, process, TPid, {shutdown, Reason}},
#watchdog{transport = TPid,
parent = Pid}) ->
@@ -446,11 +452,12 @@ transition({recv, TPid, Name, Pkt}, #watchdog{transport = TPid} = S) ->
%% Current watchdog has timed out.
transition({timeout, TRef, tw}, #watchdog{tref = TRef} = S) ->
- set_watchdog(timeout(S));
+ set_watchdog(0, timeout(S));
-%% Timer was canceled after message was already sent.
-transition({timeout, _, tw}, #watchdog{}) ->
- ok;
+%% Message has arrived since the timer was started: subtract time
+%% already elapsed from new timer.
+transition({timeout, _, tw}, #watchdog{tref = T0} = S) ->
+ set_watchdog(timer:now_diff(now(), T0) div 1000, S);
%% State query.
transition({state, Pid}, #watchdog{status = S}) ->
@@ -526,18 +533,27 @@ role() ->
%% set_watchdog/1
-set_watchdog(#watchdog{tw = TwInit,
- tref = TRef}
- = S) ->
- cancel(TRef),
- S#watchdog{tref = erlang:start_timer(tw(TwInit), self(), tw)};
-set_watchdog(stop = No) ->
- No.
+%% Timer not yet set.
+set_watchdog(#watchdog{tref = undefined} = S) ->
+ set_watchdog(0, S);
-cancel(undefined) ->
- ok;
-cancel(TRef) ->
- erlang:cancel_timer(TRef).
+%% Timer already set: start at new one only at expiry.
+set_watchdog(#watchdog{} = S) ->
+ S#watchdog{tref = now()}.
+
+%% set_watchdog/2
+
+set_watchdog(_, stop = No) ->
+ No;
+
+set_watchdog(Ms, #watchdog{tw = TwInit} = S) ->
+ S#watchdog{tref = erlang:start_timer(tw(TwInit, Ms), self(), tw)}.
+
+%% A callback could return anything, so ensure the result isn't
+%% negative. Don't prevent abuse, even though the smallest valid
+%% timeout is 4000.
+tw(TwInit, Ms) ->
+ max(tw(TwInit) - Ms, 0).
tw(T)
when is_integer(T), T >= 6000 ->