aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diameter/src')
-rw-r--r--lib/diameter/src/Makefile4
-rw-r--r--lib/diameter/src/app.sed2
-rw-r--r--lib/diameter/src/base/diameter.erl30
-rw-r--r--lib/diameter/src/base/diameter_app.erl2
-rw-r--r--lib/diameter/src/base/diameter_callback.erl2
-rw-r--r--lib/diameter/src/base/diameter_config.erl128
-rw-r--r--lib/diameter/src/base/diameter_config_sup.erl58
-rw-r--r--lib/diameter/src/base/diameter_dict.erl2
-rw-r--r--lib/diameter/src/base/diameter_internal.hrl2
-rw-r--r--lib/diameter/src/base/diameter_lib.erl57
-rw-r--r--lib/diameter/src/base/diameter_misc_sup.erl2
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl120
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm_sup.erl2
-rw-r--r--lib/diameter/src/base/diameter_reg.erl423
-rw-r--r--lib/diameter/src/base/diameter_service.erl93
-rw-r--r--lib/diameter/src/base/diameter_session.erl7
-rw-r--r--lib/diameter/src/base/diameter_sup.erl5
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl299
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl74
-rw-r--r--lib/diameter/src/base/diameter_watchdog_sup.erl2
-rw-r--r--lib/diameter/src/compiler/diameter_codegen.erl17
-rw-r--r--lib/diameter/src/compiler/diameter_dict_parser.yrl2
-rw-r--r--lib/diameter/src/compiler/diameter_dict_scanner.erl2
-rw-r--r--lib/diameter/src/compiler/diameter_dict_util.erl2
-rw-r--r--lib/diameter/src/compiler/diameter_exprecs.erl2
-rw-r--r--lib/diameter/src/compiler/diameter_make.erl2
-rw-r--r--lib/diameter/src/compiler/diameter_vsn.hrl2
-rw-r--r--lib/diameter/src/depend.sed2
-rw-r--r--lib/diameter/src/diameter.app.src2
-rw-r--r--lib/diameter/src/diameter.appup.src16
-rw-r--r--lib/diameter/src/dict/acct_rfc6733.dia2
-rw-r--r--lib/diameter/src/dict/base_accounting.dia2
-rw-r--r--lib/diameter/src/dict/base_rfc3588.dia2
-rw-r--r--lib/diameter/src/dict/base_rfc6733.dia2
-rw-r--r--lib/diameter/src/dict/capup_rfc6737.dia2
-rw-r--r--lib/diameter/src/dict/relay.dia2
-rw-r--r--lib/diameter/src/info/diameter_dbg.erl2
-rw-r--r--lib/diameter/src/modules.mk3
-rw-r--r--lib/diameter/src/transport/diameter_etcp.erl2
-rw-r--r--lib/diameter/src/transport/diameter_etcp_sup.erl2
-rw-r--r--lib/diameter/src/transport/diameter_sctp.erl117
-rw-r--r--lib/diameter/src/transport/diameter_sctp_sup.erl2
-rw-r--r--lib/diameter/src/transport/diameter_tcp.erl366
-rw-r--r--lib/diameter/src/transport/diameter_tcp_sup.erl2
-rw-r--r--lib/diameter/src/transport/diameter_transport.erl2
45 files changed, 1188 insertions, 685 deletions
diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile
index e0bbbdfe63..6bf748a727 100644
--- a/lib/diameter/src/Makefile
+++ b/lib/diameter/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2014. All Rights Reserved.
+# Copyright Ericsson AB 2010-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -123,7 +123,7 @@ ERL_COMPILE_FLAGS += \
# erl/hrl from dictionary file.
gen/diameter_gen_%.erl gen/diameter_gen_%.hrl: dict/%.dia
$(dia_verbose) \
- ../bin/diameterc -o gen -i $(EBIN) $<
+ escript ../bin/diameterc -o gen -i $(EBIN) $<
opt: $(TARGET_FILES)
diff --git a/lib/diameter/src/app.sed b/lib/diameter/src/app.sed
index 78e5bd2bad..dd3806f5f1 100644
--- a/lib/diameter/src/app.sed
+++ b/lib/diameter/src/app.sed
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2014. All Rights Reserved.
+# Copyright Ericsson AB 2014-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index de88f6befd..253f64133c 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -36,6 +36,8 @@
%% Information.
-export([services/0,
+ peer_info/1,
+ peer_find/1,
service_info/2]).
%% Start/stop the application. In a "real" application this should
@@ -53,6 +55,7 @@
service_name/0,
capability/0,
peer_filter/0,
+ peer_ref/0,
service_opt/0,
application_opt/0,
app_module/0,
@@ -147,6 +150,27 @@ service_info(SvcName, Option) ->
diameter_service:info(SvcName, Option).
%% ---------------------------------------------------------------------------
+%% peer_info/2
+%% ---------------------------------------------------------------------------
+
+-spec peer_info(peer_ref())
+ -> [tuple()].
+
+peer_info(PeerRef) ->
+ diameter_service:peer_info(PeerRef).
+
+%% ---------------------------------------------------------------------------
+%% peer_find/1
+%% ---------------------------------------------------------------------------
+
+-spec peer_find(peer_ref() | pid())
+ -> {peer_ref(), pid()}
+ | false.
+
+peer_find(Pid) ->
+ diameter_peer_fsm:find(Pid).
+
+%% ---------------------------------------------------------------------------
%% add_transport/3
%% ---------------------------------------------------------------------------
@@ -280,6 +304,9 @@ call(SvcName, App, Message) ->
| {all, [peer_filter()]}
| {any, [peer_filter()]}.
+-opaque peer_ref()
+ :: pid().
+
-type evaluable()
:: {module(), atom(), list()}
| fun()
@@ -350,6 +377,7 @@ call(SvcName, App, Message) ->
| {capabilities, [capability()]}
| {capabilities_cb, evaluable()}
| {capx_timeout, 'Unsigned32'()}
+ | {capx_strictness, boolean()}
| {disconnect_cb, evaluable()}
| {dpr_timeout, 'Unsigned32'()}
| {dpa_timeout, 'Unsigned32'()}
diff --git a/lib/diameter/src/base/diameter_app.erl b/lib/diameter/src/base/diameter_app.erl
index 6f0c78094a..122f60dd88 100644
--- a/lib/diameter/src/base/diameter_app.erl
+++ b/lib/diameter/src/base/diameter_app.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/base/diameter_callback.erl b/lib/diameter/src/base/diameter_callback.erl
index 70c70fb5bd..f479cb6612 100644
--- a/lib/diameter/src/base/diameter_callback.erl
+++ b/lib/diameter/src/base/diameter_callback.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 702f11593a..245a3ea7ac 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -38,17 +38,17 @@
-module(diameter_config).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2]}).
-
-export([start_service/2,
stop_service/1,
add_transport/2,
remove_transport/2,
have_transport/2,
- lookup/1]).
+ lookup/1,
+ subscribe/2]).
-%% child server start
--export([start_link/0]).
+%% server start
+-export([start_link/0,
+ start_link/1]).
%% gen_server callbacks
-export([init/1,
@@ -58,8 +58,8 @@
handle_info/2,
code_change/3]).
-%% diameter_sync requests.
--export([sync/1]).
+%% callbacks
+-export([sync/1]). %% diameter_sync requests
%% debug
-export([state/0,
@@ -69,7 +69,8 @@
-include("diameter_internal.hrl").
%% Server state.
--record(state, {id = diameter_lib:now()}).
+-record(state, {id = diameter_lib:now(),
+ role :: server | transport}).
%% Registered name of the server.
-define(SERVER, ?MODULE).
@@ -77,6 +78,9 @@
%% Table config is written to.
-define(TABLE, ?MODULE).
+%% Key on which a transport-specific child registers itself.
+-define(TRANSPORT_KEY(Ref), {?MODULE, transport, Ref}).
+
%% Workaround for dialyzer's lack of understanding of match specs.
-type match(T)
:: T | '_' | '$1' | '$2' | '$3' | '$4'.
@@ -225,6 +229,13 @@ pred(_) ->
?THROW(pred).
%% --------------------------------------------------------------------------
+%% # subscribe/2
+%% --------------------------------------------------------------------------
+
+subscribe(Ref, T) ->
+ diameter_reg:subscribe(?TRANSPORT_KEY(Ref), T).
+
+%% --------------------------------------------------------------------------
%% # have_transport/2
%%
%% Output: true | false
@@ -264,6 +275,9 @@ start_link() ->
Options = [{spawn_opt, diameter_lib:spawn_opts(server, [])}],
gen_server:start_link(ServerName, Module, Args, Options).
+start_link(T) ->
+ proc_lib:start_link(?MODULE, init, [T], infinity, []).
+
state() ->
call(state).
@@ -274,8 +288,27 @@ uptime() ->
%%% # init/1
%%% ----------------------------------------------------------
+%% ?SERVER start.
init([]) ->
- {ok, #state{}}.
+ {ok, #state{role = server}};
+
+%% Child start as a consequence of add_transport.
+init({SvcName, Type, Opts}) ->
+ Res = try
+ add(SvcName, Type, Opts)
+ catch
+ ?FAILURE(Reason) -> {error, Reason}
+ end,
+ proc_lib:init_ack({ok, self(), Res}),
+ loop(Res).
+
+%% loop/1
+
+loop({ok, _}) ->
+ gen_server:enter_loop(?MODULE, [], #state{role = transport});
+
+loop({error, _}) ->
+ ok. %% die
%%% ----------------------------------------------------------
%%% # handle_call/2
@@ -284,8 +317,8 @@ init([]) ->
handle_call(state, _, State) ->
{reply, State, State};
-handle_call(uptime, _, #state{id = Time} = State) ->
- {reply, diameter_lib:now_diff(Time), State};
+handle_call(uptime, _, #state{id = Time} = S) ->
+ {reply, diameter_lib:now_diff(Time), S};
handle_call(Req, From, State) ->
?UNEXPECTED([Req, From]),
@@ -304,30 +337,34 @@ handle_cast(Msg, State) ->
%%% # handle_info/2
%%% ----------------------------------------------------------
+%% remove_transport is telling published child to die.
+handle_info(stop, #state{role = transport} = S) ->
+ {stop, normal, S};
+
%% A service process has died. This is most likely a consequence of
%% stop_service, in which case the restart will find no config for the
%% service and do nothing. The entry keyed on the monitor ref is only
%% removed as a result of the 'DOWN' notification however.
-handle_info({'DOWN', MRef, process, _, Reason}, State) ->
+handle_info({'DOWN', MRef, process, _, Reason}, #state{role = server} = S) ->
[#monitor{service = SvcName} = T] = select([{#monitor{mref = MRef,
_ = '_'},
[],
['$_']}]),
queue_restart(Reason, SvcName),
delete_object(T),
- {noreply, State};
+ {noreply, S};
-handle_info({monitor, SvcName, Pid}, State) ->
- monitor(Pid, SvcName),
- {noreply, State};
+handle_info({monitor, SvcName, Pid}, #state{role = server} = S) ->
+ insert_monitor(Pid, SvcName),
+ {noreply, S};
-handle_info({restart, SvcName}, State) ->
+handle_info({restart, SvcName}, #state{role = server} = S) ->
restart(SvcName),
- {noreply, State};
+ {noreply, S};
-handle_info(restart, State) ->
+handle_info(restart, #state{role = server} = S) ->
restart(),
- {noreply, State};
+ {noreply, S};
handle_info(Info, State) ->
?UNEXPECTED([Info]),
@@ -404,19 +441,22 @@ sync({start_service, SvcName, Opts}) ->
sync({stop_service, SvcName}) ->
stop(SvcName);
+%% Start a child whose only purpose is to be alive for the lifetime of
+%% the transport configuration and publish itself in diameter_reg.
+%% This is to provide a way for processes to to be notified when the
+%% configuration is removed (diameter_reg:subscribe/2).
sync({add, SvcName, Type, Opts}) ->
- try
- add(SvcName, Type, Opts)
- catch
- ?FAILURE(Reason) -> {error, Reason}
- end;
+ {ok, _Pid, Res} = diameter_config_sup:start_child({SvcName, Type, Opts}),
+ Res;
sync({remove, SvcName, Pred}) ->
- remove(select([{#transport{service = '$1', _ = '_'},
+ Recs = select([{#transport{service = '$1', _ = '_'},
[{'=:=', '$1', {const, SvcName}}],
['$_']}]),
- SvcName,
- Pred).
+ F = fun(#transport{ref = R, type = T, options = O}) ->
+ Pred(R,T,O)
+ end,
+ remove(SvcName, lists:filter(F, Recs)).
%% start/3
@@ -438,8 +478,8 @@ startmon(SvcName, {ok, Pid}) ->
startmon(_, {error, _}) ->
ok.
-monitor(Pid, SvcName) ->
- MRef = erlang:monitor(process, Pid),
+insert_monitor(Pid, SvcName) ->
+ MRef = monitor(process, Pid),
insert(#monitor{mref = MRef, service = SvcName}).
%% queue_restart/2
@@ -503,6 +543,7 @@ add(SvcName, Type, Opts) ->
ok = transport_opts(Opts),
Ref = make_ref(),
+ true = diameter_reg:add_new(?TRANSPORT_KEY(Ref)),
T = {Ref, Type, Opts},
%% The call to the service returns error if the service isn't
%% started yet, which is harmless. The transport will be started
@@ -539,6 +580,9 @@ opt({K, Tmo})
K == dpa_timeout ->
?IS_UINT32(Tmo);
+opt({capx_strictness, B}) ->
+ is_boolean(B);
+
opt({length_errors, T}) ->
lists:member(T, [exit, handle, discard]);
@@ -594,26 +638,30 @@ start_transport(SvcName, T) ->
No
end.
-%% remove/3
-
-remove(L, SvcName, Pred) ->
- rm(SvcName, lists:filter(fun(#transport{ref = R, type = T, options = O}) ->
- Pred(R,T,O)
- end,
- L)).
+%% remove/2
-rm(_, []) ->
+remove(_, []) ->
ok;
-rm(SvcName, L) ->
+
+remove(SvcName, L) ->
Refs = lists:map(fun(#transport{ref = R}) -> R end, L),
case stop_transport(SvcName, Refs) of
ok ->
+ lists:foreach(fun stop_child/1, Refs),
diameter_stats:flush(Refs),
lists:foreach(fun delete_object/1, L);
{error, _} = No ->
No
end.
+stop_child(Ref) ->
+ case diameter_reg:match(?TRANSPORT_KEY(Ref)) of
+ [{_, Pid}] -> %% tell the transport-specific child to die
+ Pid ! stop;
+ [] -> %% already removed/dead
+ ok
+ end.
+
stop_transport(SvcName, Refs) ->
case diameter_service:stop_transport(SvcName, Refs) of
ok ->
diff --git a/lib/diameter/src/base/diameter_config_sup.erl b/lib/diameter/src/base/diameter_config_sup.erl
new file mode 100644
index 0000000000..9524573378
--- /dev/null
+++ b/lib/diameter/src/base/diameter_config_sup.erl
@@ -0,0 +1,58 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2016. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% Supervisor for config processes.
+%%
+
+-module(diameter_config_sup).
+
+-behaviour(supervisor).
+
+%% interface
+-export([start_link/0, %% supervisor start
+ start_child/1]). %% config start
+
+-export([init/1]).
+
+-define(NAME, ?MODULE). %% supervisor name
+
+%% start_link/0
+
+start_link() ->
+ SupName = {local, ?NAME},
+ supervisor:start_link(SupName, ?MODULE, []).
+
+%% start_child/1
+
+start_child(T) ->
+ supervisor:start_child(?NAME, [T]).
+
+%% init/1
+
+init([]) ->
+ Mod = diameter_config,
+ Flags = {simple_one_for_one, 0, 1},
+ ChildSpec = {Mod,
+ {Mod, start_link, []},
+ temporary,
+ 1000,
+ worker,
+ [Mod]},
+ {ok, {Flags, [ChildSpec]}}.
diff --git a/lib/diameter/src/base/diameter_dict.erl b/lib/diameter/src/base/diameter_dict.erl
index 1013690a5b..7db294a1b1 100644
--- a/lib/diameter/src/base/diameter_dict.erl
+++ b/lib/diameter/src/base/diameter_dict.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/base/diameter_internal.hrl b/lib/diameter/src/base/diameter_internal.hrl
index 518c0b9b1f..a0f4a8567d 100644
--- a/lib/diameter/src/base/diameter_internal.hrl
+++ b/lib/diameter/src/base/diameter_internal.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index 43b0ca24ab..3928769b5e 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,18 +20,17 @@
-module(diameter_lib).
-compile({no_auto_import, [now/0]}).
--compile({nowarn_deprecated_function, [{erlang, now, 0}]}).
-export([info_report/2,
error_report/2,
warning_report/2,
now/0,
+ timestamp/0,
timestamp/1,
now_diff/1,
micro_diff/1,
micro_diff/2,
time/1,
- seed/0,
eval/1,
eval_name/1,
get_stacktrace/0,
@@ -110,6 +109,16 @@ now() ->
erlang:monotonic_time().
%% ---------------------------------------------------------------------------
+%% # timestamp/0
+%% ---------------------------------------------------------------------------
+
+-spec timestamp()
+ -> erlang:timestamp().
+
+timestamp() ->
+ timestamp(now()).
+
+%% ---------------------------------------------------------------------------
%% # timestamp/1
%% ---------------------------------------------------------------------------
@@ -184,24 +193,6 @@ time(Micro) -> %% elapsed time
{H, M, S, Micro rem 1000000}.
%% ---------------------------------------------------------------------------
-%% # seed/0
-%% ---------------------------------------------------------------------------
-
--spec seed()
- -> {erlang:timestamp(), {integer(), integer(), integer()}}.
-
-%% Return an argument for random:seed/1.
-
-seed() ->
- T = now(),
- {timestamp(T), seed(T)}.
-
-%% seed/1
-
-seed(T) -> %% monotonic time
- {erlang:phash2(node()), T, erlang:unique_integer()}.
-
-%% ---------------------------------------------------------------------------
%% # eval/1
%%
%% Evaluate a function in various forms.
@@ -308,8 +299,28 @@ spawn_opts(server, Opts) ->
spawn_opts(worker, Opts) ->
opts(5000, Opts).
-opts(HeapSize, Opts) ->
- [{min_heap_size, HeapSize} | lists:keydelete(min_heap_size, 1, Opts)].
+%% These setting are historical rather than useful. In particular, the
+%% server setting can bloat many processes unnecessarily. Let them be
+%% disabled with -diameter min_heap_size false.
+
+opts(Def, Opts) ->
+ Key = min_heap_size,
+ case getenv(Key, Def) of
+ N when is_integer(N), 0 =< N ->
+ [{Key, N} | lists:keydelete(Key, 1, Opts)];
+ _ ->
+ Opts
+ end.
+
+%% getenv/1
+
+getenv(Key, Def) ->
+ case application:get_env(Key) of
+ {ok, T} ->
+ T;
+ undefined ->
+ Def
+ end.
%% ---------------------------------------------------------------------------
%% # wait/1
diff --git a/lib/diameter/src/base/diameter_misc_sup.erl b/lib/diameter/src/base/diameter_misc_sup.erl
index 2054ea7831..343688be23 100644
--- a/lib/diameter/src/base/diameter_misc_sup.erl
+++ b/lib/diameter/src/base/diameter_misc_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index 2b23183d18..7ee1e5fe59 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -32,6 +32,9 @@
-export([start/3,
result_code/2]).
+%% Interface towards diameter.
+-export([find/1]).
+
%% gen_server callbacks
-export([init/1,
handle_call/3,
@@ -117,7 +120,7 @@
parent :: pid(), %% watchdog process
transport :: pid(), %% transport process
dictionary :: module(), %% common dictionary
- service :: #diameter_service{},
+ service :: #diameter_service{} | undefined,
dpr = false :: false
| true %% DPR received, DPA sent
| {boolean(), uint32(), uint32()},
@@ -125,6 +128,7 @@
%% outgoing DPR; boolean says whether or not
%% the request was sent explicitly with
%% diameter:call/4.
+ strict :: boolean(),
length_errors :: exit | handle | discard,
incoming_maxlen :: integer() | infinity}).
@@ -185,6 +189,25 @@ start_link(T) ->
infinity,
diameter_lib:spawn_opts(server, [])).
+%% find/1
+%%
+%% Identify both pids of a peer_fsm/transport pair.
+
+find(Pid) ->
+ findl([{?MODULE, '_', Pid}, {?MODULE, Pid, '_'}]).
+
+findl([]) ->
+ false;
+
+findl([Pat | Rest]) ->
+ try
+ [{{_, Pid, TPid}, Pid}] = diameter_reg:match(Pat),
+ {Pid, TPid}
+ catch
+ error:_ ->
+ findl(Rest)
+ end.
+
%% ---------------------------------------------------------------------------
%% ---------------------------------------------------------------------------
@@ -211,10 +234,13 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) ->
proplists:get_value(dpa_timeout, Opts, ?DPA_TIMEOUT)}),
Tmo = proplists:get_value(capx_timeout, Opts, ?CAPX_TIMEOUT),
+ Strictness = proplists:get_value(capx_strictness, Opts, true),
OnLengthErr = proplists:get_value(length_errors, Opts, exit),
{TPid, Addrs} = start_transport(T, Rest, Svc),
+ diameter_reg:add({?MODULE, self(), TPid}), %% lets pairs be discovered
+
#state{state = {'Wait-Conn-Ack', Tmo},
parent = WPid,
transport = TPid,
@@ -222,6 +248,7 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) ->
mode = M,
service = svc(Svc, Addrs),
length_errors = OnLengthErr,
+ strict = Strictness,
incoming_maxlen = Maxlen}.
%% The transport returns its local ip addresses so that different
%% transports on the same service can use different local addresses.
@@ -416,8 +443,8 @@ transition({connection_timeout, _}, _) ->
ok;
%% Incoming message from the transport.
-transition({diameter, {recv, Pkt}}, S) ->
- recv(Pkt, S);
+transition({diameter, {recv, MsgT}}, S) ->
+ incoming(MsgT, S);
%% Timeout when still in the same state ...
transition({timeout = T, PS}, #state{state = PS}) ->
@@ -430,6 +457,9 @@ transition({timeout, _}, _) ->
%% Outgoing message.
transition({send, Msg}, S) ->
outgoing(Msg, S);
+transition({send, Msg, Route}, S) ->
+ put_route(Route),
+ outgoing(Msg, S);
%% Request for graceful shutdown at remove_transport, stop_service of
%% application shutdown.
@@ -459,8 +489,10 @@ transition({'DOWN', _, process, TPid, _},
= S) ->
start_next(S);
-%% Transport has died after connection timeout.
-transition({'DOWN', _, process, _, _}, _) ->
+%% Transport has died after connection timeout, or handler process has
+%% died.
+transition({'DOWN', _, process, Pid, _}, _) ->
+ erase_route(Pid),
ok;
%% State query.
@@ -470,6 +502,40 @@ transition({state, Pid}, #state{state = S, transport = TPid}) ->
%% Crash on anything unexpected.
+%% put_route/1
+%%
+%% Map identifiers in an outgoing request to be able to lookup the
+%% handler process when the answer is received.
+
+put_route({Pid, Ref, Seqs}) ->
+ MRef = monitor(process, Pid),
+ put(Pid, Seqs),
+ put(Seqs, {Pid, Ref, MRef}).
+
+%% get_route/1
+
+get_route(#diameter_packet{header = #diameter_header{is_request = false}}
+ = Pkt) ->
+ Seqs = diameter_codec:sequence_numbers(Pkt),
+ case erase(Seqs) of
+ {Pid, Ref, MRef} ->
+ demonitor(MRef),
+ erase(Pid),
+ {Pid, Ref, self()};
+ undefined ->
+ false
+ end;
+
+get_route(_) ->
+ false.
+
+%% erase_route/1
+
+erase_route(Pid) ->
+ erase(erase(Pid)).
+
+%% capx/1
+
capx(recv_CER) ->
'CER';
capx({'Wait-CEA', _, _}) ->
@@ -543,6 +609,32 @@ encode(Rec, Dict) ->
diameter_codec:encode(Dict, #diameter_packet{header = Hdr,
msg = Rec}).
+%% incoming/2
+
+incoming({Msg, NPid}, S) ->
+ try recv(Msg, S) of
+ T ->
+ NPid ! {diameter, discard},
+ T
+ catch
+ {?MODULE, Name, Pkt} ->
+ incoming(Name, Pkt, NPid, S)
+ end;
+
+incoming(Msg, S) ->
+ try
+ recv(Msg, S)
+ catch
+ {?MODULE, Name, Pkt} ->
+ incoming(Name, Pkt, false, S)
+ end.
+
+%% incoming/4
+
+incoming(Name, Pkt, NPid, #state{parent = Pid} = S) ->
+ Pid ! {recv, self(), get_route(Pkt), Name, Pkt, NPid},
+ rcv(Name, Pkt, S).
+
%% recv/2
recv(#diameter_packet{header = #diameter_header{} = Hdr}
@@ -568,6 +660,17 @@ recv1(_,
when M < size(Bin) ->
invalid(false, incoming_maxlen_exceeded, {size(Bin), H});
+%% Ignore anything but an expected CER/CEA if so configured. This is
+%% non-standard behaviour.
+recv1(Name, _, #state{state = {'Wait-CEA', _, _},
+ strict = false})
+ when Name /= 'CEA' ->
+ ok;
+recv1(Name, _, #state{state = recv_CER,
+ strict = false})
+ when Name /= 'CER' ->
+ ok;
+
%% Incoming request after outgoing DPR: discard. Don't discard DPR, so
%% both ends don't do so when sending simultaneously.
recv1(Name,
@@ -597,9 +700,8 @@ recv1('DPA' = N,
%% Any other message with a header and no length errors: send to the
%% parent.
-recv1(Name, Pkt, #state{parent = Pid} = S) ->
- Pid ! {recv, self(), Name, Pkt},
- rcv(Name, Pkt, S).
+recv1(Name, Pkt, #state{}) ->
+ throw({?MODULE, Name, Pkt}).
%% recv/3
diff --git a/lib/diameter/src/base/diameter_peer_fsm_sup.erl b/lib/diameter/src/base/diameter_peer_fsm_sup.erl
index 54bd06929d..cf3c205e3f 100644
--- a/lib/diameter/src/base/diameter_peer_fsm_sup.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl
index 7f198080ba..9027130063 100644
--- a/lib/diameter/src/base/diameter_reg.erl
+++ b/lib/diameter/src/base/diameter_reg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,14 +25,12 @@
-module(diameter_reg).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2]}).
-
-export([add/1,
add_new/1,
- del/1,
- repl/2,
+ remove/1,
match/1,
- wait/1]).
+ wait/1,
+ subscribe/2]).
-export([start_link/0]).
@@ -46,29 +44,32 @@
%% test
-export([pids/0,
- terms/0]).
+ terms/0,
+ subs/0,
+ waits/0]).
%% debug
-export([state/0,
uptime/0]).
--include("diameter_internal.hrl").
-
-define(SERVER, ?MODULE).
-define(TABLE, ?MODULE).
-%% Table entry used to keep from starting more than one monitor on the
-%% same process. This isn't a problem but there's no point in starting
-%% multiple monitors if we can avoid it. Note that we can't have a 2-tuple
-%% keyed on Pid since a registered term can be anything. Want the entry
-%% keyed on Pid so that lookup is fast.
--define(MONITOR(Pid, MRef), {Pid, monitor, MRef}).
-
-%% Table entry containing the Term -> Pid mapping.
--define(MAPPING(Term, Pid), {Term, Pid}).
+-type key() :: term().
+-type from() :: {pid(), term()}.
+-type pattern() :: term().
-record(state, {id = diameter_lib:now(),
- q = []}). %% [{From, Pat}]
+ receivers = dict:new()
+ :: dict:dict(pattern(), [[pid() | term()]%% subscribe
+ | from()]), %% wait
+ monitors = sets:new() :: sets:set(pid())}).
+
+%% The ?TABLE bag contains the Key -> Pid mapping, as {Key, Pid}
+%% tuples. Each pid is stored in the monitors set to ensure only one
+%% monitor for each pid: more are harmless, but unnecessary. A pattern
+%% is added to receivers a result of calls to wait/1 or subscribe/2:
+%% changes to ?TABLE causes processes to be notified as required.
%% ===========================================================================
%% # add(T)
@@ -77,18 +78,18 @@
%% this or other assocations can be retrieved using match/1.
%%
%% An association is removed when the calling process dies or as a
-%% result of calling del/1. Adding the same term more than once is
-%% equivalent to adding it exactly once.
+%% result of calling remove/1. Adding the same term more than once is
+%% equivalent to adding it once.
%%
%% Note that since match/1 takes a pattern as argument, specifying a
%% term that contains match variables is probably not a good idea
%% ===========================================================================
--spec add(any())
+-spec add(key())
-> true.
add(T) ->
- call({add, fun ets:insert/2, T, self()}).
+ call({add, false, T}).
%% ===========================================================================
%% # add_new(T)
@@ -97,36 +98,23 @@ add(T) ->
%% association, false being returned if an association already exists.
%% ===========================================================================
--spec add_new(any())
+-spec add_new(key())
-> boolean().
add_new(T) ->
- call({add, fun insert_new/2, T, self()}).
+ call({add, true, T}).
%% ===========================================================================
-%% # repl(T, NewT)
-%%
-%% Like add/1 but only replace an existing association on T, false
-%% being returned if it doesn't exist.
-%% ===========================================================================
-
--spec repl(any(), any())
- -> boolean().
-
-repl(T, U) ->
- call({repl, T, U, self()}).
-
-%% ===========================================================================
-%% # del(Term)
+%% # remove(Term)
%%
%% Remove any existing association of Term with self().
%% ===========================================================================
--spec del(any())
+-spec remove(key())
-> true.
-del(T) ->
- call({del, T, self()}).
+remove(T) ->
+ call({remove, T}).
%% ===========================================================================
%% # match(Pat)
@@ -139,12 +127,17 @@ del(T) ->
%% associations removed.)
%% ===========================================================================
--spec match(any())
- -> [{term(), pid()}].
+-spec match(pattern())
+ -> [{key(), pid()}].
match(Pat) ->
- ets:match_object(?TABLE, ?MAPPING(Pat, '_')).
+ match(Pat, '_').
+
+%% match/2
+match(Pat, Pid) ->
+ ets:match_object(?TABLE, {Pat, Pid}).
+
%% ===========================================================================
%% # wait(Pat)
%%
@@ -152,10 +145,29 @@ match(Pat) ->
%% It's up to the caller to ensure that the wait won't be forever.
%% ===========================================================================
+-spec wait(pattern())
+ -> [{key(), pid()}].
+
wait(Pat) ->
+ _ = match(Pat), %% ensure match can succeed
call({wait, Pat}).
%% ===========================================================================
+%% # subscribe(Pat, T)
+%%
+%% Like match/1, but additionally receive messages of the form
+%% {T, add|remove, {term(), pid()} when associations are added
+%% or removed.
+%% ===========================================================================
+
+-spec subscribe(Pat :: any(), T :: term())
+ -> [{term(), pid()}].
+
+subscribe(Pat, T) ->
+ _ = match(Pat), %% ensure match can succeed
+ call({subscribe, Pat, T}).
+
+%% ===========================================================================
start_link() ->
ServerName = {local, ?SERVER},
@@ -169,19 +181,15 @@ uptime() ->
call(uptime).
%% pids/0
-%%
-%% Return: list of {Pid, [Term, ...]}
+
+-spec pids()
+ -> [{pid(), [key()]}].
pids() ->
to_list(fun swap/1).
to_list(Fun) ->
- ets:foldl(fun(T,A) -> acc(Fun, T, A) end, orddict:new(), ?TABLE).
-
-acc(Fun, ?MAPPING(Term, Pid), Dict) ->
- append(Fun({Term, Pid}), Dict);
-acc(_, _, Dict) ->
- Dict.
+ ets:foldl(fun(T,D) -> append(Fun(T), D) end, orddict:new(), ?TABLE).
append({K,V}, Dict) ->
orddict:append(K, V, Dict).
@@ -189,14 +197,47 @@ append({K,V}, Dict) ->
id(T) -> T.
%% terms/0
-%%
-%% Return: list of {Term, [Pid, ...]}
+
+-spec terms()
+ -> [{key(), [pid()]}].
terms() ->
to_list(fun id/1).
swap({X,Y}) -> {Y,X}.
+%% subs/0
+
+-spec subs()
+ -> [{pattern(), [{pid(), term()}]}].
+
+subs() ->
+ #state{receivers = RD} = state(),
+ dict:fold(fun sub/3, orddict:new(), RD).
+
+sub(Pat, Ps, Dict) ->
+ lists:foldl(fun([P|T], D) -> orddict:append(Pat, {P,T}, D);
+ (_, D) -> D
+ end,
+ Dict,
+ Ps).
+
+%% waits/0
+
+-spec waits()
+ -> [{pattern(), [{from(), term()}]}].
+
+waits() ->
+ #state{receivers = RD} = state(),
+ dict:fold(fun wait/3, orddict:new(), RD).
+
+wait(Pat, Ps, Dict) ->
+ lists:foldl(fun({_,_} = F, D) -> orddict:append(Pat, F, D);
+ (_, D) -> D
+ end,
+ Dict,
+ Ps).
+
%% ----------------------------------------------------------
%% # init/1
%% ----------------------------------------------------------
@@ -209,57 +250,58 @@ init(_) ->
%% # handle_call/3
%% ----------------------------------------------------------
-handle_call({add, Fun, Key, Pid}, _, S) ->
- B = Fun(?TABLE, {Key, Pid}),
- monitor(B andalso no_monitor(Pid), Pid),
- {reply, B, pending(B, S)};
-
-handle_call({del, Key, Pid}, _, S) ->
- {reply, ets:delete_object(?TABLE, ?MAPPING(Key, Pid)), S};
-
-handle_call({repl, T, U, Pid}, _, S) ->
- MatchSpec = [{?MAPPING('$1', Pid),
- [{'=:=', '$1', {const, T}}],
- ['$_']}],
- {reply, repl(ets:select(?TABLE, MatchSpec), U, Pid), S};
-
-handle_call({wait, Pat}, From, #state{q = Q} = S) ->
- case find(Pat) of
- {ok, L} ->
- {reply, L, S};
- false ->
- {noreply, S#state{q = [{From, Pat} | Q]}}
+handle_call({add, Uniq, Key}, {Pid, _}, S0) ->
+ Rec = {Key, Pid},
+ S1 = flush(Uniq, Rec, S0),
+ {Res, New} = insert(Uniq, Rec),
+ {Recvs, S} = add(New, Rec, S1),
+ notify(Recvs, Rec),
+ {reply, Res, S};
+
+handle_call({remove, Key}, {Pid, _}, S) ->
+ Rec = {Key, Pid},
+ Recvs = delete([Rec], S),
+ ets:delete_object(?TABLE, Rec),
+ notify(Recvs, remove),
+ {reply, true, S};
+
+handle_call({wait, Pat}, {Pid, _} = From, #state{receivers = RD} = S) ->
+ NS = add_monitor(Pid, S),
+ case match(Pat) of
+ [_|_] = L ->
+ {reply, L, NS};
+ [] ->
+ {noreply, NS#state{receivers = dict:append(Pat, From, RD)}}
end;
+handle_call({subscribe, Pat, T}, {Pid, _}, #state{receivers = RD} = S) ->
+ NS = add_monitor(Pid, S),
+ {reply, match(Pat), NS#state{receivers = dict:append(Pat, [Pid | T], RD)}};
+
handle_call(state, _, S) ->
{reply, S, S};
handle_call(uptime, _, #state{id = Time} = S) ->
{reply, diameter_lib:now_diff(Time), S};
-handle_call(Req, From, S) ->
- ?UNEXPECTED([Req, From]),
+handle_call(_Req, _From, S) ->
{reply, nok, S}.
%% ----------------------------------------------------------
%% # handle_cast/2
%% ----------------------------------------------------------
-handle_cast(Msg, S)->
- ?UNEXPECTED([Msg]),
+handle_cast(_Msg, S)->
{noreply, S}.
%% ----------------------------------------------------------
%% # handle_info/2
%% ----------------------------------------------------------
-handle_info({'DOWN', MRef, process, Pid, _}, S) ->
- ets:delete_object(?TABLE, ?MONITOR(Pid, MRef)),
- ets:match_delete(?TABLE, ?MAPPING('_', Pid)),
- {noreply, S};
+handle_info({'DOWN', _MRef, process, Pid, _}, S) ->
+ {noreply, down(Pid, S)};
-handle_info(Info, S) ->
- ?UNEXPECTED([Info]),
+handle_info(_Info, S) ->
{noreply, S}.
%% ----------------------------------------------------------
@@ -278,71 +320,166 @@ code_change(_OldVsn, State, _Extra) ->
%% ===========================================================================
-monitor(true, Pid) ->
- ets:insert(?TABLE, ?MONITOR(Pid, erlang:monitor(process, Pid)));
-monitor(false, _) ->
- ok.
+%% insert/2
+
+insert(false, Rec) ->
+ Spec = [{'$1', [{'==', '$1', {const, Rec}}], ['$_']}],
+ X = '$end_of_table' /= ets:select(?TABLE, Spec, 1), %% entry exists?
+ X orelse ets:insert(?TABLE, Rec),
+ {true, not X};
-%% Do we need a monitor for the specified Pid?
-no_monitor(Pid) ->
- [] == ets:match_object(?TABLE, ?MONITOR(Pid, '_')).
+insert(true, Rec) ->
+ B = ets:insert_new(?TABLE, Rec), %% entry inserted?
+ {B, B}.
-%% insert_new/2
+%% add/3
-insert_new(?TABLE, {Key, _} = T) ->
- flush(ets:lookup(?TABLE, Key)),
- ets:insert_new(?TABLE, T).
+%% Only add a single monitor for any given process, since there's no
+%% use to more.
+add(true, {_Key, Pid} = Rec, S) ->
+ NS = add_monitor(Pid, S),
+ {Recvs, RD} = add(Rec, NS),
+ {Recvs, S#state{receivers = RD}};
+
+add(false = No, _, S) ->
+ {No, S}.
+
+%% add/2
+
+%% Notify processes whose patterns match the inserted key.
+add({_Key, Pid} = Rec, #state{receivers = RD}) ->
+ dict:fold(fun(Pt, Ps, A) ->
+ add(lists:member(Rec, match(Pt, Pid)), Pt, Ps, Rec, A)
+ end,
+ {sets:new(), RD},
+ RD).
+
+%% add/5
+
+add(true, Pat, Recvs, {_,_} = Rec, {Set, Dict}) ->
+ {lists:foldl(fun sets:add_element/2, Set, Recvs),
+ remove(fun erlang:is_list/1, Pat, Recvs, Dict)};
+
+add(false, _, _, _, Acc) ->
+ Acc.
+
+%% add_monitor/2
+
+add_monitor(Pid, #state{monitors = MS} = S) ->
+ add_monitor(sets:is_element(Pid, MS), Pid, S).
+
+%% add_monitor/3
+
+add_monitor(false, Pid, #state{monitors = MS} = S) ->
+ monitor(process, Pid),
+ S#state{monitors = sets:add_element(Pid, MS)};
+
+add_monitor(true, _, S) ->
+ S.
+
+%% delete/2
+
+delete(Recs, #state{receivers = RD}) ->
+ lists:foldl(fun(R,S) -> delete(R, RD, S) end, sets:new(), Recs).
+
+%% delete/3
+
+delete({_Key, Pid} = Rec, RD, Set) ->
+ dict:fold(fun(Pt, Ps, S) ->
+ delete(lists:member(Rec, match(Pt, Pid)), Rec, Ps, S)
+ end,
+ Set,
+ RD).
+
+%% delete/4
+
+%% Entry matches a pattern ...
+delete(true, Rec, Recvs, Set) ->
+ lists:foldl(fun(R,S) -> sets:add_element({R, Rec}, S) end,
+ Set,
+ Recvs);
+
+%% ... or not.
+delete(false, _, _, Set) ->
+ Set.
+
+%% notify/2
+
+notify(false = No, _) ->
+ No;
+
+notify(Recvs, remove = Op) ->
+ sets:fold(fun({P,R}, N) -> send(P, R, Op), N+1 end, 0, Recvs);
+
+notify(Recvs, {_,_} = Rec) ->
+ sets:fold(fun(P,N) -> send(P, Rec, add), N+1 end, 0, Recvs).
+
+%% send/3
+
+%% No processes waiting on remove, by construction: they've either
+%% received notification at add or aren't waiting.
+send([Pid | T], Rec, Op) ->
+ Pid ! {T, Op, Rec};
+
+send({_,_} = From, Rec, add) ->
+ gen_server:reply(From, [Rec]).
+
+%% down/2
+
+down(Pid, #state{monitors = MS} = S) ->
+ NS = flush(Pid, S),
+ Recvs = delete(match('_', Pid), NS),
+ ets:match_delete(?TABLE, {'_', Pid}),
+ notify(Recvs, remove),
+ NS#state{monitors = sets:del_element(Pid, MS)}.
+
+%% flush/3
%% Remove any processes that are dead but for which we may not have
-%% received 'DOWN' yet. This is to ensure that add_new can be used
-%% to register a unique name each time a process restarts.
-flush(List) ->
- lists:foreach(fun({_,P} = T) ->
- del(erlang:is_process_alive(P), T)
- end,
- List).
-
-del(Alive, T) ->
- Alive orelse ets:delete_object(?TABLE, T).
-
-%% repl/3
-
-repl([?MAPPING(_, Pid) = M], Key, Pid) ->
- ets:delete_object(?TABLE, M),
- true = ets:insert(?TABLE, ?MAPPING(Key, Pid));
-repl([], _, _) ->
- false.
-
-%% pending/1
-
-pending(true, #state{q = [_|_] = Q} = S) ->
- S#state{q = q(lists:reverse(Q), [])}; %% retain reply order
-pending(_, S) ->
+%% received 'DOWN' yet, to ensure that add_new can be used to register
+%% a unique name each time a registering process restarts.
+flush(true, {Key, Pid}, S) ->
+ Spec = [{{'$1', '$2'},
+ [{'andalso', {'==', '$1', {const, Key}},
+ {'/=', '$2', Pid}}],
+ ['$2']}],
+ lists:foldl(fun down/2, S, [P || P <- ets:select(?TABLE, Spec),
+ not is_process_alive(P)]);
+
+flush(false, _, S) ->
S.
-q([], Q) ->
- Q;
-q([{From, Pat} = T | Rest], Q) ->
- case find(Pat) of
- {ok, L} ->
- gen_server:reply(From, L),
- q(Rest, Q);
- false ->
- q(Rest, [T|Q])
- end.
-
-%% find/1
-
-find(Pat) ->
- try match(Pat) of
- [] ->
- false;
- L ->
- {ok, L}
- catch
- _:_ ->
- {ok, []}
- end.
+%% flush/2
+
+%% Process has died and should no longer receive messages/replies.
+flush(Pid, #state{receivers = RD} = S)
+ when is_pid(Pid) ->
+ S#state{receivers = dict:fold(fun(Pt,Ps,D) -> flush(Pid, Pt, Ps, D) end,
+ RD,
+ RD)}.
+
+%% flush/4
+
+flush(Pid, Pat, Recvs, Dict) ->
+ remove(fun(T) -> Pid /= head(T) end, Pat, Recvs, Dict).
+
+%% head/1
+
+head([P|_]) ->
+ P;
+
+head({P,_}) ->
+ P.
+
+%% remove/4
+
+remove(Pred, Key, Values, Dict) ->
+ case lists:filter(Pred, Values) of
+ [] ->
+ dict:erase(Key, Dict);
+ Rest ->
+ dict:store(Key, Rest, Dict)
+ end.
%% call/1
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 87ef2e522d..e4f77e3a24 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
-export([subscribe/1,
unsubscribe/1,
services/0,
+ peer_info/1,
info/2]).
%% towards diameter_config
@@ -128,14 +129,14 @@
%% Record representing an RFC 3539 watchdog process implemented by
%% diameter_watchdog.
-record(watchdog,
- {pid :: match(pid()),
+ {pid :: match(pid()) | undefined,
type :: match(connect | accept),
ref :: match(reference()), %% key into diameter_config
options :: match([diameter:transport_opt()]),%% from start_transport
state = ?WD_INITIAL :: match(wd_state()),
started = diameter_lib:now(),%% at process start
peer = false :: match(boolean() | pid())}).
- %% true at accepted, pid() at okay/reopen
+ %% true at accepted/remove, pid() at okay/reopen
%% Record representing a Peer State Machine processes implemented by
%% diameter_peer_fsm.
@@ -218,6 +219,29 @@ lookup_state(SvcName) ->
end.
%% ---------------------------------------------------------------------------
+%% # peer_info/2
+%% ---------------------------------------------------------------------------
+
+%% An extended version of info_peer/1 for peer_info/1.
+peer_info(Pid) ->
+ try
+ {_, PD} = process_info(Pid, dictionary),
+ {_, T} = lists:keyfind({diameter_peer_fsm, start}, 1, PD),
+ {TPid, {{Type, Ref}, TMod, Cfg}} = T,
+ {_, TD} = process_info(TPid, dictionary),
+ {_, Data} = lists:keyfind({TMod, info}, 1, TD),
+ [{ref, Ref},
+ {type, Type},
+ {owner, TPid},
+ {module, TMod},
+ {config, Cfg}
+ | try TMod:info(Data) catch _:_ -> [] end]
+ catch
+ error:_ ->
+ []
+ end.
+
+%% ---------------------------------------------------------------------------
%% # subscribe/1
%% # unsubscribe/1
%% ---------------------------------------------------------------------------
@@ -226,7 +250,7 @@ subscribe(SvcName) ->
diameter_reg:add({?MODULE, subscriber, SvcName}).
unsubscribe(SvcName) ->
- diameter_reg:del({?MODULE, subscriber, SvcName}).
+ diameter_reg:remove({?MODULE, subscriber, SvcName}).
subscriptions(Pat) ->
pmap(diameter_reg:match({?MODULE, subscriber, Pat})).
@@ -652,25 +676,34 @@ mod_state(Alias, ModS) ->
%% remove_transport
shutdown(Refs, #state{watchdogT = WatchdogT})
when is_list(Refs) ->
- ets:foldl(fun(P,ok) -> st(P, Refs), ok end, ok, WatchdogT);
+ ets:insert(WatchdogT, ets:foldl(fun(R,A) -> st(R, Refs, A) end,
+ [],
+ WatchdogT));
%% application/service shutdown
shutdown(Reason, #state{watchdogT = WatchdogT})
when Reason == application;
Reason == service ->
- diameter_lib:wait(ets:foldl(fun(P,A) -> st(P, Reason, A) end,
+ diameter_lib:wait(ets:foldl(fun(P,A) -> ss(P, Reason, A) end,
[],
WatchdogT)).
-%% st/2
+%% st/3
-st(#watchdog{ref = Ref, pid = Pid}, Refs) ->
- lists:member(Ref, Refs)
- andalso (Pid ! {shutdown, self(), transport}). %% 'DOWN' cleans up
+%% Mark replacement as started so that a subsequent accept doesn't
+%% result in a new process that isn't terminated.
+st(#watchdog{ref = Ref, pid = Pid, peer = P} = Rec, Refs, Acc) ->
+ case lists:member(Ref, Refs) of
+ true ->
+ Pid ! {shutdown, self(), transport}, %% 'DOWN' cleans up
+ [Rec#watchdog{peer = true} || P == false] ++ Acc;
+ false ->
+ Acc
+ end.
-%% st/3
+%% ss/3
-st(#watchdog{pid = Pid}, Reason, Acc) ->
+ss(#watchdog{pid = Pid}, Reason, Acc) ->
MRef = monitor(process, Pid),
Pid ! {shutdown, self(), Reason},
[MRef | Acc].
@@ -950,11 +983,22 @@ ms(_, Svc) ->
%% ---------------------------------------------------------------------------
accepted(Pid, _TPid, #state{watchdogT = WatchdogT} = S) ->
- #watchdog{ref = Ref, type = accept = T, peer = false, options = Opts}
+ #watchdog{type = accept = T, peer = P}
= Wd
= fetch(WatchdogT, Pid),
- ets:insert(WatchdogT, Wd#watchdog{peer = true}),%% mark replacement started
- start(Ref, T, Opts, S). %% start new watchdog
+ if not P ->
+ #watchdog{ref = Ref, options = Opts} = Wd,
+ %% Mark replacement started, and start new watchdog.
+ ets:insert(WatchdogT, Wd#watchdog{peer = true}),
+ start(Ref, T, Opts, S);
+ P ->
+ %% Transport removal in progress: true has been set in
+ %% shutdown/2, and the transport will die as a
+ %% consequence.
+ ok
+ end.
+
+%% fetch/2
fetch(Tid, Key) ->
[T] = ets:lookup(Tid, Key),
@@ -1293,8 +1337,7 @@ start_tc(Tc, T, _) ->
tc_timeout({Ref, _Type, _Opts} = T, #state{service_name = SvcName} = S) ->
tc(diameter_config:have_transport(SvcName, Ref), T, S).
-tc(true, {Ref, Type, Opts}, #state{service_name = SvcName}
- = S) ->
+tc(true, {Ref, Type, Opts}, #state{service_name = SvcName} = S) ->
send_event(SvcName, {reconnect, Ref, Opts}),
start(Ref, Type, Opts, S);
tc(false = No, _, _) -> %% removed
@@ -1815,13 +1858,6 @@ eq(Any, Id, PeerId) ->
%% OctetString() can be specified as an iolist() so test for string
%% rather then term equality.
-%% transports/1
-
-transports(#state{watchdogT = WatchdogT}) ->
- ets:select(WatchdogT, [{#watchdog{peer = '$1', _ = '_'},
- [{'is_pid', '$1'}],
- ['$1']}]).
-
%% ---------------------------------------------------------------------------
%% # service_info/2
%% ---------------------------------------------------------------------------
@@ -1844,7 +1880,6 @@ transports(#state{watchdogT = WatchdogT}) ->
-define(ALL_INFO, [capabilities,
applications,
transport,
- pending,
options]).
%% The rest.
@@ -1938,7 +1973,6 @@ complete_info(Item, #state{service = Svc} = S) ->
applications -> info_apps(S);
transport -> info_transport(S);
options -> info_options(S);
- pending -> info_pending(S);
keys -> ?ALL_INFO ++ ?CAP_INFO ++ ?OTHER_INFO;
all -> service_info(?ALL_INFO, S);
statistics -> info_stats(S);
@@ -2146,13 +2180,6 @@ info_apps(#state{service = #diameter_service{applications = Apps}}) ->
mk_app(#diameter_app{} = A) ->
lists:zip(record_info(fields, diameter_app), tl(tuple_to_list(A))).
-%% info_pending/1
-%%
-%% One entry for each outgoing request whose answer is outstanding.
-
-info_pending(#state{} = S) ->
- diameter_traffic:pending(transports(S)).
-
%% info_info/1
%%
%% Extract process_info from connections info.
diff --git a/lib/diameter/src/base/diameter_session.erl b/lib/diameter/src/base/diameter_session.erl
index 4cd76ed1f1..d854bc36a5 100644
--- a/lib/diameter/src/base/diameter_session.erl
+++ b/lib/diameter/src/base/diameter_session.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -158,10 +158,9 @@ session_id(Host) ->
%% ---------------------------------------------------------------------------
init() ->
- {Now, Seed} = diameter_lib:seed(),
- random:seed(Seed),
+ Now = diameter_lib:timestamp(),
Time = time32(Now),
- Seq = (?INT32 band (Time bsl 20)) bor (random:uniform(1 bsl 20) - 1),
+ Seq = (?INT32 band (Time bsl 20)) bor (rand:uniform(1 bsl 20) - 1),
ets:insert(diameter_sequence, [{origin_state_id, Time},
{session_base, Time bsl 32},
{sequence, Seq}]),
diff --git a/lib/diameter/src/base/diameter_sup.erl b/lib/diameter/src/base/diameter_sup.erl
index e89ede9843..01c51f0856 100644
--- a/lib/diameter/src/base/diameter_sup.erl
+++ b/lib/diameter/src/base/diameter_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
-export([init/1]).
-define(CHILDREN, [diameter_misc_sup,
+ diameter_config_sup,
diameter_watchdog_sup,
diameter_peer_fsm_sup,
diameter_transport_sup,
@@ -41,7 +42,7 @@
-define(TABLES, [{diameter_sequence, [set]},
{diameter_service, [set, {keypos, 3}]},
- {diameter_request, [bag]},
+ {diameter_request, [set]},
{diameter_config, [bag, {keypos, 2}]}]).
%% start_link/0
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index c169d3fc2c..bc1ccf4feb 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
-export([send_request/4]).
%% towards diameter_watchdog
--export([receive_message/4]).
+-export([receive_message/6]).
%% towards diameter_peer_fsm and diameter_watchdog
-export([incr/4,
@@ -40,11 +40,11 @@
%% towards diameter_service
-export([make_recvdata/1,
peer_up/1,
- peer_down/1,
- pending/1]).
+ peer_down/1]).
-%% towards ?MODULE
--export([send/1]). %% send from remote node
+%% internal
+-export([send/1, %% send from remote node
+ init/1]). %% monitor process start
-include_lib("diameter/include/diameter.hrl").
-include("diameter_internal.hrl").
@@ -57,14 +57,12 @@
-define(DEFAULT_TIMEOUT, 5000). %% for outgoing requests
-define(DEFAULT_SPAWN_OPTS, []).
-%% Table containing outgoing requests for which a reply has yet to be
-%% received.
+%% Table containing outgoing entries that live and die with
+%% peer_up/down. The name is historic, since the table used to contain
+%% information about outgoing requests for which an answer has yet to
+%% be received.
-define(REQUEST_TABLE, diameter_request).
-%% Workaround for dialyzer's lack of understanding of match specs.
--type match(T)
- :: T | '_' | '$1' | '$2' | '$3' | '$4'.
-
%% Record diameter:call/4 options are parsed into.
-record(options,
{filter = none :: diameter:peer_filter(),
@@ -72,7 +70,7 @@
timeout = ?DEFAULT_TIMEOUT :: 0..16#FFFFFFFF,
detach = false :: boolean()}).
-%% Term passed back to receive_message/4 with every incoming message.
+%% Term passed back to receive_message/6 with every incoming message.
-record(recvdata,
{peerT :: ets:tid(),
service_name :: diameter:service_name(),
@@ -87,12 +85,12 @@
%% Record stored in diameter_request for each outgoing request.
-record(request,
- {ref :: match(reference()), %% used to receive answer
- caller :: match(pid()), %% calling process
- handler :: match(pid()), %% request process
- transport :: match(pid()), %% peer process
- caps :: match(#diameter_caps{}), %% of connection
- packet :: match(#diameter_packet{})}). %% of request
+ {ref :: reference(), %% used to receive answer
+ caller :: pid() | undefined, %% calling process
+ handler :: pid(), %% request process
+ transport :: pid() | undefined, %% peer process
+ caps :: #diameter_caps{} | undefined, %% of connection
+ packet :: #diameter_packet{} | undefined}). %% of request
%% ---------------------------------------------------------------------------
%% # make_recvdata/1
@@ -113,18 +111,27 @@ make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) ->
%% peer_up/1
%% ---------------------------------------------------------------------------
-%% Insert an element that is used to detect whether or not there has
-%% been a failover when inserting an outgoing request.
+%% Start a process that dies with peer_down/1, on which request
+%% processes can monitor. There is no other process that dies with
+%% peer_down since failover doesn't imply the loss of transport in the
+%% case of a watchdog transition into state SUSPECT.
peer_up(TPid) ->
- ets:insert(?REQUEST_TABLE, {TPid}).
+ proc_lib:start(?MODULE, init, [TPid]).
+
+init(TPid) ->
+ ets:insert(?REQUEST_TABLE, {TPid, self()}),
+ proc_lib:init_ack(self()),
+ proc_lib:hibernate(erlang, exit, [{shutdown, TPid}]).
%% ---------------------------------------------------------------------------
%% peer_down/1
%% ---------------------------------------------------------------------------
peer_down(TPid) ->
+ [{_, Pid}] = ets:lookup(?REQUEST_TABLE, TPid),
ets:delete(?REQUEST_TABLE, TPid),
- failover(TPid).
+ Pid ! ok, %% make it die
+ Pid.
%% ---------------------------------------------------------------------------
%% incr/4
@@ -199,56 +206,42 @@ incr_rc(Dir, Pkt, TPid, Dict0) ->
incr_rc(Dir, Pkt, TPid, {Dict0, Dict0, Dict0}).
%% ---------------------------------------------------------------------------
-%% pending/1
+%% # receive_message/6
+%%
+%% Handle an incoming Diameter message.
%% ---------------------------------------------------------------------------
-pending(TPids) ->
- MatchSpec = [{{'$1',
- #request{caller = '$2',
- handler = '$3',
- transport = '$4',
- _ = '_'},
- '_'},
- [?ORCOND([{'==', T, '$4'} || T <- TPids])],
- [{{'$1', [{{caller, '$2'}},
- {{handler, '$3'}},
- {{transport, '$4'}}]}}]}],
+%% Handle an incoming Diameter message in the watchdog process.
- try
- ets:select(?REQUEST_TABLE, MatchSpec)
- catch
- error: badarg -> [] %% service has gone down
- end.
+receive_message(TPid, Route, Pkt, false, Dict0, RecvData) ->
+ incoming(TPid, Route, Pkt, Dict0, RecvData);
-%% ---------------------------------------------------------------------------
-%% # receive_message/4
-%%
-%% Handle an incoming Diameter message.
-%% ---------------------------------------------------------------------------
+receive_message(TPid, Route, Pkt, NPid, Dict0, RecvData) ->
+ NPid ! {diameter, incoming(TPid, Route, Pkt, Dict0, RecvData)}.
-%% Handle an incoming Diameter message in the watchdog process. This
-%% used to come through the service process but this avoids that
-%% becoming a bottleneck.
+%% incoming/4
-receive_message(TPid, Pkt, Dict0, RecvData)
+incoming(TPid, Route, Pkt, Dict0, RecvData)
when is_pid(TPid) ->
#diameter_packet{header = #diameter_header{is_request = R}} = Pkt,
- recv(R,
- (not R) andalso lookup_request(Pkt, TPid),
- TPid,
- Pkt,
- Dict0,
- RecvData).
+ recv(R, Route, TPid, Pkt, Dict0, RecvData).
%% recv/6
%% Incoming request ...
recv(true, false, TPid, Pkt, Dict0, T) ->
- spawn_request(TPid, Pkt, Dict0, T);
+ try
+ {request, spawn_request(TPid, Pkt, Dict0, T)}
+ catch
+ error: system_limit = E -> %% discard
+ ?LOG(error, E),
+ discard
+ end;
%% ... answer to known request ...
-recv(false, #request{ref = Ref, handler = Pid} = Req, _, Pkt, Dict0, _) ->
- Pid ! {answer, Ref, Req, Dict0, Pkt};
+recv(false, {Pid, Ref, TPid}, _, Pkt, Dict0, _) ->
+ Pid ! {answer, Ref, TPid, Dict0, Pkt},
+ {answer, Pid};
%% Note that failover could have happened prior to this message being
%% received and triggering failback. That is, both a failover message
@@ -263,7 +256,7 @@ recv(false, #request{ref = Ref, handler = Pid} = Req, _, Pkt, Dict0, _) ->
recv(false, false, TPid, Pkt, _, _) ->
?LOG(discarded, Pkt#diameter_packet.header),
incr(TPid, {{unknown, 0}, recv, discarded}),
- ok.
+ discard.
%% spawn_request/4
@@ -273,12 +266,7 @@ spawn_request(TPid, Pkt, Dict0, RecvData) ->
spawn_request(TPid, Pkt, Dict0, ?DEFAULT_SPAWN_OPTS, RecvData).
spawn_request(TPid, Pkt, Dict0, Opts, RecvData) ->
- try
- spawn_opt(fun() -> recv_request(TPid, Pkt, Dict0, RecvData) end, Opts)
- catch
- error: system_limit = E -> %% discard
- ?LOG(error, E)
- end.
+ spawn_opt(fun() -> recv_request(TPid, Pkt, Dict0, RecvData) end, Opts).
%% ---------------------------------------------------------------------------
%% recv_request/4
@@ -901,7 +889,7 @@ failed(Rec, FailedAvp, Dict) ->
{'Failed-AVP', [FailedAvp]}
catch
error: _ ->
- Avps = Dict:'get-'('AVP', Rec),
+ Avps = Dict:'#get-'('AVP', Rec),
A = #diameter_avp{name = 'Failed-AVP',
value = FailedAvp},
{'AVP', [A|Avps]}
@@ -1442,7 +1430,7 @@ make_request_packet(#diameter_packet{header = Hdr} = Pkt,
make_request_packet(Msg, Pkt) ->
Pkt#diameter_packet{msg = Msg}.
-%% make_retransmit_packet/2
+%% make_retransmit_packet/1
make_retransmit_packet(#diameter_packet{msg = [#diameter_header{} = Hdr
| Avps]}
@@ -1485,32 +1473,39 @@ send_R(Pkt0,
packet = Pkt0},
incr(send, Pkt, TPid, AppDict),
- TRef = send_request(TPid, Pkt, Req, SvcName, Timeout),
+ {TRef, MRef} = zend_requezt(TPid, Pkt, Req, SvcName, Timeout),
Pid ! Ref, %% tell caller a send has been attempted
handle_answer(SvcName,
App,
- recv_A(Timeout, SvcName, App, Opts, {TRef, Req})).
+ recv_A(Timeout, SvcName, App, Opts, {TRef, MRef, Req})).
%% recv_A/5
-recv_A(Timeout, SvcName, App, Opts, {TRef, #request{ref = Ref} = Req}) ->
+recv_A(Timeout, SvcName, App, Opts, {TRef, MRef, #request{ref = Ref} = Req}) ->
%% Matching on TRef below ensures we ignore messages that pertain
%% to a previous transport prior to failover. The answer message
- %% includes the #request{} since it's not necessarily Req; that
- %% is, from the last peer to which we've transmitted.
+ %% includes the pid of the transport on which it was received,
+ %% which may not be the last peer to which we've transmitted.
receive
- {answer = A, Ref, Rq, Dict0, Pkt} -> %% Answer from peer
- {A, Rq, Dict0, Pkt};
+ {answer = A, Ref, TPid, Dict0, Pkt} -> %% Answer from peer
+ {A, #request{} = erase(TPid), Dict0, Pkt};
{timeout = Reason, TRef, _} -> %% No timely reply
{error, Req, Reason};
- {failover, TRef} -> %% Service says peer has gone down
- retransmit(pick_peer(SvcName, App, Req, Opts),
- Req,
- Opts,
- SvcName,
- Timeout)
+ {'DOWN', MRef, process, _, _} when false /= MRef -> %% local peer_down
+ failover(SvcName, App, Req, Opts, Timeout);
+ {failover, TRef} -> %% local or remote peer_down
+ failover(SvcName, App, Req, Opts, Timeout)
end.
+%% failover/5
+
+failover(SvcName, App, Req, Opts, Timeout) ->
+ retransmit(pick_peer(SvcName, App, Req, Opts),
+ Req,
+ Opts,
+ SvcName,
+ Timeout).
+
%% handle_answer/3
handle_answer(SvcName, App, {error, Req, Reason}) ->
@@ -1687,47 +1682,63 @@ encode(DictT, TPid, #diameter_packet{bin = undefined} = Pkt) ->
encode(_, _, #diameter_packet{} = Pkt) ->
Pkt.
+%% zend_requezt/5
+%%
+%% Strip potentially large record fields that aren't used by the
+%% processes the records can be send to, possibly on a remote node.
+
+zend_requezt(TPid, Pkt, Req, SvcName, Timeout) ->
+ put(TPid, Req),
+ send_request(TPid, z(Pkt), Req, SvcName, Timeout).
+
%% send_request/5
send_request(TPid, #diameter_packet{bin = Bin} = Pkt, Req, _SvcName, Timeout)
when node() == node(TPid) ->
Seqs = diameter_codec:sequence_numbers(Bin),
TRef = erlang:start_timer(Timeout, self(), TPid),
- Entry = {Seqs, Req, TRef},
-
- %% Ensure that request table is cleaned even if we receive an exit
- %% signal. An alternative would be to simply trap exits, but
- %% callbacks are applied in this process, and these could possibly
- %% be expecting the prevailing behaviour.
- Self = self(),
- spawn(fun() -> diameter_lib:wait([Self]), erase_request(Entry) end),
-
- store_request(Entry, TPid),
- send(TPid, Pkt),
- TRef;
+ send(TPid, Pkt, _Route = {self(), Req#request.ref, Seqs}),
+ {TRef, _MRef = peer_monitor(TPid, TRef)};
%% Send using a remote transport: spawn a process on the remote node
%% to relay the answer.
send_request(TPid, #diameter_packet{} = Pkt, Req, SvcName, Timeout) ->
TRef = erlang:start_timer(Timeout, self(), TPid),
- T = {TPid, Pkt, Req, SvcName, Timeout, TRef},
+ T = {TPid, Pkt, z(Req), SvcName, Timeout, TRef},
spawn(node(TPid), ?MODULE, send, [T]),
- TRef.
+ {TRef, false}.
+
+%% z/1
+%%
+%% Avoid sending potentially large terms unnecessarily. The records
+%% themselves are retained since they're sent between nodes in send/1
+%% and changing what's sent causes upgrade issues.
+
+z(#request{ref = Ref, handler = Pid}) ->
+ #request{ref = Ref,
+ handler = Pid};
+
+z(#diameter_packet{header = H, bin = Bin, transport_data = T}) ->
+ #diameter_packet{header = H,
+ bin = Bin,
+ transport_data = T}.
%% send/1
send({TPid, Pkt, #request{handler = Pid} = Req0, SvcName, Timeout, TRef}) ->
Req = Req0#request{handler = self()},
- recv(TPid, Pid, TRef, send_request(TPid, Pkt, Req, SvcName, Timeout)).
+ recv(TPid, Pid, TRef, zend_requezt(TPid, Pkt, Req, SvcName, Timeout)).
%% recv/4
%%
%% Relay an answer from a remote node.
-recv(TPid, Pid, TRef, LocalTRef) ->
+recv(TPid, Pid, TRef, {LocalTRef, MRef}) ->
receive
{answer, _, _, _, _} = A ->
Pid ! A;
+ {'DOWN', MRef, process, _, _} ->
+ Pid ! {failover, TRef};
{failover = T, LocalTRef} ->
Pid ! {T, TRef};
T ->
@@ -1736,14 +1747,13 @@ recv(TPid, Pid, TRef, LocalTRef) ->
%% send/2
-send(Pid, Pkt) -> %% Strip potentially large message terms.
- #diameter_packet{header = H,
- bin = Bin,
- transport_data = T}
- = Pkt,
- Pid ! {send, #diameter_packet{header = H,
- bin = Bin,
- transport_data = T}}.
+send(Pid, Pkt) ->
+ Pid ! {send, Pkt}.
+
+%% send/3
+
+send(Pid, Pkt, Route) ->
+ Pid ! {send, Pkt, Route}.
%% retransmit/4
@@ -1753,8 +1763,8 @@ retransmit({TPid, Caps, App}
= Req,
SvcName,
Timeout) ->
- have_request(Pkt0, TPid) %% Don't failover to a peer we've
- andalso ?THROW(timeout), %% already sent to.
+ undefined == get(TPid) %% Don't failover to a peer we've
+ orelse ?THROW(timeout), %% already sent to.
Pkt = make_retransmit_packet(Pkt0),
@@ -1764,6 +1774,8 @@ retransmit({TPid, Caps, App}
SvcName,
Timeout,
[]).
+%% When sending a binary, it's up to prepare_retransmit to modify it
+%% accordingly.
retransmit({send, Msg},
Transport,
@@ -1805,77 +1817,20 @@ resend_request(Pkt0,
?LOG(retransmission, Pkt#diameter_packet.header),
incr(TPid, {msg_id(Pkt, AppDict), send, retransmission}),
- TRef = send_request(TPid, Pkt, Req, SvcName, Tmo),
- {TRef, Req}.
+ {TRef, MRef} = zend_requezt(TPid, Pkt, Req, SvcName, Tmo),
+ {TRef, MRef, Req}.
-%% store_request/2
+%% peer_monitor/2
-store_request(T, TPid) ->
- ets:insert(?REQUEST_TABLE, T),
- ets:member(?REQUEST_TABLE, TPid)
- orelse begin
- {_Seqs, _Req, TRef} = T,
- self() ! {failover, TRef} %% failover/1 may have missed
- end.
-
-%% lookup_request/2
-%%
-%% Note the match on both the key and transport pid. The latter is
-%% necessary since the same Hop-by-Hop and End-to-End identifiers are
-%% reused in the case of retransmission.
-
-lookup_request(Msg, TPid) ->
- Seqs = diameter_codec:sequence_numbers(Msg),
- Spec = [{{Seqs, #request{transport = TPid, _ = '_'}, '_'},
- [],
- ['$_']}],
- case ets:select(?REQUEST_TABLE, Spec) of
- [{_, Req, _}] ->
- Req;
- [] ->
+peer_monitor(TPid, TRef) ->
+ case ets:lookup(?REQUEST_TABLE, TPid) of %% at peer_up/1
+ [{_, MPid}] ->
+ monitor(process, MPid);
+ [] -> %% transport has gone down
+ self() ! {failover, TRef},
false
end.
-%% erase_request/1
-
-erase_request(T) ->
- ets:delete_object(?REQUEST_TABLE, T).
-
-%% match_requests/1
-
-match_requests(TPid) ->
- Pat = {'_', #request{transport = TPid, _ = '_'}, '_'},
- ets:select(?REQUEST_TABLE, [{Pat, [], ['$_']}]).
-
-%% have_request/2
-
-have_request(Pkt, TPid) ->
- Seqs = diameter_codec:sequence_numbers(Pkt),
- Pat = {Seqs, #request{transport = TPid, _ = '_'}, '_'},
- '$end_of_table' /= ets:select(?REQUEST_TABLE, [{Pat, [], ['$_']}], 1).
-
-%% ---------------------------------------------------------------------------
-%% # failover/1-2
-%% ---------------------------------------------------------------------------
-
-failover(TPid)
- when is_pid(TPid) ->
- lists:foreach(fun failover/1, match_requests(TPid));
-%% Note that a request process can store its request after failover
-%% notifications are sent here: store_request/2 sends the notification
-%% in that case.
-
-%% Failover as a consequence of peer_down/1: inform the
-%% request process.
-failover({_, Req, TRef}) ->
- #request{handler = Pid,
- packet = #diameter_packet{msg = M}}
- = Req,
- M /= undefined andalso (Pid ! {failover, TRef}).
-%% Failover is not performed when msg = binary() since sending
-%% pre-encoded binaries is only partially supported. (Mostly for
-%% test.)
-
%% get_destination/2
get_destination(Dict, Msg) ->
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index ea8b2fdb0e..f28b8f2910 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -125,8 +125,6 @@ i({Ack, T, Pid, {RecvData,
= Svc}}) ->
monitor(process, Pid),
wait(Ack, Pid),
- {_, Seed} = diameter_lib:seed(),
- random:seed(Seed),
putr(restart, {T, Opts, Svc, SvcOpts}), %% save seeing it in trace
putr(dwr, dwr(Caps)), %%
{_,_} = Mask = proplists:get_value(sequence, SvcOpts),
@@ -285,7 +283,7 @@ event(Msg,
?LOG(transition, {From, To}).
data(Msg, TPid, reopen, okay) ->
- {recv, TPid, 'DWA', _Pkt} = Msg, %% assert
+ {recv, TPid, false, 'DWA', _Pkt, _NPid} = Msg, %% assert
{TPid, T} = eraser(open),
[T];
@@ -449,8 +447,16 @@ transition({'DOWN', _, process, TPid, _Reason} = D,
end;
%% Incoming message.
-transition({recv, TPid, Name, Pkt}, #watchdog{transport = TPid} = S) ->
- recv(Name, Pkt, S);
+transition({recv, TPid, Route, Name, Pkt, NPid},
+ #watchdog{transport = TPid}
+ = S) ->
+ try
+ incoming(Name, Pkt, NPid, S)
+ catch
+ #watchdog{dictionary = Dict0, receive_data = T} = NS ->
+ diameter_traffic:receive_message(TPid, Route, Pkt, NPid, Dict0, T),
+ NS
+ end;
%% Current watchdog has timed out.
transition({timeout, TRef, tw}, #watchdog{tref = TRef} = S) ->
@@ -559,7 +565,7 @@ tw(TwInit, Ms) ->
tw(T)
when is_integer(T), T >= 6000 ->
- T - 2000 + (random:uniform(4001) - 1); %% RFC3539 jitter of +/- 2 sec.
+ T - 2000 + (rand:uniform(4001) - 1); %% RFC3539 jitter of +/- 2 sec.
tw({M,F,A}) ->
apply(M,F,A).
@@ -578,22 +584,34 @@ send_watchdog(#watchdog{pending = false,
%% Don't count encode errors since we don't expect any on DWR/DWA.
+%% incoming/4
+
+incoming(Name, Pkt, false, S) ->
+ recv(Name, Pkt, S);
+
+incoming(Name, Pkt, NPid, S) ->
+ try
+ recv(Name, Pkt, S)
+ after
+ NPid ! {diameter, discard}
+ end.
+
%% recv/3
recv(Name, Pkt, S) ->
- try rcv(Name, S) of
+ try rcv(Name, Pkt, rcv(Name, S)) of
#watchdog{} = NS ->
- rcv(Name, Pkt, S),
- NS
+ throw(NS)
catch
- {?MODULE, throwaway, #watchdog{} = NS} ->
+ #watchdog{} = NS -> %% throwaway
NS
end.
%% rcv/3
rcv('DWR', Pkt, #watchdog{transport = TPid,
- dictionary = Dict0}) ->
+ dictionary = Dict0}
+ = S) ->
?LOG(recv, 'DWR'),
DPkt = diameter_codec:decode(Dict0, Pkt),
diameter_traffic:incr(recv, DPkt, TPid, Dict0),
@@ -610,32 +628,30 @@ rcv('DWR', Pkt, #watchdog{transport = TPid,
send(TPid, {send, #diameter_packet{header = H,
transport_data = T,
bin = Bin}}),
- ?LOG(send, 'DWA');
+ ?LOG(send, 'DWA'),
+ throw(S);
rcv('DWA', Pkt, #watchdog{transport = TPid,
- dictionary = Dict0}) ->
+ dictionary = Dict0}
+ = S) ->
?LOG(recv, 'DWA'),
diameter_traffic:incr(recv, Pkt, TPid, Dict0),
diameter_traffic:incr_rc(recv,
diameter_codec:decode(Dict0, Pkt),
TPid,
- Dict0);
+ Dict0),
+ throw(S);
-rcv(N, _, _)
+rcv(N, _, S)
when N == 'CER';
N == 'CEA';
N == 'DPR' ->
- false;
+ throw(S);
%% DPR can be sent explicitly with diameter:call/4. Only the
%% corresponding DPAs arrive here.
-rcv(_, Pkt, #watchdog{transport = TPid,
- dictionary = Dict0,
- receive_data = T}) ->
- diameter_traffic:receive_message(TPid, Pkt, Dict0, T).
-
-throwaway(S) ->
- throw({?MODULE, throwaway, S}).
+rcv(_, _, S)->
+ S.
%% rcv/2
%%
@@ -652,20 +668,20 @@ throwaway(S) ->
%% INITIAL Receive non-DWA Throwaway() INITIAL
rcv('DWA', #watchdog{status = initial} = S) ->
- throwaway(S#watchdog{pending = false});
+ throw(S#watchdog{pending = false});
rcv(_, #watchdog{status = initial} = S) ->
- throwaway(S);
+ throw(S);
%% DOWN Receive DWA Pending = FALSE
%% Throwaway() DOWN
%% DOWN Receive non-DWA Throwaway() DOWN
rcv('DWA', #watchdog{status = down} = S) ->
- throwaway(S#watchdog{pending = false});
+ throw(S#watchdog{pending = false});
rcv(_, #watchdog{status = down} = S) ->
- throwaway(S);
+ throw(S);
%% OKAY Receive DWA Pending = FALSE
%% SetWatchdog() OKAY
@@ -721,7 +737,7 @@ rcv('DWR', #watchdog{status = reopen} = S) ->
S; %% ensure DWA: the RFC isn't explicit about answering
rcv(_, #watchdog{status = reopen} = S) ->
- throwaway(S).
+ throw(S).
%% timeout/1
%%
diff --git a/lib/diameter/src/base/diameter_watchdog_sup.erl b/lib/diameter/src/base/diameter_watchdog_sup.erl
index 5d24e12f19..7b6669f381 100644
--- a/lib/diameter/src/base/diameter_watchdog_sup.erl
+++ b/lib/diameter/src/base/diameter_watchdog_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl
index cdaa9aa7f9..864d5f0691 100644
--- a/lib/diameter/src/compiler/diameter_codegen.erl
+++ b/lib/diameter/src/compiler/diameter_codegen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -790,20 +790,7 @@ header() ->
("%% -------------------------------------------------------------------\n"
"%% This is a generated file.\n"
"%% -------------------------------------------------------------------\n"
- "\n"
- "%%\n"
- "%% Copyright (c) Ericsson AB. All rights reserved.\n"
- "%%\n"
- "%% The information in this document is the property of Ericsson.\n"
- "%%\n"
- "%% Except as specifically authorized in writing by Ericsson, the\n"
- "%% receiver of this document shall keep the information contained\n"
- "%% herein confidential and shall protect the same in whole or in\n"
- "%% part from disclosure and dissemination to third parties.\n"
- "%%\n"
- "%% Disclosure and disseminations to the receivers employees shall\n"
- "%% only be made on a strict need to know basis.\n"
- "%%\n\n").
+ "\n").
hrl_header(Name) ->
header() ++ "-hrl_name('" ++ ?S(Name) ++ ".hrl').\n".
diff --git a/lib/diameter/src/compiler/diameter_dict_parser.yrl b/lib/diameter/src/compiler/diameter_dict_parser.yrl
index ef8d58d63b..4d2eb09831 100644
--- a/lib/diameter/src/compiler/diameter_dict_parser.yrl
+++ b/lib/diameter/src/compiler/diameter_dict_parser.yrl
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/compiler/diameter_dict_scanner.erl b/lib/diameter/src/compiler/diameter_dict_scanner.erl
index aeedc89d83..a9abb67640 100644
--- a/lib/diameter/src/compiler/diameter_dict_scanner.erl
+++ b/lib/diameter/src/compiler/diameter_dict_scanner.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl
index 9525393128..f9f2b02e94 100644
--- a/lib/diameter/src/compiler/diameter_dict_util.erl
+++ b/lib/diameter/src/compiler/diameter_dict_util.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/compiler/diameter_exprecs.erl b/lib/diameter/src/compiler/diameter_exprecs.erl
index 2a04917995..9a0cb6baf2 100644
--- a/lib/diameter/src/compiler/diameter_exprecs.erl
+++ b/lib/diameter/src/compiler/diameter_exprecs.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/compiler/diameter_make.erl b/lib/diameter/src/compiler/diameter_make.erl
index d9709029ae..eae40dbafd 100644
--- a/lib/diameter/src/compiler/diameter_make.erl
+++ b/lib/diameter/src/compiler/diameter_make.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/compiler/diameter_vsn.hrl b/lib/diameter/src/compiler/diameter_vsn.hrl
index 2efac98bff..5201e6b84d 100644
--- a/lib/diameter/src/compiler/diameter_vsn.hrl
+++ b/lib/diameter/src/compiler/diameter_vsn.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/depend.sed b/lib/diameter/src/depend.sed
index 5adf7f05d5..e385d7db4a 100644
--- a/lib/diameter/src/depend.sed
+++ b/lib/diameter/src/depend.sed
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
+# Copyright Ericsson AB 2010-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/diameter.app.src b/lib/diameter/src/diameter.app.src
index 49bfd803e7..d380ebbd92 100644
--- a/lib/diameter/src/diameter.app.src
+++ b/lib/diameter/src/diameter.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index a3e21db012..eb5a5a44f3 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -47,10 +47,11 @@
{"1.9.2.2", [{restart_application, diameter}]}, %% 17.5.6.7
{"1.9.2.3", [{restart_application, diameter}]}, %% 17.5.6.8
{"1.10", [{restart_application, diameter}]}, %% 18.0
- {"1.11", [{load_module, diameter_traffic}, %% 18.1
- {update, diameter_service, {advanced, []}}]},
- {"1.11.1", [{load_module, diameter_traffic}, %% 18.2
- {update, diameter_service, {advanced, []}}]}
+ {"1.11", [{restart_application, diameter}]}, %% 18.1
+ {"1.11.1", [{restart_application, diameter}]}, %% 18.2
+ {"1.11.2", [{restart_application, diameter}]}, %% 18.3
+ {"1.12", [{restart_application, diameter}]}, %% 19.0
+ {"1.12.1", [{restart_application, diameter}]} %% 19.1
],
[
{"0.9", [{restart_application, diameter}]},
@@ -80,6 +81,9 @@
{"1.9.2.3", [{restart_application, diameter}]},
{"1.10", [{restart_application, diameter}]},
{"1.11", [{restart_application, diameter}]},
- {"1.11.1", [{restart_application, diameter}]}
+ {"1.11.1", [{restart_application, diameter}]},
+ {"1.11.2", [{restart_application, diameter}]},
+ {"1.12", [{restart_application, diameter}]},
+ {"1.12.1", [{restart_application, diameter}]}
]
}.
diff --git a/lib/diameter/src/dict/acct_rfc6733.dia b/lib/diameter/src/dict/acct_rfc6733.dia
index 4eb326ce88..7690958932 100644
--- a/lib/diameter/src/dict/acct_rfc6733.dia
+++ b/lib/diameter/src/dict/acct_rfc6733.dia
@@ -1,7 +1,7 @@
;;
;; %CopyrightBegin%
;;
-;; Copyright Ericsson AB 2013. All Rights Reserved.
+;; Copyright Ericsson AB 2013-2016. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/dict/base_accounting.dia b/lib/diameter/src/dict/base_accounting.dia
index 839add8764..b719bcd128 100644
--- a/lib/diameter/src/dict/base_accounting.dia
+++ b/lib/diameter/src/dict/base_accounting.dia
@@ -1,7 +1,7 @@
;;
;; %CopyrightBegin%
;;
-;; Copyright Ericsson AB 2010-2011. All Rights Reserved.
+;; Copyright Ericsson AB 2010-2016. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/dict/base_rfc3588.dia b/lib/diameter/src/dict/base_rfc3588.dia
index f7316208a6..ef4c8388c3 100644
--- a/lib/diameter/src/dict/base_rfc3588.dia
+++ b/lib/diameter/src/dict/base_rfc3588.dia
@@ -1,7 +1,7 @@
;;
;; %CopyrightBegin%
;;
-;; Copyright Ericsson AB 2010-2013. All Rights Reserved.
+;; Copyright Ericsson AB 2010-2016. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/dict/base_rfc6733.dia b/lib/diameter/src/dict/base_rfc6733.dia
index d92760711c..a120b4eece 100644
--- a/lib/diameter/src/dict/base_rfc6733.dia
+++ b/lib/diameter/src/dict/base_rfc6733.dia
@@ -1,7 +1,7 @@
;;
;; %CopyrightBegin%
;;
-;; Copyright Ericsson AB 2013. All Rights Reserved.
+;; Copyright Ericsson AB 2013-2016. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/dict/capup_rfc6737.dia b/lib/diameter/src/dict/capup_rfc6737.dia
index 396c7de9ac..a473cc9d22 100644
--- a/lib/diameter/src/dict/capup_rfc6737.dia
+++ b/lib/diameter/src/dict/capup_rfc6737.dia
@@ -1,7 +1,7 @@
;;
;; %CopyrightBegin%
;;
-;; Copyright Ericsson AB 2013. All Rights Reserved.
+;; Copyright Ericsson AB 2013-2016. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/dict/relay.dia b/lib/diameter/src/dict/relay.dia
index 23772fedf8..c7320cc737 100644
--- a/lib/diameter/src/dict/relay.dia
+++ b/lib/diameter/src/dict/relay.dia
@@ -1,7 +1,7 @@
;;
;; %CopyrightBegin%
;;
-;; Copyright Ericsson AB 2010-2011. All Rights Reserved.
+;; Copyright Ericsson AB 2010-2016. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/info/diameter_dbg.erl b/lib/diameter/src/info/diameter_dbg.erl
index 4f5c91d24f..e1d2086871 100644
--- a/lib/diameter/src/info/diameter_dbg.erl
+++ b/lib/diameter/src/info/diameter_dbg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk
index 3b223ea391..4e4ce60ddf 100644
--- a/lib/diameter/src/modules.mk
+++ b/lib/diameter/src/modules.mk
@@ -1,7 +1,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2015. All Rights Reserved.
+# Copyright Ericsson AB 2010-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ RT_MODULES = \
base/diameter_callback \
base/diameter_capx \
base/diameter_config \
+ base/diameter_config_sup \
base/diameter_codec \
base/diameter_dict \
base/diameter_lib \
diff --git a/lib/diameter/src/transport/diameter_etcp.erl b/lib/diameter/src/transport/diameter_etcp.erl
index 9db198ff86..10a014754f 100644
--- a/lib/diameter/src/transport/diameter_etcp.erl
+++ b/lib/diameter/src/transport/diameter_etcp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/transport/diameter_etcp_sup.erl b/lib/diameter/src/transport/diameter_etcp_sup.erl
index 48794d4fe1..b230397b59 100644
--- a/lib/diameter/src/transport/diameter_etcp_sup.erl
+++ b/lib/diameter/src/transport/diameter_etcp_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl
index 678dc9b5d6..f48e4347ee 100644
--- a/lib/diameter/src/transport/diameter_sctp.erl
+++ b/lib/diameter/src/transport/diameter_sctp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -61,10 +61,6 @@
%% Remote addresses to accept connections from.
-define(DEFAULT_ACCEPT, []). %% any
-%% How long a listener with no associations lives before offing
-%% itself.
--define(LISTENER_TIMEOUT, 30000).
-
%% How long to wait for a transport process to attach after
%% association establishment.
-define(ACCEPT_TIMEOUT, 5000).
@@ -84,25 +80,26 @@
%% Accepting/connecting transport process state.
-record(transport,
- {parent :: pid(),
+ {parent :: pid() | undefined,
mode :: {accept, pid()}
| accept
| {connect, {[inet:ip_address()], uint(), list()}}
%% {RAs, RP, Errors}
| connect,
- socket :: gen_sctp:sctp_socket(),
+ socket :: gen_sctp:sctp_socket() | undefined,
assoc_id :: gen_sctp:assoc_id(), %% association identifier
- peer :: {[inet:ip_address()], uint()}, %% {RAs, RP}
- streams :: {uint(), uint()}, %% {InStream, OutStream} counts
+ peer :: {[inet:ip_address()], uint()} %% {RAs, RP}
+ | undefined,
+ streams :: {uint(), uint()} %% {InStream, OutStream} counts
+ | undefined,
os = 0 :: uint()}). %% next output stream
%% Listener process state.
-record(listener,
{ref :: reference(),
socket :: gen_sctp:sctp_socket(),
- count = 0 :: uint(), %% attached transport processes
+ service = false :: false | pid(), %% service process
pending = {0, queue:new()},
- tref :: reference(),
accept :: [match()]}).
%% Field pending implements two queues: the first of transport-to-be
%% processes to which an association has been assigned but for which
@@ -132,11 +129,14 @@
-> {ok, pid(), [inet:ip_address()]}
when Ref :: diameter:transport_ref().
-start(T, #diameter_service{capabilities = Caps}, Opts)
+start(T, Svc, Opts)
when is_list(Opts) ->
+ #diameter_service{capabilities = Caps,
+ pid = SPid}
+ = Svc,
diameter_sctp_sup:start(), %% start supervisors on demand
Addrs = Caps#diameter_caps.host_ip_address,
- s(T, Addrs, lists:map(fun ip/1, Opts)).
+ s(T, Addrs, SPid, lists:map(fun ip/1, Opts)).
ip({ifaddr, A}) ->
{ip, A};
@@ -147,18 +147,22 @@ ip(T) ->
%% when there is not yet an association to assign it, or at comm_up on
%% a new association in which case the call retrieves a transport from
%% the pending queue.
-s({accept, Ref} = A, Addrs, Opts) ->
- {LPid, LAs} = listener(Ref, {Opts, Addrs}),
- try gen_server:call(LPid, {A, self()}, infinity) of
- {ok, TPid} -> {ok, TPid, LAs}
+s({accept, Ref} = A, Addrs, SPid, Opts) ->
+ {ok, LPid, LAs} = listener(Ref, {Opts, Addrs}),
+ try gen_server:call(LPid, {A, self(), SPid}, infinity) of
+ {ok, TPid} ->
+ {ok, TPid, LAs};
+ No ->
+ {error, No}
catch
- exit: Reason -> {error, Reason}
+ exit: Reason ->
+ {error, Reason}
end;
%% This implementation is due to there being no accept call in
%% gen_sctp in order to be able to accept a new association only
%% *after* an accepting transport has been spawned.
-s({connect = C, Ref}, Addrs, Opts) ->
+s({connect = C, Ref}, Addrs, _SPid, Opts) ->
diameter_sctp_sup:start_child({C, self(), Opts, Addrs, Ref}).
%% start_link/1
@@ -214,14 +218,15 @@ init(T) ->
%% A process owning a listening socket.
i({listen, Ref, {Opts, Addrs}}) ->
+ [_] = diameter_config:subscribe(Ref, transport), %% assert existence
{[Matches], Rest} = proplists:split(Opts, [accept]),
{LAs, Sock} = AS = open(Addrs, Rest, ?DEFAULT_PORT),
ok = gen_sctp:listen(Sock, true),
true = diameter_reg:add_new({?MODULE, listener, {Ref, AS}}),
proc_lib:init_ack({ok, self(), LAs}),
- start_timer(#listener{ref = Ref,
- socket = Sock,
- accept = [[M] || {accept, M} <- Matches]});
+ #listener{ref = Ref,
+ socket = Sock,
+ accept = [[M] || {accept, M} <- Matches]};
%% A connecting transport.
i({connect, Pid, Opts, Addrs, Ref}) ->
@@ -283,24 +288,23 @@ i({K, Ref}, #transport{mode = {accept, _}} = S) ->
%% Accepting processes can be started concurrently: ensure only one
%% listener is started.
-listener(LRef, T) ->
- diameter_sync:call({?MODULE, listener, LRef},
- {?MODULE, listener, [{LRef, T}]},
+listener(Ref, T) ->
+ diameter_sync:call({?MODULE, listener, Ref},
+ {?MODULE, listener, [{Ref, T}]},
infinity,
infinity).
-listener({LRef, T}) ->
- l(diameter_reg:match({?MODULE, listener, {LRef, '_'}}), LRef, T).
+listener({Ref, T}) ->
+ l(diameter_reg:match({?MODULE, listener, {Ref, '_'}}), Ref, T).
%% Existing listening process ...
l([{{?MODULE, listener, {_, AS}}, LPid}], _, _) ->
- {LAs, _Sock} = AS,
- {LPid, LAs};
+ {LAs, _Sock} = AS,
+ {ok, LPid, LAs};
%% ... or not.
-l([], LRef, T) ->
- {ok, LPid, LAs} = diameter_sctp_sup:start_child({listen, LRef, T}),
- {LPid, LAs}.
+l([], Ref, T) ->
+ diameter_sctp_sup:start_child({listen, Ref, T}).
%% open/3
@@ -366,11 +370,17 @@ type(T) ->
%% # handle_call/3
%% ---------------------------------------------------------------------------
-handle_call({{accept, Ref}, Pid}, _, #listener{ref = Ref,
- count = K}
- = S) ->
+handle_call({{accept, Ref}, Pid}, _, #listener{ref = Ref} = S) ->
{TPid, NewS} = accept(Ref, Pid, S),
- {reply, {ok, TPid}, NewS#listener{count = K+1}};
+ {reply, {ok, TPid}, NewS};
+
+handle_call({{accept, _} = T, Pid, SPid}, From, #listener{service = P} = S) ->
+ handle_call({T, Pid}, From, if not is_pid(P), is_pid(SPid) ->
+ monitor(process, SPid),
+ S#listener{service = SPid};
+ true ->
+ S
+ end);
handle_call(_, _, State) ->
{reply, nok, State}.
@@ -429,13 +439,6 @@ putr(Key, Val) ->
getr(Key) ->
get({?MODULE, Key}).
-%% start_timer/1
-
-start_timer(#listener{count = 0} = S) ->
- S#listener{tref = erlang:start_timer(?LISTENER_TIMEOUT, self(), close)};
-start_timer(S) ->
- S.
-
%% l/2
%%
%% Transition listener state.
@@ -450,35 +453,37 @@ l({sctp, Sock, _RA, _RP, Data} = T, #listener{socket = Sock,
setopts(Sock),
NewS;
+%% Service process has died.
+l({'DOWN', _, process, Pid, _} = T, #listener{service = Pid,
+ socket = Sock}) ->
+ gen_sctp:close(Sock),
+ x(T);
+
+%% Accepting process has died.
l({'DOWN', _MRef, process, TPid, _}, #listener{pending = {_,Q}} = S) ->
down(queue:member(TPid, Q), TPid, S);
-%% Timeout after the last accepting process has died.
-l({timeout, TRef, close = T}, #listener{tref = TRef,
- count = 0}) ->
- x(T);
-l({timeout, _, close}, #listener{} = S) ->
- S.
+%% Transport has been removed.
+l({transport, remove, _} = T, #listener{socket = Sock}) ->
+ gen_sctp:close(Sock),
+ x(T).
%% down/3
%%
%% Accepting transport has died.
%% One that's waiting for transport start in the pending queue ...
-down(true, TPid, #listener{pending = {N,Q},
- count = K}
- = S) ->
+down(true, TPid, #listener{pending = {N,Q}} = S) ->
NQ = queue:filter(fun(P) -> P /= TPid end, Q),
if N < 0 -> %% awaiting an association ...
- start_timer(S#listener{count = K-1,
- pending = {N+1, NQ}});
+ S#listener{pending = {N+1, NQ}};
true -> %% ... or one has been assigned
S#listener{pending = {N-1, NQ}}
end;
%% ... or one that's already attached.
-down(false, _TPid, #listener{count = K} = S) ->
- start_timer(S#listener{count = K-1}).
+down(false, _TPid, S) ->
+ S.
%% t/2
%%
diff --git a/lib/diameter/src/transport/diameter_sctp_sup.erl b/lib/diameter/src/transport/diameter_sctp_sup.erl
index 48df975ae9..36050aaf28 100644
--- a/lib/diameter/src/transport/diameter_sctp_sup.erl
+++ b/lib/diameter/src/transport/diameter_sctp_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl
index 005b2442c0..44abc5c3b4 100644
--- a/lib/diameter/src/transport/diameter_tcp.erl
+++ b/lib/diameter/src/transport/diameter_tcp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
%%
-module(diameter_tcp).
+-dialyzer({no_fail_call, throttle/2}).
-behaviour(gen_server).
@@ -56,7 +57,6 @@
-define(ERROR(T), erlang:error({T, ?MODULE, ?LINE})).
-define(DEFAULT_PORT, 3868). %% RFC 3588, ch 2.1
--define(LISTENER_TIMEOUT, 30000).
-define(DEFAULT_FRAGMENT_TIMEOUT, 1000).
-define(IS_UINT32(N), (is_integer(N) andalso 0 =< N andalso 0 == N bsr 32)).
@@ -71,9 +71,8 @@
%% a process owning the listening port.
%% Listener process state.
--record(listener, {socket :: inet:socket(),
- count = 1 :: non_neg_integer(),
- tref :: reference()}).
+-record(listener, {socket :: inet:socket(),
+ service = false :: false | pid()}). %% service process
%% Monitor process state.
-record(monitor,
@@ -102,7 +101,8 @@
| gen_tcp:listen_option().
-type option() :: {port, non_neg_integer()}
- | {fragment_timer, 0..16#FFFFFFFF}.
+ | {fragment_timer, 0..16#FFFFFFFF}
+ | {throttle_cb, diameter:evaluable()}.
%% Accepting/connecting transport process state.
-record(transport,
@@ -110,10 +110,13 @@
parent :: pid(), %% of process that started us
module :: module(), %% gen_tcp-like module
frag = <<>> :: frag(), %% message fragment
- ssl :: boolean() | [term()], %% ssl options
+ ssl :: [term()] | boolean(), %% ssl options, ssl or not
timeout :: infinity | 0..16#FFFFFFFF, %% fragment timeout
tref = false :: false | reference(), %% fragment timer reference
- flush = false :: boolean()}). %% flush fragment at timeout?
+ flush = false :: boolean(), %% flush fragment at timeout?
+ throttle_cb :: false | diameter:evaluable(), %% ask to receive
+ throttled :: boolean() | binary()}). %% stopped receiving?
+
%% The usual transport using gen_tcp can be replaced by anything
%% sufficiently gen_tcp-like by passing a 'module' option as the first
%% (for simplicity) transport option. The transport_module diameter_etcp
@@ -132,11 +135,15 @@
| {ok, pid()}
when Ref :: diameter:transport_ref().
-start({T, Ref}, #diameter_service{capabilities = Caps}, Opts) ->
+start({T, Ref}, Svc, Opts) ->
+ #diameter_service{capabilities = Caps,
+ pid = SPid}
+ = Svc,
+
diameter_tcp_sup:start(), %% start tcp supervisors on demand
{Mod, Rest} = split(Opts),
Addrs = Caps#diameter_caps.host_ip_address,
- Arg = {T, Ref, Mod, self(), Rest, Addrs},
+ Arg = {T, Ref, Mod, self(), Rest, Addrs, SPid},
diameter_tcp_sup:start_child(Arg).
split([{module, M} | Opts]) ->
@@ -190,7 +197,7 @@ init(T) ->
%% i/1
%% A transport process.
-i({T, Ref, Mod, Pid, Opts, Addrs})
+i({T, Ref, Mod, Pid, Opts, Addrs, SPid})
when T == accept;
T == connect ->
monitor(process, Pid),
@@ -198,25 +205,35 @@ i({T, Ref, Mod, Pid, Opts, Addrs})
%% that does nothing but kill us with the parent until call
%% returns.
{ok, MPid} = diameter_tcp_sup:start_child(#monitor{parent = Pid}),
- {SslOpts, Rest0} = ssl(Opts),
- {OwnOpts, Rest} = own(Rest0),
+ {[SO|TO], Rest} = proplists:split(Opts, [ssl_options,
+ fragment_timer,
+ throttle_cb]),
+ SslOpts = ssl_opts(SO),
+ OwnOpts = lists:append(TO),
Tmo = proplists:get_value(fragment_timer,
OwnOpts,
?DEFAULT_FRAGMENT_TIMEOUT),
?IS_TIMEOUT(Tmo) orelse ?ERROR({fragment_timer, Tmo}),
- Sock = init(T, Ref, Mod, Pid, SslOpts, Rest, Addrs),
+ Throttle = proplists:get_value(throttle_cb, OwnOpts, false),
+ Sock = init(T, Ref, Mod, Pid, SslOpts, Rest, Addrs, SPid),
MPid ! {stop, self()}, %% tell the monitor to die
M = if SslOpts -> ssl; true -> Mod end,
- setopts(M, Sock),
putr(?REF_KEY, Ref),
- #transport{parent = Pid,
- module = M,
- socket = Sock,
- ssl = SslOpts,
- timeout = Tmo};
+ throttle(#transport{parent = Pid,
+ module = M,
+ socket = Sock,
+ ssl = SslOpts,
+ timeout = Tmo,
+ throttle_cb = Throttle,
+ throttled = false /= Throttle});
%% Put the reference in the process dictionary since we now use it
%% advertise the ssl socket after TLS upgrade.
+i({T, _Ref, _Mod, _Pid, _Opts, _Addrs} = Arg) %% from old code
+ when T == accept;
+ T == connect ->
+ i(erlang:append_element(Arg, _SPid = false));
+
%% A monitor process to kill the transport if the parent dies.
i(#monitor{parent = Pid, transport = TPid} = S) ->
proc_lib:init_ack({ok, self()}),
@@ -229,16 +246,19 @@ i(#monitor{parent = Pid, transport = TPid} = S) ->
%% death. However, a link can be unlinked and this is exactly what
%% gen_tcp seems to so. Links should be left to supervisors.
-i({listen, LRef, APid, {Mod, Opts, Addrs}}) ->
+i({listen = L, Ref, _APid, T}) -> %% from old code
+ i({L, Ref, T});
+
+i({listen, Ref, {Mod, Opts, Addrs}}) ->
+ [_] = diameter_config:subscribe(Ref, transport), %% assert existence
{[LA, LP], Rest} = proplists:split(Opts, [ip, port]),
LAddrOpt = get_addr(LA, Addrs),
LPort = get_port(LP),
{ok, LSock} = Mod:listen(LPort, gen_opts(LAddrOpt, Rest)),
LAddr = laddr(LAddrOpt, Mod, LSock),
- true = diameter_reg:add_new({?MODULE, listener, {LRef, {LAddr, LSock}}}),
+ true = diameter_reg:add_new({?MODULE, listener, {Ref, {LAddr, LSock}}}),
proc_lib:init_ack({ok, self(), {LAddr, LSock}}),
- monitor(process, APid),
- start_timer(#listener{socket = LSock}).
+ #listener{socket = LSock}.
laddr([], Mod, Sock) ->
{ok, {Addr, _Port}} = sockname(Mod, Sock),
@@ -246,14 +266,6 @@ laddr([], Mod, Sock) ->
laddr([{ip, Addr}], _, _) ->
Addr.
-own(Opts) ->
- {[Own], Rest} = proplists:split(Opts, [fragment_timer]),
- {Own, Rest}.
-
-ssl(Opts) ->
- {[SslOpts], Rest} = proplists:split(Opts, [ssl_options]),
- {ssl_opts(SslOpts), Rest}.
-
ssl_opts([]) ->
false;
ssl_opts([{ssl_options, true}]) ->
@@ -261,24 +273,25 @@ ssl_opts([{ssl_options, true}]) ->
ssl_opts([{ssl_options, Opts}])
when is_list(Opts) ->
Opts;
-ssl_opts(L) ->
- ?ERROR({ssl_options, L}).
+ssl_opts(T) ->
+ ?ERROR({ssl_options, T}).
-%% init/7
+%% init/8
%% Establish a TLS connection before capabilities exchange ...
-init(Type, Ref, Mod, Pid, true, Opts, Addrs) ->
- init(Type, Ref, ssl, Pid, [{cb_info, ?TCP_CB(Mod)} | Opts], Addrs);
+init(Type, Ref, Mod, Pid, true, Opts, Addrs, SPid) ->
+ init(Type, Ref, ssl, Pid, [{cb_info, ?TCP_CB(Mod)} | Opts], Addrs, SPid);
%% ... or not.
-init(Type, Ref, Mod, Pid, _, Opts, Addrs) ->
- init(Type, Ref, Mod, Pid, Opts, Addrs).
+init(Type, Ref, Mod, Pid, _, Opts, Addrs, SPid) ->
+ init(Type, Ref, Mod, Pid, Opts, Addrs, SPid).
-%% init/6
+%% init/7
-init(accept = T, Ref, Mod, Pid, Opts, Addrs) ->
+init(accept = T, Ref, Mod, Pid, Opts, Addrs, SPid) ->
{[Matches], Rest} = proplists:split(Opts, [accept]),
- {LAddr, LSock} = listener(Ref, {Mod, Rest, Addrs}),
+ {ok, LPid, {LAddr, LSock}} = listener(Ref, {Mod, Rest, Addrs}),
+ ok = gen_server:call(LPid, {accept, SPid}, infinity),
proc_lib:init_ack({ok, self(), [LAddr]}),
Sock = ok(accept(Mod, LSock)),
ok = accept_peer(Mod, Sock, accept(Matches)),
@@ -286,7 +299,7 @@ init(accept = T, Ref, Mod, Pid, Opts, Addrs) ->
diameter_peer:up(Pid),
Sock;
-init(connect = T, Ref, Mod, Pid, Opts, Addrs) ->
+init(connect = T, Ref, Mod, Pid, Opts, Addrs, _SPid) ->
{[LA, RA, RP], Rest} = proplists:split(Opts, [ip, raddr, rport]),
LAddrOpt = get_addr(LA, Addrs),
RAddr = get_addr(RA),
@@ -340,24 +353,26 @@ accept(Opts) ->
%% Accepting processes can be started concurrently: ensure only one
%% listener is started.
-listener(LRef, T) ->
- diameter_sync:call({?MODULE, listener, LRef},
- {?MODULE, listener, [{LRef, T, self()}]},
+listener(Ref, T) ->
+ diameter_sync:call({?MODULE, listener, Ref},
+ {?MODULE, listener, [{Ref, T, self()}]},
infinity,
infinity).
-listener({LRef, T, TPid}) ->
- l(diameter_reg:match({?MODULE, listener, {LRef, '_'}}), LRef, T, TPid).
+%% listener/1
+
+listener({Ref, T, _TPid}) ->
+ l(diameter_reg:match({?MODULE, listener, {Ref, '_'}}), Ref, T).
+
+%% l/3
%% Existing listening process ...
-l([{{?MODULE, listener, {_, AS}}, LPid}], _, _, TPid) ->
- LPid ! {accept, TPid},
- AS;
+l([{{?MODULE, listener, {_, AS}}, LPid}], _, _) ->
+ {ok, LPid, AS};
%% ... or not.
-l([], LRef, T, TPid) ->
- {ok, _, AS} = diameter_tcp_sup:start_child({listen, LRef, TPid, T}),
- AS.
+l([], Ref, T) ->
+ diameter_tcp_sup:start_child({listen, Ref, T}).
%% get_addr/1
@@ -393,7 +408,7 @@ get_port(Ps) ->
gen_opts(LAddrOpt, Opts) ->
{L,_} = proplists:split(Opts, [binary, packet, active]),
[[],[],[]] == L orelse ?ERROR({reserved_options, Opts}),
- [binary, {packet, 0}, {active, once}] ++ LAddrOpt ++ Opts.
+ [binary, {packet, 0}, {active, false}] ++ LAddrOpt ++ Opts.
%% ---------------------------------------------------------------------------
%% # ports/1
@@ -436,6 +451,14 @@ portnr(Sock) ->
%% # handle_call/3
%% ---------------------------------------------------------------------------
+handle_call({accept, SPid}, _From, #listener{service = P} = S) ->
+ {reply, ok, if not is_pid(P), is_pid(SPid) ->
+ monitor(process, SPid),
+ S#listener{service = SPid};
+ true ->
+ S
+ end};
+
handle_call(_, _, State) ->
{reply, nok, State}.
@@ -482,13 +505,6 @@ putr(Key, Val) ->
getr(Key) ->
get({?MODULE, Key}).
-%% start_timer/1
-
-start_timer(#listener{count = 0} = S) ->
- S#listener{tref = erlang:start_timer(?LISTENER_TIMEOUT, self(), close)};
-start_timer(S) ->
- S.
-
%% m/2
%%
%% Transition monitor state.
@@ -510,20 +526,19 @@ m({'DOWN', _, process, Pid, _}, #monitor{parent = Pid,
%%
%% Transition listener state.
-%% Another accept transport is attaching.
-l({accept, TPid}, #listener{count = N} = S) ->
- monitor(process, TPid),
- S#listener{count = N+1};
-
-%% Accepting process has died.
-l({'DOWN', _, process, _, _}, #listener{count = N} = S) ->
- start_timer(S#listener{count = N-1});
+%% Service process has died.
+l({'DOWN', _, process, Pid, _} = T, #listener{service = Pid,
+ socket = Sock}) ->
+ gen_tcp:close(Sock),
+ x(T);
-%% Timeout after the last accepting process has died.
-l({timeout, TRef, close = T}, #listener{tref = TRef,
- count = 0}) ->
+%% Transport has been removed.
+l({transport, remove, _} = T, #listener{socket = Sock}) ->
+ gen_tcp:close(Sock),
x(T);
-l({timeout, _, close}, #listener{} = S) ->
+
+%% Possibly death of an accepting process monitored in old code.
+l(_, S) ->
S.
%% t/2
@@ -536,53 +551,37 @@ t(T,S) ->
S;
#transport{} = NS ->
NS;
- {stop, Reason} ->
- x(Reason);
stop ->
x(T)
end.
%% transition/2
-%% Initial incoming message when we might need to upgrade to TLS:
-%% don't request another message until we know.
-
-transition({tcp, Sock, Bin}, #transport{socket = Sock,
- parent = Pid,
- frag = Head,
- module = M,
- ssl = Opts}
- = S)
- when is_list(Opts) ->
- case rcv(Head, Bin) of
- {Msg, B} when is_binary(Msg) ->
- diameter_peer:recv(Pid, Msg),
- S#transport{frag = B};
- Frag ->
- setopts(M, Sock),
- start_fragment_timer(S#transport{frag = Frag})
- end;
-
%% Incoming message.
transition({P, Sock, Bin}, #transport{socket = Sock,
- module = M,
- ssl = B}
+ ssl = B,
+ throttled = T}
= S)
- when P == tcp, not B;
- P == ssl, B ->
- setopts(M, Sock),
- start_fragment_timer(recv(Bin, S));
+ when P == ssl, true == B;
+ P == tcp ->
+ false = T, %% assert
+ recv(Bin, S);
+
+%% Make a new throttling callback after a timeout.
+transition(throttle, #transport{throttled = false}) ->
+ ok;
+transition(throttle, S) ->
+ throttle(S);
%% Capabilties exchange has decided on whether or not to run over TLS.
transition({diameter, {tls, Ref, Type, B}}, #transport{parent = Pid}
= S) ->
- #transport{socket = Sock,
- module = M}
+ true = is_boolean(B), %% assert
+ #transport{}
= NS
= tls_handshake(Type, B, S),
Pid ! {diameter, {tls, Ref}},
- setopts(M, Sock),
- start_fragment_timer(NS#transport{ssl = B});
+ throttle(NS#transport{ssl = B});
transition({C, Sock}, #transport{socket = Sock,
ssl = B})
@@ -598,14 +597,8 @@ transition({E, Sock, _Reason} = T, #transport{socket = Sock,
?ERROR({T,S});
%% Outgoing message.
-transition({diameter, {send, Bin}}, #transport{socket = Sock,
- module = M}) ->
- case send(M, Sock, Bin) of
- ok ->
- ok;
- {error, Reason} ->
- {stop, {send, Reason}}
- end;
+transition({diameter, {send, Bin}}, S) ->
+ send(Bin, S);
%% Request to close the transport connection.
transition({diameter, {close, Pid}}, #transport{parent = Pid,
@@ -672,16 +665,25 @@ tls(accept, Sock, Opts) ->
%% Reassemble fragmented messages and extract multiple message sent
%% using Nagle.
-recv(Bin, #transport{parent = Pid, frag = Head} = S) ->
+%% Receive packets until a full message is received,
+recv(Bin, #transport{frag = Head, throttled = false} = S) ->
case rcv(Head, Bin) of
- {Msg, B} when is_binary(Msg) ->
- diameter_peer:recv(Pid, Msg),
- recv(B, S#transport{frag = <<>>});
+ {Msg, B} ->
+ throttle(S#transport{frag = B, throttled = Msg});
Frag ->
- S#transport{frag = Frag,
- flush = false}
+ setopts(S),
+ start_fragment_timer(S#transport{frag = Frag,
+ flush = false})
end.
+%% recv/1
+
+recv(#transport{throttled = false} = S) ->
+ recv(<<>>, S);
+
+recv(#transport{} = S) ->
+ S.
+
%% rcv/2
%% No previous fragment.
@@ -765,8 +767,10 @@ bin(Bin)
%% since all messages with length problems are discarded this should
%% also eventually lead to watchdog failover.
-%% No fragment to flush.
-flush(#transport{frag = <<>>} = S) ->
+%% No fragment to flush or not receiving messages.
+flush(#transport{frag = Frag, throttled = B} = S)
+ when Frag == <<>>;
+ B /= false ->
S;
%% Messages have been received since last timer expiry.
@@ -807,6 +811,17 @@ accept(Mod, LSock) ->
connect(Mod, Host, Port, Opts) ->
Mod:connect(Host, Port, Opts).
+%% send/2
+
+send(Bin, #transport{socket = Sock,
+ module = M}) ->
+ case send(M, Sock, Bin) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ x({send, Reason})
+ end.
+
%% send/3
send(gen_tcp, Sock, Bin) ->
@@ -825,6 +840,11 @@ setopts(ssl, Sock, Opts) ->
setopts(M, Sock, Opts) ->
M:setopts(Sock, Opts).
+%% setopts/1
+
+setopts(#transport{socket = Sock, module = M}) ->
+ setopts(M, Sock).
+
%% setopts/2
setopts(M, Sock) ->
@@ -833,6 +853,110 @@ setopts(M, Sock) ->
X -> x({setopts, M, Sock, X}) %% possibly on peer disconnect
end.
+%% throttle/1
+
+%% Still collecting packets for a complete message: keep receiving.
+throttle(#transport{throttled = false} = S) ->
+ recv(S);
+
+%% Decide whether to receive another, or whether to accept a message
+%% that's been received.
+throttle(#transport{throttle_cb = F, throttled = T} = S) ->
+ Res = cb(F, T),
+
+ try throttle(Res, S) of
+ #transport{ssl = SB} = NS when is_boolean(SB) ->
+ throttle(defrag(NS));
+ #transport{throttled = Msg} = NS when is_binary(Msg) ->
+ %% Initial incoming message when we might need to upgrade
+ %% to TLS: wait for reception of a tls tuple.
+ defrag(NS)
+ catch
+ #transport{} = NS ->
+ recv(NS)
+ end.
+
+%% cb/2
+
+cb(false, _) ->
+ ok;
+
+cb(F, B) ->
+ diameter_lib:eval([F, true /= B andalso B]).
+
+%% throttle/2
+
+%% Callback says to receive another message.
+throttle(ok, #transport{throttled = true} = S) ->
+ throw(S#transport{throttled = false});
+
+%% Callback says to accept a received message.
+throttle(ok, #transport{parent = Pid, throttled = Msg} = S)
+ when is_binary(Msg) ->
+ diameter_peer:recv(Pid, Msg),
+ S;
+
+throttle({ok = T, F}, S) ->
+ throttle(T, S#transport{throttle_cb = F});
+
+%% Callback says to accept a received message and acknowledged the
+%% returned pid with a {request, Pid} message if a request pid is
+%% spawned, a discard message otherwise. The latter does not mean that
+%% the message was necessarily discarded: it could have been an
+%% answer.
+throttle(NPid, #transport{parent = Pid, throttled = Msg} = S)
+ when is_pid(NPid), is_binary(Msg) ->
+ diameter_peer:recv(Pid, {Msg, NPid}),
+ S;
+
+throttle({NPid, F}, #transport{throttled = Msg} = S)
+ when is_pid(NPid), is_binary(Msg) ->
+ throttle(NPid, S#transport{throttle_cb = F});
+
+%% Callback to accept a received message says to discard it.
+throttle(discard, #transport{throttled = Msg} = S)
+ when is_binary(Msg) ->
+ S;
+
+throttle({discard = T, F}, #transport{throttled = Msg} = S)
+ when is_binary(Msg) ->
+ throttle(T, S#transport{throttle_cb = F});
+
+%% Callback to accept a received message says to answer it with the
+%% supplied binary.
+throttle(Bin, #transport{throttled = Msg} = S)
+ when is_binary(Bin), is_binary(Msg) ->
+ send(Bin, S),
+ S;
+
+throttle({Bin, F}, #transport{throttled = Msg} = S)
+ when is_binary(Bin), is_binary(Msg) ->
+ throttle(Bin, S#transport{throttle_cb = F});
+
+%% Callback says to ask again in the specified number of milliseconds.
+throttle({timeout, Tmo}, S) ->
+ erlang:send_after(Tmo, self(), throttle),
+ throw(S);
+
+throttle({timeout = T, Tmo, F}, S) ->
+ throttle({T, Tmo}, S#transport{throttle_cb = F});
+
+throttle(T, #transport{throttle_cb = F}) ->
+ ?ERROR({invalid_return, T, F}).
+
+%% defrag/1
+%%
+%% Try to extract another message from packets already read before
+%% another throttling callback.
+
+defrag(#transport{frag = Head} = S) ->
+ case rcv(Head, <<>>) of
+ {Msg, B} ->
+ S#transport{throttled = Msg, frag = B};
+ _ ->
+ S#transport{throttled = true}
+ end.
+
%% portnr/2
portnr(gen_tcp, Sock) ->
diff --git a/lib/diameter/src/transport/diameter_tcp_sup.erl b/lib/diameter/src/transport/diameter_tcp_sup.erl
index a7bdb49968..6cf758748a 100644
--- a/lib/diameter/src/transport/diameter_tcp_sup.erl
+++ b/lib/diameter/src/transport/diameter_tcp_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/diameter/src/transport/diameter_transport.erl b/lib/diameter/src/transport/diameter_transport.erl
index 5a7c59b4dc..3ca4d97f50 100644
--- a/lib/diameter/src/transport/diameter_transport.erl
+++ b/lib/diameter/src/transport/diameter_transport.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.