%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2010-2015. 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% %% %% %% Some gen_sctp-specific tests demonstrating problems that were %% encountered during diameter development but have nothing %% specifically to do with diameter. At least one of them can cause %% diameter_transport_SUITE testcases to fail. %% -module(diameter_gen_sctp_SUITE). -export([suite/0, all/0, init_per_suite/1, end_per_suite/1]). %% testcases -export([send_not_from_controlling_process/1, send_from_multiple_clients/1, receive_what_was_sent/1]). -include_lib("kernel/include/inet_sctp.hrl"). %% Message from gen_sctp are of this form. -define(SCTP(Sock, Data), {sctp, Sock, _, _, Data}). %% Open sockets on the loopback address. -define(ADDR, {127,0,0,1}). %% Snooze, nap, siesta. -define(SLEEP(T), receive after T -> ok end). %% An indescribably long number of milliseconds after which everthing %% that should have happened has. -define(FOREVER, 2000). %% The first byte in each message we send as a simple guard against %% not receiving what was sent. -define(MAGIC, 42). %% =========================================================================== suite() -> [{timetrap, {minutes, 2}}]. all() -> [send_not_from_controlling_process, send_from_multiple_clients, receive_what_was_sent]. init_per_suite(Config) -> case gen_sctp:open() of {ok, Sock} -> gen_sctp:close(Sock), Config; {error, E} when E == eprotonosupport; E == esocktnosupport -> {skip, no_sctp} end. end_per_suite(_Config) -> ok. %% =========================================================================== %% send_not_from_controlling_process/1 %% %% This testcase failing shows gen_sctp:send/4 hanging when called %% outside the controlling process of the socket in question. send_not_from_controlling_process(_) -> Pids = send_not_from_controlling_process(), ?SLEEP(?FOREVER), try [] = [{P,I} || P <- Pids, I <- [process_info(P)], I /= undefined] after lists:foreach(fun(P) -> exit(P, kill) end, Pids) end. %% send_not_from_controlling_process/0 %% %% Returns the pids of three spawned processes: a listening process, a %% connecting process and a sending process. %% %% The expected behaviour is that all three processes exit: %% %% - The listening process exits upon receiving an SCTP message %% sent by the sending process. %% - The connecting process exits upon listening process exit. %% - The sending process exits upon gen_sctp:send/4 return. %% %% The observed behaviour is that all three processes remain alive %% indefinitely: %% %% - The listening process never receives the SCTP message sent %% by the sending process. %% - The connecting process has an inet_reply message in its mailbox %% as a consequence of the call to gen_sctp:send/4 call from the %% sending process. %% - The call to gen_sctp:send/4 in the sending process doesn't return, %% hanging in prim_inet:getopts/2. send_not_from_controlling_process() -> FPid = self(), {L, MRef} = spawn_monitor(fun() -> listen(FPid) end),%% listening process receive {?MODULE, C, S} -> erlang:demonitor(MRef, [flush]), [L,C,S]; {'DOWN', MRef, process, _, _} = T -> error(T) end. %% listen/1 listen(FPid) -> {ok, Sock} = open(), ok = gen_sctp:listen(Sock, true), {ok, PortNr} = inet:port(Sock), LPid = self(), spawn(fun() -> connect1(PortNr, FPid, LPid) end), %% connecting process Id = assoc(Sock), ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], _Bin}) = recv(). %% Waits with this as current_function. %% recv/0 recv() -> receive T -> T end. %% connect1/3 connect1(PortNr, FPid, LPid) -> {ok, Sock} = open(), ok = gen_sctp:connect_init(Sock, ?ADDR, PortNr, []), Id = assoc(Sock), FPid ! {?MODULE, self(), spawn(fun() -> send(Sock, Id) end)}, %% sending process MRef = erlang:monitor(process, LPid), down(MRef). %% Waits with this as current_function. %% down/1 down(MRef) -> receive {'DOWN', MRef, process, _, Reason} -> Reason end. %% send/2 send(Sock, Id) -> ok = gen_sctp:send(Sock, Id, 0, <<0:32>>). %% =========================================================================== %% send_from_multiple_clients/0 %% %% Demonstrates sluggish delivery of messages. send_from_multiple_clients(_) -> {S, Rs} = T = send_from_multiple_clients(8, 1024), Max = ?FOREVER*1000, {false, [], _} = {Max < S, Rs -- [OI || {O,_} = OI <- Rs, is_integer(O)], T}. %% send_from_multiple_clients/2 %% %% Opens a listening socket and then spawns a specified number of %% processes, each of which connects to the listening socket. Each %% connecting process then sends a message, whose size in bytes is %% passed as an argument, the listening process sends a reply %% containing the time at which the message was received, and the %% connecting process then exits upon reception of this reply. %% %% Returns the elapsed time for all connecting process to exit %% together with a list of exit reasons for the connecting processes. %% In the successful case a connecting process exits with the %% outbound/inbound transit times for the sent/received message as %% reason. %% %% The observed behaviour is that some outbound messages (that is, %% from a connecting process to the listening process) can take an %% unexpectedly long time to complete their journey. The more %% connecting processes, the longer the possible delay it seems. %% %% eg. (With F = fun send_from_multiple_clients/2.) %% %% 5> F(2, 1024). %% {875,[{128,116},{113,139}]} %% 6> F(4, 1024). %% {2995290,[{2994022,250},{2994071,80},{200,130},{211,113}]} %% 7> F(8, 1024). %% {8997461,[{8996161,116}, %% {2996471,86}, %% {2996278,116}, %% {2996360,95}, %% {246,112}, %% {213,159}, %% {373,173}, %% {376,118}]} %% 8> F(8, 1024). %% {21001891,[{20999968,128}, %% {8997891,172}, %% {8997927,91}, %% {2995716,164}, %% {2995860,87}, %% {134,100}, %% {117,98}, %% {149,125}]} %% %% This turns out to have been due to SCTP resends as a consequence of %% the listener having an insufficient recbuf. Increasing the size %% solves the problem. %% send_from_multiple_clients(N, Sz) when is_integer(N), 0 < N, is_integer(Sz), 0 < Sz -> timer:tc(fun listen/2, [N, <>]). %% listen/2 listen(N, Bin) -> {ok, Sock} = open(), ok = gen_sctp:listen(Sock, true), {ok, PortNr} = inet:port(Sock), %% Spawn a middleman that in turn spawns N connecting processes, %% collects a list of exit reasons and then exits with the list as %% reason. loop/3 returns when we receive this list from the %% middleman's 'DOWN'. Self = self(), Fun = fun() -> exit(connect2(Self, PortNr, Bin)) end, {_, MRef} = spawn_monitor(fun() -> exit(fold(N, Fun)) end), loop(Sock, MRef, Bin). %% fold/2 %% %% Spawn N processes and collect their exit reasons in a list. fold(N, Fun) -> start(N, Fun), acc(N, []). start(0, _) -> ok; start(N, Fun) -> spawn_monitor(Fun), start(N-1, Fun). acc(0, Acc) -> Acc; acc(N, Acc) -> receive {'DOWN', _MRef, process, _, RC} -> acc(N-1, [RC | Acc]) end. %% loop/3 loop(Sock, MRef, Bin) -> receive ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], B}) -> Sz = size(Bin), {Sz, Bin} = {size(B), B}, %% assert ok = send(Sock, Id, mark(Bin)), loop(Sock, MRef, Bin); ?SCTP(Sock, _) -> loop(Sock, MRef, Bin); {'DOWN', MRef, process, _, Reason} -> Reason end. %% connect2/3 connect2(Pid, PortNr, Bin) -> erlang:monitor(process, Pid), {ok, Sock} = open(), ok = gen_sctp:connect_init(Sock, ?ADDR, PortNr, []), Id = assoc(Sock), %% T1 = time before send %% T2 = time after listening process received our message %% T3 = time after reply is received T1 = diameter_util:timestamp(), ok = send(Sock, Id, Bin), T2 = unmark(recv(Sock, Id)), T3 = diameter_util:timestamp(), {diameter_lib:micro_diff(T2, T1), %% Outbound diameter_lib:micro_diff(T3, T2)}. %% Inbound %% recv/2 recv(Sock, Id) -> receive ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], Bin}) -> Bin; T -> %% eg. 'DOWN' exit(T) end. %% send/3 send(Sock, Id, Bin) -> gen_sctp:send(Sock, Id, 0, Bin). %% mark/1 mark(Bin) -> Info = term_to_binary(diameter_util:timestamp()), <>. %% unmark/1 unmark(Bin) -> {_,_,_} = binary_to_term(Bin). %% =========================================================================== %% receive_what_was_sent/1 %% %% Demonstrates reception of a message that differs from that sent. receive_what_was_sent(_Config) -> send_from_multiple_clients(1, 1024*32). %% fails %% =========================================================================== %% open/0 open() -> open([]). %% open/1 open(Opts) -> gen_sctp:open([{ip, ?ADDR}, {port, 0}, {active, true}, binary, {recbuf, 1 bsl 16}, {sndbuf, 1 bsl 16} | Opts]). %% assoc/1 assoc(Sock) -> receive ?SCTP(Sock, {[], #sctp_assoc_change{state = S, assoc_id = Id}}) -> comm_up = S, %% assert Id end.