aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diameter/src')
-rw-r--r--lib/diameter/src/Makefile2
-rw-r--r--lib/diameter/src/base/diameter_lib.erl24
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl86
-rw-r--r--lib/diameter/src/transport/diameter_sctp.erl76
-rw-r--r--lib/diameter/src/transport/diameter_tcp.erl104
5 files changed, 167 insertions, 125 deletions
diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile
index 7f61620fc1..6bf748a727 100644
--- a/lib/diameter/src/Makefile
+++ b/lib/diameter/src/Makefile
@@ -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/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index b835e87967..3928769b5e 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -299,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_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index f22ca737a9..d93a3e71e3 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -123,8 +123,16 @@ peer_up(TPid) ->
%% ---------------------------------------------------------------------------
peer_down(TPid) ->
- ets:delete(?REQUEST_TABLE, TPid),
- failover(TPid).
+ ets:delete_object(?REQUEST_TABLE, {TPid}),
+ lists:foreach(fun failover/1, ets:lookup(?REQUEST_TABLE, TPid)).
+%% Note that a request process can store its request after failover
+%% notifications are sent here: insert_request/2 sends the notification
+%% in that case.
+
+%% failover/1
+
+failover({_TPid, {Pid, TRef}}) ->
+ Pid ! {failover, TRef}.
%% ---------------------------------------------------------------------------
%% incr/4
@@ -1452,7 +1460,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]}
@@ -1703,16 +1711,13 @@ 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},
+ Entry = {Seqs, #request{handler = Pid} = 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),
+ %% Ensure that request table is cleaned even if the process is
+ %% killed.
+ spawn(fun() -> diameter_lib:wait([Pid]), delete_request(Entry) end),
- store_request(Entry, TPid),
+ insert_request(Entry),
send(TPid, Pkt),
TRef;
@@ -1774,6 +1779,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,
@@ -1818,15 +1825,21 @@ resend_request(Pkt0,
TRef = send_request(TPid, Pkt, Req, SvcName, Tmo),
{TRef, Req}.
-%% store_request/2
+%% insert_request/1
-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.
+insert_request({_Seqs, #request{transport = TPid}, TRef} = T) ->
+ ets:insert(?REQUEST_TABLE, [T, {TPid, {self(), TRef}}]),
+ is_peer_up(TPid)
+ orelse (self() ! {failover, TRef}). %% failover/1 may have missed
+
+%% is_peer_up/1
+%%
+%% Is the entry written by peer_up/1 and deleted by peer_down/1 still
+%% in the request table?
+
+is_peer_up(TPid) ->
+ Spec = [{{TPid}, [], ['$_']}],
+ '$end_of_table' /= ets:select(?REQUEST_TABLE, Spec, 1).
%% lookup_request/2
%%
@@ -1846,16 +1859,11 @@ lookup_request(Msg, TPid) ->
false
end.
-%% erase_request/1
-
-erase_request(T) ->
- ets:delete_object(?REQUEST_TABLE, T).
-
-%% match_requests/1
+%% delete_request/1
-match_requests(TPid) ->
- Pat = {'_', #request{transport = TPid, _ = '_'}, '_'},
- ets:select(?REQUEST_TABLE, [{Pat, [], ['$_']}]).
+delete_request({_Seqs, #request{handler = Pid, transport = TPid}, TRef} = T) ->
+ Spec = [{R, [], [true]} || R <- [T, {TPid, {Pid, TRef}}]],
+ ets:select_delete(?REQUEST_TABLE, Spec).
%% have_request/2
@@ -1864,28 +1872,6 @@ have_request(Pkt, TPid) ->
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/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl
index 4a005b853d..f48e4347ee 100644
--- a/lib/diameter/src/transport/diameter_sctp.erl
+++ b/lib/diameter/src/transport/diameter_sctp.erl
@@ -98,7 +98,7 @@
-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()},
accept :: [match()]}).
%% Field pending implements two queues: the first of transport-to-be
@@ -129,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};
@@ -144,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
@@ -281,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
@@ -364,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}.
@@ -441,6 +453,13 @@ 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);
@@ -454,20 +473,17 @@ l({transport, remove, _} = T, #listener{socket = Sock}) ->
%% 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 ...
- 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) ->
- S#listener{count = K-1}.
+down(false, _TPid, S) ->
+ S.
%% t/2
%%
diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl
index 546c2cfa5e..44abc5c3b4 100644
--- a/lib/diameter/src/transport/diameter_tcp.erl
+++ b/lib/diameter/src/transport/diameter_tcp.erl
@@ -71,11 +71,8 @@
%% a process owning the listening port.
%% Listener process state.
--record(listener, {socket :: inet:socket(),
- count = 1 :: non_neg_integer()}). %% accepting processes
-%% The count of accepting processes was previously used to terminate
-%% the listening process, but diameter_reg:subscribe/2 is now used for
-%% this. Leave the the count for trace purposes.
+-record(listener, {socket :: inet:socket(),
+ service = false :: false | pid()}). %% service process
%% Monitor process state.
-record(monitor,
@@ -138,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]) ->
@@ -196,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),
@@ -214,7 +215,7 @@ i({T, Ref, Mod, Pid, Opts, Addrs})
?DEFAULT_FRAGMENT_TIMEOUT),
?IS_TIMEOUT(Tmo) orelse ?ERROR({fragment_timer, Tmo}),
Throttle = proplists:get_value(throttle_cb, OwnOpts, false),
- Sock = init(T, Ref, Mod, Pid, SslOpts, Rest, Addrs),
+ 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,
putr(?REF_KEY, Ref),
@@ -228,6 +229,11 @@ i({T, Ref, Mod, Pid, Opts, Addrs})
%% 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()}),
@@ -240,16 +246,18 @@ 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}}) ->
- [_] = diameter_config:subscribe(LRef, transport), %% assert existence
+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),
#listener{socket = LSock}.
laddr([], Mod, Sock) ->
@@ -268,21 +276,22 @@ ssl_opts([{ssl_options, Opts}])
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)),
@@ -290,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),
@@ -344,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
@@ -440,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}.
@@ -507,19 +526,20 @@ m({'DOWN', _, process, Pid, _}, #monitor{parent = Pid,
%%
%% Transition listener state.
-%% An accepting 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) ->
- 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);
%% Transport has been removed.
l({transport, remove, _} = T, #listener{socket = Sock}) ->
gen_tcp:close(Sock),
- x(T).
+ x(T);
+
+%% Possibly death of an accepting process monitored in old code.
+l(_, S) ->
+ S.
%% t/2
%%