aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/test/gen_tcp_misc_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/test/gen_tcp_misc_SUITE.erl')
-rw-r--r--lib/kernel/test/gen_tcp_misc_SUITE.erl2362
1 files changed, 2362 insertions, 0 deletions
diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl
new file mode 100644
index 0000000000..5d726a3b1b
--- /dev/null
+++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl
@@ -0,0 +1,2362 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. 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%
+%%
+-module(gen_tcp_misc_SUITE).
+
+-include("test_server.hrl").
+
+%-compile(export_all).
+
+-export([all/1, controlling_process/1, no_accept/1, close_with_pending_output/1,
+ data_before_close/1, iter_max_socks/1, get_status/1,
+ passive_sockets/1, accept_closed_by_other_process/1,
+ init_per_testcase/2, fin_per_testcase/2,
+ otp_3924/1, otp_3924_sender/4, closed_socket/1,
+ shutdown_active/1, shutdown_passive/1, shutdown_pending/1,
+ default_options/1, http_bad_packet/1,
+ busy_send/1, busy_disconnect_passive/1, busy_disconnect_active/1,
+ fill_sendq/1, partial_recv_and_close/1,
+ partial_recv_and_close_2/1,partial_recv_and_close_3/1,so_priority/1,
+ % Accept tests
+ primitive_accept/1,multi_accept_close_listen/1,accept_timeout/1,
+ accept_timeouts_in_order/1,accept_timeouts_in_order2/1,accept_timeouts_in_order3/1,
+ accept_timeouts_mixed/1,
+ killing_acceptor/1,killing_multi_acceptors/1,killing_multi_acceptors2/1,
+ several_accepts_in_one_go/1,active_once_closed/1, send_timeout/1, otp_7731/1,
+ zombie_sockets/1, otp_7816/1, otp_8102/1]).
+
+%% Internal exports.
+-export([sender/3, not_owner/1, passive_sockets_server/2, priority_server/1, otp_7731_server/1, zombie_server/2]).
+
+init_per_testcase(_Func, Config) when is_list(Config) ->
+ Dog = test_server:timetrap(test_server:seconds(240)),
+ [{watchdog, Dog}|Config].
+fin_per_testcase(_Func, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog).
+
+all(suite) ->
+ [controlling_process, no_accept,
+ close_with_pending_output,
+ data_before_close, iter_max_socks, passive_sockets,
+ accept_closed_by_other_process, otp_3924, closed_socket,
+ shutdown_active, shutdown_passive, shutdown_pending,
+ default_options, http_bad_packet,
+ busy_send, busy_disconnect_passive, busy_disconnect_active,
+ fill_sendq, partial_recv_and_close,
+ partial_recv_and_close_2, partial_recv_and_close_3, so_priority,
+ primitive_accept,multi_accept_close_listen,accept_timeout,
+ accept_timeouts_in_order,accept_timeouts_in_order2,accept_timeouts_in_order3,
+ accept_timeouts_mixed,
+ killing_acceptor,killing_multi_acceptors,killing_multi_acceptors2,
+ several_accepts_in_one_go, active_once_closed, send_timeout, otp_7731,
+ zombie_sockets, otp_7816, otp_8102].
+
+
+default_options(doc) ->
+ ["Tests kernel application variables inet_default_listen_options and "
+ "inet_default_connect_options"];
+default_options(suite) ->
+ [];
+default_options(Config) when is_list(Config) ->
+ %% First check the delay_send option
+ ?line {true,true,true}=do_delay_send_1(),
+ ?line {false,false,false}=do_delay_send_2(),
+ ?line {true,false,false}=do_delay_send_3(),
+ ?line {false,false,false}=do_delay_send_4(),
+ ?line {false,false,false}=do_delay_send_5(),
+ ?line {false,true,true}=do_delay_send_6(),
+ %% Now lets start some nodes with different combinations of options:
+ ?line {true,true,true} = do_delay_on_other_node("",
+ fun do_delay_send_1/0),
+ ?line {true,false,false} =
+ do_delay_on_other_node("-kernel inet_default_connect_options "
+ "\"[{delay_send,true}]\"",
+ fun do_delay_send_2/0),
+
+ ?line {false,true,true} =
+ do_delay_on_other_node("-kernel inet_default_listen_options "
+ "\"[{delay_send,true}]\"",
+ fun do_delay_send_2/0),
+
+ ?line {true,true,true} =
+ do_delay_on_other_node("-kernel inet_default_listen_options "
+ "\"[{delay_send,true}]\"",
+ fun do_delay_send_3/0),
+ ?line {true,true,true} =
+ do_delay_on_other_node("-kernel inet_default_connect_options "
+ "\"[{delay_send,true}]\"",
+ fun do_delay_send_6/0),
+ ?line {false,false,false} =
+ do_delay_on_other_node("-kernel inet_default_connect_options "
+ "\"[{delay_send,true}]\"",
+ fun do_delay_send_5/0),
+ ?line {false,true,true} =
+ do_delay_on_other_node("-kernel inet_default_connect_options "
+ "\"[{delay_send,true}]\" "
+ "-kernel inet_default_listen_options "
+ "\"[{delay_send,true}]\"",
+ fun do_delay_send_5/0),
+ ?line {true,false,false} =
+ do_delay_on_other_node("-kernel inet_default_connect_options "
+ "\"[{delay_send,true}]\" "
+ "-kernel inet_default_listen_options "
+ "\"[{delay_send,true}]\"",
+ fun do_delay_send_4/0),
+ ?line {true,true,true} =
+ do_delay_on_other_node("-kernel inet_default_connect_options "
+ "\"{delay_send,true}\" "
+ "-kernel inet_default_listen_options "
+ "\"{delay_send,true}\"",
+ fun do_delay_send_2/0),
+ %% Active is to dangerous and is supressed
+ ?line {true,true,true} =
+ do_delay_on_other_node("-kernel inet_default_connect_options "
+ "\"{active,false}\" "
+ "-kernel inet_default_listen_options "
+ "\"{active,false}\"",
+ fun do_delay_send_7/0),
+ ?line {true,true,true} =
+ do_delay_on_other_node("-kernel inet_default_connect_options "
+ "\"[{active,false},{delay_send,true}]\" "
+ "-kernel inet_default_listen_options "
+ "\"[{active,false},{delay_send,true}]\"",
+ fun do_delay_send_7/0),
+ ?line {true,true,true} =
+ do_delay_on_other_node("-kernel inet_default_connect_options "
+ "\"[{active,false},{delay_send,true}]\" "
+ "-kernel inet_default_listen_options "
+ "\"[{active,false},{delay_send,true}]\"",
+ fun do_delay_send_2/0),
+ ok.
+
+
+do_delay_on_other_node(XArgs, Function) ->
+ Dir = filename:dirname(code:which(?MODULE)),
+ {ok,Node} = test_server:start_node(test_default_options_slave,slave,
+ [{args,"-pa " ++ Dir ++ " " ++
+ XArgs}]),
+ Res = rpc:call(Node,erlang,apply,[Function,[]]),
+ test_server:stop_node(Node),
+ Res.
+
+
+do_delay_send_1() ->
+ {ok,LS}=gen_tcp:listen(0,[{delay_send,true}]),
+ {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
+ {ok,S}=gen_tcp:connect("localhost",PortNum,[{delay_send,true}]),
+ {ok,S2}= gen_tcp:accept(LS),
+ {ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
+ {ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
+ {ok,[{delay_send,B3}]}=inet:getopts(S2,[delay_send]),
+ gen_tcp:close(S2),
+ gen_tcp:close(S),
+ gen_tcp:close(LS),
+ {B1,B2,B3}.
+
+do_delay_send_2() ->
+ {ok,LS}=gen_tcp:listen(0,[]),
+ {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
+ {ok,S}=gen_tcp:connect("localhost",PortNum,[]),
+ {ok,S2}= gen_tcp:accept(LS),
+ {ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
+ {ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
+ {ok,[{delay_send,B3}]}=inet:getopts(S2,[delay_send]),
+ gen_tcp:close(S2),
+ gen_tcp:close(S),
+ gen_tcp:close(LS),
+ {B1,B2,B3}.
+
+do_delay_send_3() ->
+ {ok,LS}=gen_tcp:listen(0,[]),
+ {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
+ {ok,S}=gen_tcp:connect("localhost",PortNum,[{delay_send,true}]),
+ {ok,S2}= gen_tcp:accept(LS),
+ {ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
+ {ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
+ {ok,[{delay_send,B3}]}=inet:getopts(S2,[delay_send]),
+ gen_tcp:close(S2),
+ gen_tcp:close(S),
+ gen_tcp:close(LS),
+ {B1,B2,B3}.
+
+do_delay_send_4() ->
+ {ok,LS}=gen_tcp:listen(0,[{delay_send,false}]),
+ {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
+ {ok,S}=gen_tcp:connect("localhost",PortNum,[]),
+ {ok,S2}= gen_tcp:accept(LS),
+ {ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
+ {ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
+ {ok,[{delay_send,B3}]}=inet:getopts(S2,[delay_send]),
+ gen_tcp:close(S2),
+ gen_tcp:close(S),
+ gen_tcp:close(LS),
+ {B1,B2,B3}.
+
+do_delay_send_5() ->
+ {ok,LS}=gen_tcp:listen(0,[]),
+ {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
+ {ok,S}=gen_tcp:connect("localhost",PortNum,[{delay_send,false}]),
+ {ok,S2}= gen_tcp:accept(LS),
+ {ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
+ {ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
+ {ok,[{delay_send,B3}]}=inet:getopts(S2,[delay_send]),
+ gen_tcp:close(S2),
+ gen_tcp:close(S),
+ gen_tcp:close(LS),
+ {B1,B2,B3}.
+
+do_delay_send_6() ->
+ {ok,LS}=gen_tcp:listen(0,[{delay_send,true}]),
+ {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
+ {ok,S}=gen_tcp:connect("localhost",PortNum,[]),
+ {ok,S2}= gen_tcp:accept(LS),
+ {ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
+ {ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
+ {ok,[{delay_send,B3}]}=inet:getopts(S2,[delay_send]),
+ gen_tcp:close(S2),
+ gen_tcp:close(S),
+ gen_tcp:close(LS),
+ {B1,B2,B3}.
+
+do_delay_send_7() ->
+ {ok,LS}=gen_tcp:listen(0,[]),
+ {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
+ {ok,S}=gen_tcp:connect("localhost",PortNum,[]),
+ {ok,S2}= gen_tcp:accept(LS),
+ {ok,[{active,B1}]}=inet:getopts(S,[active]),
+ {ok,[{active,B2}]}=inet:getopts(LS,[active]),
+ {ok,[{active,B3}]}=inet:getopts(S2,[active]),
+ gen_tcp:close(S2),
+ gen_tcp:close(S),
+ gen_tcp:close(LS),
+ {B1,B2,B3}.
+
+
+
+controlling_process(doc) ->
+ ["Open a listen port and change controlling_process for it",
+ "The result should be ok of done by the owner process,"
+ "Otherwise is should return {error,not_owner} or similar"];
+controlling_process(suite) -> [];
+controlling_process(Config) when is_list(Config) ->
+ {ok,S} = gen_tcp:listen(0,[]),
+ Pid2 = spawn(?MODULE,not_owner,[S]),
+ Pid2 ! {self(),2,control},
+ ?line {error, E} = receive {2,_E} ->
+ _E
+ after 10000 -> timeout
+ end,
+ io:format("received ~p~n",[E]),
+ Pid = spawn(?MODULE,not_owner,[S]),
+ ?line ok = gen_tcp:controlling_process(S,Pid),
+ Pid ! {self(),1,control},
+ ?line ok = receive {1,ok} ->
+ ok
+ after 1000 -> timeout
+ end,
+ Pid ! close.
+
+not_owner(S) ->
+ receive
+ {From,Tag,control} ->
+ From ! {Tag,gen_tcp:controlling_process(S,self())};
+ close ->
+ gen_tcp:close(S)
+ after 1000 ->
+ ok
+ end.
+
+no_accept(doc) ->
+ ["Open a listen port and connect to it, then close the listen port ",
+ "without doing any accept. The connected socket should receive ",
+ "a tcp_closed message."];
+no_accept(suite) -> [];
+no_accept(Config) when is_list(Config) ->
+ case os:type() of
+ vxworks ->
+ {skip,"Too tough for vxworks"};
+ _ ->
+ no_accept2()
+ end.
+
+no_accept2() ->
+ ?line {ok, L} = gen_tcp:listen(0, []),
+ ?line {ok, {_, Port}} = inet:sockname(L),
+ ?line {ok, Client} = gen_tcp:connect(localhost, Port, []),
+ ?line ok = gen_tcp:close(L),
+ ?line receive
+ {tcp_closed, Client} ->
+ ok
+ after 5000 ->
+ ?line test_server:fail(never_closed)
+
+ end.
+
+close_with_pending_output(doc) ->
+ ["Send several packets to a socket and close it. All packets should arrive ",
+ "to the other end."];
+close_with_pending_output(suite) -> [];
+close_with_pending_output(Config) when is_list(Config) ->
+ case os:type() of
+ vxworks ->
+ {skipped,"Too tough for vxworks"};
+ _ ->
+ close_with_pending_output2()
+ end.
+
+close_with_pending_output2() ->
+ ?line {ok, L} = gen_tcp:listen(0, [binary, {active, false}]),
+ ?line {ok, {_, Port}} = inet:sockname(L),
+ ?line Packets = 16,
+ ?line Total = 2048*Packets,
+ case start_remote(close_pending) of
+ {ok, Node} ->
+ ?line {ok, Host} = inet:gethostname(),
+ ?line spawn_link(Node, ?MODULE, sender, [Port, Packets, Host]),
+ ?line {ok, A} = gen_tcp:accept(L),
+ ?line case gen_tcp:recv(A, Total) of
+ {ok, Bin} when byte_size(Bin) == Total ->
+ gen_tcp:close(A),
+ gen_tcp:close(L);
+ {ok, Bin} ->
+ ?line test_server:fail({small_packet,
+ byte_size(Bin)});
+ Error ->
+ ?line test_server:fail({unexpected, Error})
+ end,
+ ok;
+ {error, no_remote_hosts} ->
+ {skipped,"No remote hosts"};
+ {error, Other} ->
+ ?line ?t:fail({failed_to_start_slave_node, Other})
+ end.
+
+sender(Port, Packets, Host) ->
+ X256 = lists:seq(0, 255),
+ X512 = [X256|X256],
+ X1K = [X512|X512],
+ Bin = list_to_binary([X1K|X1K]),
+ {ok, Sock} = gen_tcp:connect(Host, Port, []),
+ send_loop(Sock, Bin, Packets),
+ ok = gen_tcp:close(Sock).
+
+send_loop(_Sock, _Data, 0) -> ok;
+send_loop(Sock, Data, Left) ->
+ ok = gen_tcp:send(Sock, Data),
+ send_loop(Sock, Data, Left-1).
+
+-define(OTP_3924_MAX_DELAY, 100).
+%% Taken out of the blue, but on intra host connections
+%% I expect propagation of a close to be quite fast
+%% so 100 ms seems reasonable.
+
+otp_3924(doc) ->
+ ["Tests that a socket can be closed fast enough."];
+otp_3924(suite) -> [];
+otp_3924(Config) when is_list(Config) ->
+ MaxDelay = (case has_superfluous_schedulers() of
+ true -> 4;
+ false -> 1
+ end
+ * case {erlang:system_info(debug_compiled),
+ erlang:system_info(lock_checking)} of
+ {true, _} -> 6;
+ {_, true} -> 2;
+ _ -> 1
+ end * ?OTP_3924_MAX_DELAY),
+ case os:type() of
+ vxworks ->
+%% {skip,"Too tough for vxworks"};
+ otp_3924_1(MaxDelay);
+ _ ->
+ otp_3924_1(MaxDelay)
+ end.
+
+otp_3924_1(MaxDelay) ->
+ Dog = test_server:timetrap(test_server:seconds(240)),
+ ?line {ok, Node} = start_node(otp_3924),
+ ?line DataLen = 100*1024,
+ ?line Data = otp_3924_data(DataLen),
+ % Repeat the test a couple of times to prevent the test from passing
+ % by chance.
+ repeat(10,
+ fun (N) ->
+ ?line ok = otp_3924(MaxDelay, Node, Data, DataLen, N)
+ end),
+ ?line test_server:stop_node(Node),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+otp_3924(MaxDelay, Node, Data, DataLen, N) ->
+ ?line {ok, L} = gen_tcp:listen(0, [list, {active, false}]),
+ ?line {ok, {_, Port}} = inet:sockname(L),
+ ?line {ok, Host} = inet:gethostname(),
+ ?line Sender = spawn_link(Node,
+ ?MODULE,
+ otp_3924_sender,
+ [self(), Host, Port, Data]),
+ ?line Data = otp_3924_receive_data(L, Sender, MaxDelay, DataLen, N),
+ ?line ok = gen_tcp:close(L).
+
+otp_3924_receive_data(LSock, Sender, MaxDelay, Len, N) ->
+ ?line OP = process_flag(priority, max),
+ ?line OTE = process_flag(trap_exit, true),
+ ?line TimeoutRef = make_ref(),
+ ?line Data = (catch begin
+ ?line Sender ! start,
+ ?line {ok, Sock} = gen_tcp:accept(LSock),
+ ?line D = otp_3924_receive_data(Sock,
+ TimeoutRef,
+ MaxDelay,
+ Len,
+ [],
+ 0),
+ ?line ok = gen_tcp:close(Sock),
+ D
+ end),
+ ?line unlink(Sender),
+ ?line process_flag(trap_exit, OTE),
+ ?line process_flag(priority, OP),
+ receive
+ {'EXIT', _, TimeoutRef} ->
+ ?line test_server:fail({close_not_fast_enough,MaxDelay,N});
+ {'EXIT', Sender, Reason} ->
+ ?line test_server:fail({sender_exited, Reason});
+ {'EXIT', _Other, Reason} ->
+ ?line test_server:fail({linked_process_exited, Reason})
+ after 0 ->
+ case Data of
+ {'EXIT', {A,B}} ->
+ ?line test_server:fail({A,B,N});
+ {'EXIT', Failure} ->
+ ?line test_server:fail(Failure);
+ _ ->
+ ?line Data
+ end
+ end.
+
+
+otp_3924_receive_data(Sock, TimeoutRef, MaxDelay, Len, Acc, AccLen) ->
+ case gen_tcp:recv(Sock, 0) of
+ {ok, Data} ->
+ NewAccLen = AccLen + length(Data),
+ if
+ NewAccLen == Len ->
+ ?line {ok, TRef} = timer:exit_after(MaxDelay,
+ self(),
+ TimeoutRef),
+ ?line {error, closed} = gen_tcp:recv(Sock, 0),
+ ?line timer:cancel(TRef),
+ ?line lists:flatten([Acc, Data]);
+ NewAccLen > Len ->
+ exit({received_too_much, NewAccLen});
+ true ->
+ otp_3924_receive_data(Sock,
+ TimeoutRef,
+ MaxDelay,
+ Len,
+ [Acc, Data],
+ NewAccLen)
+ end;
+ {error, closed} ->
+ exit({premature_close, AccLen});
+ Error ->
+ exit({unexpected_error, Error})
+ end.
+
+otp_3924_data(Size) ->
+ Block =
+ "This is a sequence of characters that will be repeated "
+ "again and again and again and again and again and ... ",
+ L = length(Block),
+ otp_3924_data(Block, [], Size div L, Size rem L).
+
+otp_3924_data(_, Acc, 0, 0) ->
+ lists:flatten(Acc);
+otp_3924_data(_, Acc, 0, SingleLeft) ->
+ otp_3924_data(false, ["."|Acc], 0, SingleLeft-1);
+otp_3924_data(Block, Acc, BlockLeft, SingleLeft) ->
+ otp_3924_data(Block, [Block|Acc], BlockLeft-1, SingleLeft).
+
+otp_3924_sender(Receiver, Host, Port, Data) ->
+ receive
+ start ->
+ {ok, Sock} = gen_tcp:connect(Host, Port, [list]),
+ gen_tcp:send(Sock, Data),
+ ok = gen_tcp:close(Sock),
+ unlink(Receiver)
+ end.
+
+
+data_before_close(doc) ->
+ ["Tests that a huge amount of data can be received before a close."];
+data_before_close(Config) when is_list(Config) ->
+ case os:type() of
+ vxworks ->
+ {skip,"Too tough for vxworks"};
+ _ ->
+ data_before_close2()
+ end.
+
+data_before_close2() ->
+ ?line {ok, L} = gen_tcp:listen(0, [binary]),
+ ?line {ok, {_, TcpPort}} = inet:sockname(L),
+ ?line Bytes = 256*1024,
+ ?line spawn_link(fun() -> huge_sender(TcpPort, Bytes) end),
+ ?line {ok, A} = gen_tcp:accept(L),
+ ?line case count_bytes_recv(A, 0) of
+ {Bytes, Result} ->
+ io:format("Result: ~p", [Result]);
+ {Wrong, Result} ->
+ io:format("Result: ~p", [Result]),
+ test_server:fail({wrong_count, Wrong})
+ end,
+ ok.
+
+count_bytes_recv(Sock, Total) ->
+ receive
+ {tcp, Sock, Bin} ->
+ count_bytes_recv(Sock, Total+byte_size(Bin));
+ Other ->
+ {Total, Other}
+ end.
+
+huge_sender(TcpPort, Bytes) ->
+ {ok, Client} = gen_tcp:connect(localhost, TcpPort, []),
+ receive after 500 -> ok end,
+ gen_tcp:send(Client, make_zero_packet(Bytes)),
+ gen_tcp:close(Client).
+
+make_zero_packet(0) -> [];
+make_zero_packet(N) when N rem 2 == 0 ->
+ P = make_zero_packet(N div 2),
+ [P|P];
+make_zero_packet(N) ->
+ P = make_zero_packet(N div 2),
+ [0, P|P].
+get_status(doc) ->
+ ["OTP-2924",
+ "test that the socket process does not crash when sys:get_status(Pid)",
+ "is called."];
+get_status(suite) -> [];
+get_status(Config) when is_list(Config) ->
+ ?line {ok,{socket,Pid,_,_}} = gen_tcp:listen(5678,[]),
+ ?line {status,Pid,_,_} = sys:get_status(Pid).
+
+iter_max_socks(doc) ->
+ ["Open as many sockets as possible. Do this several times and check ",
+ "that we get the same number of sockets every time."];
+iter_max_socks(Config) when is_list(Config) ->
+ case os:type() of
+ vxworks ->
+ {skip,"Too tough for vxworks"};
+ _ ->
+ iter_max_socks2()
+ end.
+
+-define(RECOVER_SLEEP, 60000).
+-define(RETRY_SLEEP, 15000).
+
+iter_max_socks2() ->
+ ?line N =
+ case os:type() of
+ vxworks ->
+ 10;
+ _ ->
+ 20
+ end,
+ L = do_iter_max_socks(N, initalize),
+ ?line io:format("Result: ~p",[L]),
+ ?line all_equal(L),
+ ?line {comment, "Max sockets: " ++ integer_to_list(hd(L))}.
+
+do_iter_max_socks(0, _) ->
+ [];
+do_iter_max_socks(N, initalize) ->
+ MS = max_socks(),
+ [MS|do_iter_max_socks(N-1, MS)];
+do_iter_max_socks(N, failed) ->
+ MS = max_socks(),
+ [MS|do_iter_max_socks(N-1, failed)];
+do_iter_max_socks(N, First) when is_integer(First) ->
+ ?line MS = max_socks(),
+ if MS == First ->
+ ?line [MS|do_iter_max_socks(N-1, First)];
+ true ->
+ ?line io:format("Sleeping for ~p seconds...~n",
+ [?RETRY_SLEEP/1000]),
+ ?line ?t:sleep(?RETRY_SLEEP),
+ ?line io:format("Trying again...~n", []),
+ ?line RetryMS = max_socks(),
+ ?line if RetryMS == First ->
+ ?line [RetryMS|do_iter_max_socks(N-1, First)];
+ true ->
+ ?line [RetryMS|do_iter_max_socks(N-1, failed)]
+ end
+ end.
+
+all_equal([]) ->
+ ok;
+all_equal([Rule | T]) ->
+ all_equal(Rule, T).
+
+all_equal(Rule, [Rule | T]) ->
+ all_equal(Rule, T);
+all_equal(_, [_ | _]) ->
+ ?line ?t:sleep(?RECOVER_SLEEP), % Wait a while and *hope* that we'll
+ % recover so other tests won't be
+ % affected.
+ ?t:fail(max_socket_mismatch);
+all_equal(_Rule, []) ->
+ ok.
+
+max_socks() ->
+ ?line Socks = open_socks(),
+ ?line N = length(Socks),
+ ?line lists:foreach(fun(S) -> ok = gen_tcp:close(S) end, Socks),
+ io:format("Got ~p sockets", [N]),
+ N.
+
+open_socks() ->
+ case gen_tcp:listen(0, []) of
+ {ok, L} ->
+ {ok, {_, Port}} = inet:sockname(L),
+ [L| connect_accept(L, Port)];
+ _ ->
+ []
+ end.
+
+connect_accept(L, Port) ->
+ case gen_tcp:connect(localhost, Port, []) of
+ {ok, C} ->
+ [C| do_accept(L, Port)];
+ _ ->
+ []
+ end.
+
+do_accept(L, Port) ->
+ case gen_tcp:accept(L) of
+ {ok, A} -> [A| connect_accept(L, Port)];
+ _ -> []
+ end.
+
+start_node(Name) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{args, "-pa " ++ Pa}]).
+
+start_remote(Name) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{remote, true}, {args, "-pa " ++ Pa}]).
+
+passive_sockets(doc) ->
+ ["Tests that when 'the other side' on a passive socket closes, the connecting",
+ "side still can read until the end of data."];
+passive_sockets(Config) when is_list(Config) ->
+ ?line spawn_link(?MODULE, passive_sockets_server,
+ [[{active,false}],self()]),
+ ?line receive
+ {socket,Port} -> ok
+ end,
+ ?t:sleep(500),
+ ?line case gen_tcp:connect("localhost", Port, [{active, false}]) of
+ {ok, Sock} ->
+ passive_sockets_read(Sock);
+ Error ->
+ ?t:fail({"Could not connect to server", Error})
+ end.
+
+%%
+%% Read until we get an {error, closed}. If we get another error, this test case
+%% should fail.
+%%
+passive_sockets_read(Sock) ->
+ case gen_tcp:recv(Sock, 0, 2000) of
+ {ok, Data} ->
+ io:format("Received ~p bytes~n", [length(Data)]),
+ passive_sockets_read(Sock);
+ {error, closed} ->
+ gen_tcp:close(Sock);
+ Error ->
+ gen_tcp:close(Sock),
+ ?t:fail({"Did not get {error, closed} before other error", Error})
+ end.
+
+passive_sockets_server(Opts, Parent) ->
+ ?line case gen_tcp:listen(0, Opts) of
+ {ok, LSock} ->
+ {ok,{_,Port}} = inet:sockname(LSock),
+ Parent ! {socket,Port},
+ passive_sockets_server_accept(LSock);
+ Error ->
+ ?t:fail({"Could not create listen socket", Error})
+ end.
+
+passive_sockets_server_accept(Sock) ->
+ ?line case gen_tcp:accept(Sock) of
+ {ok, Socket} ->
+ ?t:sleep(500), % Simulate latency
+ passive_sockets_server_send(Socket, 5),
+ passive_sockets_server_accept(Sock);
+ Error ->
+ ?t:fail({"Could not accept connection", Error})
+ end.
+
+passive_sockets_server_send(Socket, 0) ->
+ io:format("Closing other end..~n", []),
+ gen_tcp:close(Socket);
+passive_sockets_server_send(Socket, X) ->
+ ?line Data = lists:duplicate(1024*X, $a),
+ ?line case gen_tcp:send(Socket, Data) of
+ ok ->
+ ?t:sleep(50), % Simulate some processing.
+ passive_sockets_server_send(Socket, X-1);
+ {error, _Reason} ->
+ ?t:fail("Failed to send data")
+ end.
+
+
+accept_closed_by_other_process(doc) ->
+ ["Tests the return value from gen_tcp:accept when ",
+ "the socket is closed from an other process. (OTP-3817)"];
+accept_closed_by_other_process(Config) when is_list(Config) ->
+ ?line Parent = self(),
+ ?line {ok, ListenSocket} = gen_tcp:listen(0, []),
+ ?line Child =
+ spawn_link(
+ fun() ->
+ Parent ! {self(), gen_tcp:accept(ListenSocket)}
+ end),
+ ?line receive after 1000 -> ok end,
+ ?line ok = gen_tcp:close(ListenSocket),
+ ?line receive
+ {Child, {error, closed}} ->
+ ok;
+ {Child, Other} ->
+ ?t:fail({"Wrong result of gen_tcp:accept", Other})
+ end.
+
+repeat(N, Fun) ->
+ repeat(N, N, Fun).
+
+repeat(N, T, Fun) when is_integer(N), N > 0 ->
+ Fun(T-N),
+ repeat(N-1, T, Fun);
+repeat(_, _, _) ->
+ ok.
+
+
+closed_socket(suite) ->
+ [];
+closed_socket(doc) ->
+ ["Tests the response when using a closed socket as argument"];
+closed_socket(Config) when is_list(Config) ->
+ ?line {ok, LS1} = gen_tcp:listen(0, []),
+ ?line erlang:yield(),
+ ?line ok = gen_tcp:close(LS1),
+ %% If the following delay is uncommented, the result error values
+ %% below will change from {error, einval} to {error, closed} since
+ %% inet_db then will have noticed that the socket is closed.
+ %% This is a scheduling issue, i.e when the gen_server in
+ %% in inet_db processes the 'EXIT' message from the port,
+ %% the socket is unregistered.
+ %%
+ %% ?line test_server:sleep(test_server:seconds(2)),
+ %%
+ ?line {error, R_send} = gen_tcp:send(LS1, "data"),
+ ?line {error, R_recv} = gen_tcp:recv(LS1, 17),
+ ?line {error, R_accept} = gen_tcp:accept(LS1),
+ ?line {error, R_controlling_process} =
+ gen_tcp:controlling_process(LS1, self()),
+ %%
+ ?line ok = io:format("R_send = ~p~n", [R_send]),
+ ?line ok = io:format("R_recv = ~p~n", [R_recv]),
+ ?line ok = io:format("R_accept = ~p~n", [R_accept]),
+ ?line ok = io:format("R_controlling_process = ~p~n",
+ [R_controlling_process]),
+ ok.
+
+%%%
+%%% Test using the gen_tcp:shutdown/2 function using a sort server.
+%%%
+
+shutdown_active(Config) when is_list(Config) ->
+ ?line shutdown_common(true).
+
+shutdown_passive(Config) when is_list(Config) ->
+ ?line shutdown_common(false).
+
+shutdown_common(Active) ->
+ ?line P = sort_server(Active),
+ io:format("Sort server port: ~p\n", [P]),
+
+ ?line do_sort(P, []),
+ ?line do_sort(P, ["glurf"]),
+ ?line do_sort(P, ["abc","nisse","dum"]),
+
+ ?line do_sort(P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(25, 255)]),
+ ?line do_sort(P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(77, 999)]),
+ ?line do_sort(P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(25, 55)]),
+ ?line do_sort(P, []),
+ ?line do_sort(P, ["apa"]),
+ ?line do_sort(P, ["kluns","gorilla"]),
+ ?line do_sort(P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(25, 1233)]),
+ ?line do_sort(P, []),
+
+ receive
+ Any ->
+ ?t:fail({unexpected_message,Any})
+ after 0 -> ok
+ end.
+
+do_sort(P, List0) ->
+ List = [El++"\n" || El <- List0],
+ {ok,S} = gen_tcp:connect(localhost, P, [{packet,line}]),
+ send_lines(S, List),
+ gen_tcp:shutdown(S, write),
+ Lines = collect_lines(S, true),
+ io:format("~p\n", [Lines]),
+ Lines = lists:sort(List),
+ ok = gen_tcp:close(S).
+
+sort_server(Active) ->
+ Opts = [{exit_on_close,false},{packet,line},{active,Active}],
+ ?line {ok,L} = gen_tcp:listen(0, Opts),
+ Go = make_ref(),
+ ?line Pid = spawn_link(fun() ->
+ receive Go -> sort_server_1(L, Active) end
+ end),
+ ?line ok = gen_tcp:controlling_process(L, Pid),
+ ?line Pid ! Go,
+ ?line {ok,Port} = inet:port(L),
+ Port.
+
+sort_server_1(L, Active) ->
+ {ok,S} = gen_tcp:accept(L),
+ Go = make_ref(),
+ Sorter = spawn(fun() -> receive Go -> sorter(S, Active) end end),
+ ok = gen_tcp:controlling_process(S, Sorter),
+ Sorter ! Go,
+ sort_server_1(L, Active).
+
+sorter(S, Active) ->
+ Lines = collect_lines(S, Active),
+ send_lines(S, lists:sort(Lines)),
+ gen_tcp:shutdown(S, write),
+ gen_tcp:close(S).
+
+collect_lines(S, true) ->
+ collect_lines_1(S, []);
+collect_lines(S, false) ->
+ passive_collect_lines_1(S, []).
+
+collect_lines_1(S, Acc) ->
+ receive
+ {tcp,S,Line} -> collect_lines_1(S, [Line|Acc]);
+ {tcp_closed,S} -> lists:reverse(Acc)
+ end.
+
+passive_collect_lines_1(S, Acc) ->
+ case gen_tcp:recv(S, 0) of
+ {ok,Line} -> passive_collect_lines_1(S, [Line|Acc]);
+ {error,closed} -> lists:reverse(Acc)
+ end.
+
+
+send_lines(S, Lines) ->
+ lists:foreach(fun(Line) ->
+ gen_tcp:send(S, Line)
+ end, Lines).
+
+%%%
+%%% Shutdown pending.
+%%%
+
+shutdown_pending(Config) when is_list(Config) ->
+ N = 512*1024+17,
+ io:format("~p\n", [N]),
+ Data = [<<N:32>>,ones(N),42],
+ P = a_server(),
+ io:format("Server port: ~p\n", [P]),
+ ?line {ok,S} = gen_tcp:connect(localhost, P, []),
+ ?line gen_tcp:send(S, Data),
+ ?line gen_tcp:shutdown(S, write),
+ ?line receive
+ {tcp,S,Msg} ->
+ io:format("~p\n", [Msg]),
+ ?line N = list_to_integer(Msg) - 5;
+ Other ->
+ ?t:fail({unexpected,Other})
+ end,
+ ok.
+
+ ones(0) -> [];
+ ones(1) -> [1];
+ ones(N) ->
+ Half = N div 2,
+ Ones = ones(Half),
+ case 2*Half of
+ N -> [Ones|Ones];
+ _ -> [1,Ones|Ones]
+ end.
+
+ a_server() ->
+ ?line {ok,L} = gen_tcp:listen(0, [{exit_on_close,false},{active,false}]),
+ ?line Pid = spawn_link(fun() -> a_server(L) end),
+ ?line ok = gen_tcp:controlling_process(L, Pid),
+ ?line {ok,Port} = inet:port(L),
+ Port.
+
+ a_server(L) ->
+ {ok,S} = gen_tcp:accept(L),
+ do_recv(S, []).
+
+ do_recv(S, Bs0) ->
+ case gen_tcp:recv(S, 0) of
+ {ok,B} ->
+ do_recv(S, [Bs0,B]);
+ {error,closed} ->
+ Bs = list_to_binary(Bs0),
+ gen_tcp:send(S, integer_to_list(byte_size(Bs))),
+ gen_tcp:close(S)
+ end.
+
+
+%% Thanks to Luke Gorrie. Tests for a very specific problem with
+%% corrupt data. The testcase will be killed by the timetrap timeout
+%% if the bug is present.
+http_bad_packet(Config) when is_list(Config) ->
+ ?line {ok,L} = gen_tcp:listen(0,
+ [{active, false},
+ binary,
+ {reuseaddr, true},
+ {packet, http}]),
+ ?line {ok,Port} = inet:port(L),
+ ?line spawn_link(fun() -> erlang:yield(), http_bad_client(Port) end),
+ ?line case gen_tcp:accept(L) of
+ {ok,S} ->
+ http_worker(S);
+ Err ->
+ exit({accept,Err})
+ end.
+
+http_worker(S) ->
+ case gen_tcp:recv(S, 0, 30000) of
+ {ok,Data} ->
+ io:format("Data: ~p\n", [Data]),
+ http_worker(S);
+ {error,Rsn} ->
+ io:format("Error: ~p\n", [Rsn]),
+ ok
+ end.
+
+http_bad_client(Port) ->
+ {ok,S} = gen_tcp:connect("localhost", Port, [{active,false}, binary]),
+ ok = gen_tcp:send(S, "\r\n"),
+ ok = gen_tcp:close(S).
+
+
+%% Fill send queue and then start receiving.
+%%
+busy_send(Config) when is_list(Config) ->
+ ?line Master = self(),
+ ?line Msg = <<"the quick brown fox jumps over a lazy dog~n">>,
+ ?line Server =
+ spawn_link(fun () ->
+ {ok,L} = gen_tcp:listen
+ (0, [{active,false},binary,
+ {reuseaddr,true},{packet,0}]),
+ {ok,Port} = inet:port(L),
+ Master ! {self(),client,
+ busy_send_client(Port, Master, Msg)},
+ busy_send_srv(L, Master, Msg)
+ end),
+ ?line io:format("~p Server~n", [Server]),
+ ?line receive
+ {Server,client,Client} ->
+ ?line io:format("~p Client~n", [Client]),
+ ?line busy_send_loop(Server, Client, 0)
+ end.
+
+busy_send_loop(Server, Client, N) ->
+ %% Master
+ %%
+ ?line receive {Server,send} ->
+ busy_send_loop(Server, Client, N+1)
+ after 2000 ->
+ %% Send queue full, sender blocked
+ %% -> stop sender and release client
+ ?line io:format("Send timeout, time to receive...~n", []),
+ ?line Server ! {self(),close},
+ ?line Client ! {self(),recv,N+1},
+ ?line receive
+ {Server,send} ->
+ ?line busy_send_2(Server, Client, N+1)
+ after 10000 ->
+ ?t:fail({timeout,{server,not_send,flush([])}})
+ end
+ end.
+
+busy_send_2(Server, Client, _N) ->
+ %% Master
+ %%
+ ?line receive
+ {Server,[closed]} ->
+ ?line receive
+ {Client,[0,{error,closed}]} ->
+ ok
+ end
+ after 10000 ->
+ ?t:fail({timeout,{server,not_closed,flush([])}})
+ end.
+
+busy_send_srv(L, Master, Msg) ->
+ %% Server
+ %%
+ {ok,Socket} = gen_tcp:accept(L),
+ busy_send_srv_loop(Socket, Master, Msg).
+
+busy_send_srv_loop(Socket, Master, Msg) ->
+ %% Server
+ %%
+ receive
+ {Master,close} ->
+ ok = gen_tcp:close(Socket),
+ Master ! {self(),flush([closed])}
+ after 0 ->
+ ok = gen_tcp:send(Socket, Msg),
+ Master ! {self(),send},
+ busy_send_srv_loop(Socket, Master, Msg)
+ end.
+
+busy_send_client(Port, Master, Msg) ->
+ %% Client
+ %%
+ spawn_link(
+ fun () ->
+ {ok,Socket} = gen_tcp:connect(
+ "localhost", Port,
+ [{active,false},binary,{packet,0}]),
+ receive
+ {Master,recv, N} ->
+ busy_send_client_loop(Socket, Master, Msg, N)
+ end
+ end).
+
+busy_send_client_loop(Socket, Master, Msg, N) ->
+ %% Client
+ %%
+ Size = byte_size(Msg),
+ case gen_tcp:recv(Socket, Size) of
+ {ok,Msg} ->
+ busy_send_client_loop(Socket, Master, Msg, N-1);
+ Other ->
+ Master ! {self(),flush([Other,N])}
+ end.
+
+%%%
+%%% Send to a socket whose other end does not read until the port gets busy.
+%%% Then close the other end. The writer should get an {error,closed} error.
+%%% (Passive mode.)
+%%%
+
+busy_disconnect_passive(Config) when is_list(Config) ->
+ MuchoData = list_to_binary(ones(64*1024)),
+ ?line [do_busy_disconnect_passive(MuchoData) || _ <- lists:seq(1, 10)],
+ ok.
+
+do_busy_disconnect_passive(MuchoData) ->
+ S = busy_disconnect_prepare_server([{active,false}]),
+ busy_disconnect_passive_send(S, MuchoData).
+
+busy_disconnect_passive_send(S, Data) ->
+ ?line case gen_tcp:send(S, Data) of
+ ok -> ?line busy_disconnect_passive_send(S, Data);
+ {error,closed} -> ok
+ end.
+
+%%%
+%%% Send to a socket whose other end does not read until the port gets busy.
+%%% Then close the other end. The writer should get an {error,closed} error and
+%%% a {tcp_closed,Socket} message. (Active mode.)
+%%%
+busy_disconnect_active(Config) when is_list(Config) ->
+ MuchoData = list_to_binary(ones(64*1024)),
+ ?line [do_busy_disconnect_active(MuchoData) || _ <- lists:seq(1, 10)],
+ ok.
+
+do_busy_disconnect_active(MuchoData) ->
+ S = busy_disconnect_prepare_server([{active,true}]),
+ busy_disconnect_active_send(S, MuchoData).
+
+busy_disconnect_active_send(S, Data) ->
+ ?line case gen_tcp:send(S, Data) of
+ ok -> ?line busy_disconnect_active_send(S, Data);
+ {error,closed} ->
+ receive
+ {tcp_closed,S} -> ok;
+ _Other -> ?line ?t:fail()
+ end
+ end.
+
+
+busy_disconnect_prepare_server(ConnectOpts) ->
+ ?line Sender = self(),
+ ?line Server = spawn_link(fun() -> busy_disconnect_server(Sender) end),
+ receive {port,Server,Port} -> ok end,
+ ?line {ok,S} = gen_tcp:connect(localhost, Port, ConnectOpts),
+ Server ! {Sender,sending},
+ S.
+
+busy_disconnect_server(Sender) ->
+ {ok,L} = gen_tcp:listen(0, [{active,false},binary,{reuseaddr,true},{packet,0}]),
+ {ok,Port} = inet:port(L),
+ Sender ! {port,self(),Port},
+ {ok,S} = gen_tcp:accept(L),
+ receive
+ {Sender,sending} ->
+ busy_disconnect_server_wait_for_busy(Sender, S)
+ end.
+
+%% Close the socket as soon as the Sender process can't send because of
+%% a busy port.
+busy_disconnect_server_wait_for_busy(Sender, S) ->
+ case process_info(Sender, status) of
+ {status,waiting} ->
+ %% We KNOW that the sender will be in state 'waiting' only
+ %% if the port has become busy. (Fallback solution if the
+ %% implementation changes: Watch Sender's reduction count;
+ %% when it stops changing, wait 2 seconds and then close.)
+ gen_tcp:close(S);
+ _Other ->
+ io:format("~p\n", [_Other]),
+ timer:sleep(100),
+ busy_disconnect_server_wait_for_busy(Sender, S)
+ end.
+
+%%%
+%%% Fill send queue
+%%%
+fill_sendq(Config) when is_list(Config) ->
+ ?line Master = self(),
+ ?line Server =
+ spawn_link(fun () ->
+ {ok,L} = gen_tcp:listen
+ (0, [{active,false},binary,
+ {reuseaddr,true},{packet,0}]),
+ {ok,Port} = inet:port(L),
+ Master ! {self(),client,
+ fill_sendq_client(Port, Master)},
+ fill_sendq_srv(L, Master)
+ end),
+ ?line io:format("~p Server~n", [Server]),
+ ?line receive {Server,client,Client} ->
+ ?line io:format("~p Client~n", [Client]),
+ ?line receive {Server,reader,Reader} ->
+ ?line io:format("~p Reader~n", [Reader]),
+ ?line fill_sendq_loop(Server, Client, Reader)
+ end
+ end.
+
+fill_sendq_loop(Server, Client, Reader) ->
+ %% Master
+ %%
+ receive {Server,send} ->
+ fill_sendq_loop(Server, Client, Reader)
+ after 2000 ->
+ %% Send queue full, sender blocked -> close client.
+ ?line io:format("Send timeout, closing Client...~n", []),
+ ?line Client ! {self(),close},
+ ?line receive {Server,[{error,closed}]} ->
+ ?line io:format("Got server closed.~n"),
+ ?line receive {Reader,[{error,closed}]} ->
+ ?line io:format
+ ("Got reader closed.~n"),
+ ok
+ after 3000 ->
+ ?t:fail({timeout,{closed,reader}})
+ end;
+ {Reader,[{error,closed}]} ->
+ ?line io:format("Got reader closed.~n"),
+ ?line receive {Server,[{error,closed}]} ->
+ ?line io:format("Got server closed~n"),
+ ok
+ after 3000 ->
+ ?t:fail({timeout,{closed,server}})
+ end
+ after 3000 ->
+ ?t:fail({timeout,{closed,[server,reader]}})
+ end
+ end.
+
+fill_sendq_srv(L, Master) ->
+ %% Server
+ %%
+ case gen_tcp:accept(L) of
+ {ok,S} ->
+ Master ! {self(),reader,
+ spawn_link(fun () -> fill_sendq_read(S, Master) end)},
+ Msg = "the quick brown fox jumps over a lazy dog~n",
+ fill_sendq_write(S, Master, [Msg,Msg,Msg,Msg,Msg,Msg,Msg,Msg]);
+ Error ->
+ io:format("~p error: ~p.~n", [self(),Error]),
+ Master ! {self(),flush([Error])}
+ end.
+
+fill_sendq_write(S, Master, Msg) ->
+ %% Server
+ %%
+ %%io:format("~p sending...~n", [self()]),
+ Master ! {self(),send},
+ case gen_tcp:send(S, Msg) of
+ ok ->
+ %%io:format("~p ok.~n", [self()]),
+ fill_sendq_write(S, Master, Msg);
+ E ->
+ Error = flush([E]),
+ io:format("~p error: ~p.~n", [self(),Error]),
+ Master ! {self(),Error}
+ end.
+
+fill_sendq_read(S, Master) ->
+ %% Reader
+ %%
+ io:format("~p read infinity...~n", [self()]),
+ case gen_tcp:recv(S, 0, infinity) of
+ {ok,Data} ->
+ io:format("~p got: ~p.~n", [self(),Data]),
+ fill_sendq_read(S, Master);
+ E ->
+ Error = flush([E]),
+ io:format("~p error: ~p.~n", [self(),Error]),
+ Master ! {self(),Error}
+ end.
+
+fill_sendq_client(Port, Master) ->
+ %% Client
+ %%
+ spawn_link(fun () ->
+ %% Just close on order
+ {ok,S} = gen_tcp:connect(
+ "localhost", Port,
+ [{active,false},binary,{packet,0}]),
+ receive
+ {Master,close} ->
+ ok = gen_tcp:close(S)
+ end
+ end).
+
+%%% Try to receive more than available number of bytes from
+%%% a closed socket.
+%%%
+partial_recv_and_close(Config) when is_list(Config) ->
+ ?line Msg = "the quick brown fox jumps over a lazy dog 0123456789\n",
+ ?line Len = length(Msg),
+ ?line {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ ?line {ok,P} = inet:port(L),
+ ?line {ok,S} = gen_tcp:connect("localhost", P, [{active,false}]),
+ ?line {ok,A} = gen_tcp:accept(L),
+ ?line ok = gen_tcp:send(S, Msg),
+ ?line ok = gen_tcp:close(S),
+ ?line {error,closed} = gen_tcp:recv(A, Len+1),
+ ok.
+
+%%% Try to receive more than available number of bytes from
+%%% a closed socket, this time waiting in the recv before closing.
+%%%
+partial_recv_and_close_2(Config) when is_list(Config) ->
+ ?line Msg = "the quick brown fox jumps over a lazy dog 0123456789\n",
+ ?line Len = length(Msg),
+ ?line {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ ?line {ok,P} = inet:port(L),
+ ?line Server = self(),
+ ?line Client =
+ spawn_link(
+ fun () ->
+ receive after 2000 -> ok end,
+ {ok,S} = gen_tcp:connect("localhost", P, [{active,false}]),
+ ?line ok = gen_tcp:send(S, Msg),
+ receive {Server,close} -> ok end,
+ receive after 2000 -> ok end,
+ ?line ok = gen_tcp:close(S)
+ end),
+ ?line {ok,A} = gen_tcp:accept(L),
+ ?line Client ! {Server,close},
+ ?line {error,closed} = gen_tcp:recv(A, Len+1),
+ ok.
+
+%%% Here we tests that gen_tcp:recv/2 will return {error,closed} following
+%%% a send operation of a huge amount data when the other end closed the socket.
+%%%
+partial_recv_and_close_3(Config) when is_list(Config) ->
+ [do_partial_recv_and_close_3() || _ <- lists:seq(0, 20)],
+ ok.
+
+do_partial_recv_and_close_3() ->
+ Parent = self(),
+ spawn_link(fun() ->
+ {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ {ok,{_,Port}} = inet:sockname(L),
+ Parent ! {port,Port},
+ {ok,S} = gen_tcp:accept(L),
+ gen_tcp:recv(S, 1),
+ gen_tcp:close(S)
+ end),
+ receive
+ {port,Port} -> ok
+ end,
+ ?line Much = ones(8*64*1024),
+ ?line {ok,S} = gen_tcp:connect(localhost, Port, [{active,false}]),
+
+ %% Send a lot of data (most of it will be queued). The receiver will read one byte
+ %% and close the connection. The write operation will fail.
+ ?line gen_tcp:send(S, Much),
+
+ %% We should always get {error,closed} here.
+ ?line {error,closed} = gen_tcp:recv(S, 0).
+
+
+test_prio_put_get() ->
+ Tos = 3 bsl 5,
+ ?line {ok,L1} = gen_tcp:listen(0, [{active,false}]),
+ ?line ok = inet:setopts(L1,[{priority,3}]),
+ ?line ok = inet:setopts(L1,[{tos,Tos}]),
+ ?line {ok,[{priority,3},{tos,Tos}]} = inet:getopts(L1,[priority,tos]),
+ ?line ok = inet:setopts(L1,[{priority,3}]), % Dont destroy each other
+ ?line {ok,[{priority,3},{tos,Tos}]} = inet:getopts(L1,[priority,tos]),
+ ?line ok = inet:setopts(L1,[{reuseaddr,true}]), % Dont let others destroy
+ ?line {ok,[{priority,3},{tos,Tos}]} = inet:getopts(L1,[priority,tos]),
+ ?line gen_tcp:close(L1),
+ ok.
+test_prio_accept() ->
+ ?line {ok,Sock}=gen_tcp:listen(0,[binary,{packet,0},{active,false},
+ {reuseaddr,true},{priority,4}]),
+ ?line {ok,Port} = inet:port(Sock),
+ ?line {ok,Sock2}=gen_tcp:connect("localhost",Port,[binary,{packet,0},
+ {active,false},
+ {reuseaddr,true},
+ {priority,4}]),
+ ?line {ok,Sock3}=gen_tcp:accept(Sock),
+ ?line {ok,[{priority,4}]} = inet:getopts(Sock,[priority]),
+ ?line {ok,[{priority,4}]} = inet:getopts(Sock2,[priority]),
+ ?line {ok,[{priority,4}]} = inet:getopts(Sock3,[priority]),
+ ?line gen_tcp:close(Sock),
+ ?line gen_tcp:close(Sock2),
+ ?line gen_tcp:close(Sock3),
+ ok.
+
+test_prio_accept2() ->
+ Tos1 = 4 bsl 5,
+ Tos2 = 3 bsl 5,
+ ?line {ok,Sock}=gen_tcp:listen(0,[binary,{packet,0},{active,false},
+ {reuseaddr,true},{priority,4},
+ {tos,Tos1}]),
+ ?line {ok,Port} = inet:port(Sock),
+ ?line {ok,Sock2}=gen_tcp:connect("localhost",Port,[binary,{packet,0},
+ {active,false},
+ {reuseaddr,true},
+ {priority,4},
+ {tos,Tos2}]),
+ ?line {ok,Sock3}=gen_tcp:accept(Sock),
+ ?line {ok,[{priority,4},{tos,Tos1}]} = inet:getopts(Sock,[priority,tos]),
+ ?line {ok,[{priority,4},{tos,Tos2}]} = inet:getopts(Sock2,[priority,tos]),
+ ?line {ok,[{priority,4},{tos,Tos1}]} = inet:getopts(Sock3,[priority,tos]),
+ ?line gen_tcp:close(Sock),
+ ?line gen_tcp:close(Sock2),
+ ?line gen_tcp:close(Sock3),
+ ok.
+
+test_prio_accept3() ->
+ Tos1 = 4 bsl 5,
+ Tos2 = 3 bsl 5,
+ ?line {ok,Sock}=gen_tcp:listen(0,[binary,{packet,0},{active,false},
+ {reuseaddr,true},
+ {tos,Tos1}]),
+ ?line {ok,Port} = inet:port(Sock),
+ ?line {ok,Sock2}=gen_tcp:connect("localhost",Port,[binary,{packet,0},
+ {active,false},
+ {reuseaddr,true},
+ {tos,Tos2}]),
+ ?line {ok,Sock3}=gen_tcp:accept(Sock),
+ ?line {ok,[{priority,0},{tos,Tos1}]} = inet:getopts(Sock,[priority,tos]),
+ ?line {ok,[{priority,0},{tos,Tos2}]} = inet:getopts(Sock2,[priority,tos]),
+ ?line {ok,[{priority,0},{tos,Tos1}]} = inet:getopts(Sock3,[priority,tos]),
+ ?line gen_tcp:close(Sock),
+ ?line gen_tcp:close(Sock2),
+ ?line gen_tcp:close(Sock3),
+ ok.
+
+test_prio_accept_async() ->
+ Tos1 = 4 bsl 5,
+ Tos2 = 3 bsl 5,
+ Ref = make_ref(),
+ ?line spawn(?MODULE,priority_server,[{self(),Ref}]),
+ ?line Port = receive
+ {Ref,P} -> P
+ after 5000 -> ?t:fail({error,"helper process timeout"})
+ end,
+ ?line receive
+ after 3000 -> ok
+ end,
+ ?line {ok,Sock2}=gen_tcp:connect("localhost",Port,[binary,{packet,0},
+ {active,false},
+ {reuseaddr,true},
+ {priority,4},
+ {tos,Tos2}]),
+ ?line receive
+ {Ref,{ok,[{priority,4},{tos,Tos1}]}} ->
+ ok ;
+ {Ref,Error} ->
+ ?t:fail({missmatch,Error})
+ after 5000 -> ?t:fail({error,"helper process timeout"})
+ end,
+ ?line receive
+ {Ref,{ok,[{priority,4},{tos,Tos1}]}} ->
+ ok ;
+ {Ref,Error2} ->
+ ?t:fail({missmatch,Error2})
+ after 5000 -> ?t:fail({error,"helper process timeout"})
+ end,
+
+ ?line {ok,[{priority,4},{tos,Tos2}]} = inet:getopts(Sock2,[priority,tos]),
+ ?line catch gen_tcp:close(Sock2),
+ ok.
+
+priority_server({Parent,Ref}) ->
+ Tos1 = 4 bsl 5,
+ ?line {ok,Sock}=gen_tcp:listen(0,[binary,{packet,0},{active,false},
+ {reuseaddr,true},{priority,4},
+ {tos,Tos1}]),
+ ?line {ok,Port} = inet:port(Sock),
+ Parent ! {Ref,Port},
+ ?line {ok,Sock3}=gen_tcp:accept(Sock),
+ Parent ! {Ref, inet:getopts(Sock,[priority,tos])},
+ Parent ! {Ref, inet:getopts(Sock3,[priority,tos])},
+ ok.
+
+test_prio_fail() ->
+ ?line {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ ?line {error,_} = inet:setopts(L,[{priority,1000}]),
+% This error could only happen in linux kernels earlier than 2.6.24.4
+% Privilege check is now disabled and IP_TOS can never fail (only silently
+% be masked).
+% ?line {error,_} = inet:setopts(L,[{tos,6 bsl 5}]),
+ ?line gen_tcp:close(L),
+ ok.
+
+test_prio_udp() ->
+ Tos = 3 bsl 5,
+ ?line {ok,S} = gen_udp:open(0,[{active,false},binary,{tos, Tos},
+ {priority,3}]),
+ ?line {ok,[{priority,3},{tos,Tos}]} = inet:getopts(S,[priority,tos]),
+ ?line gen_udp:close(S),
+ ok.
+
+so_priority(doc) ->
+ ["Tests the so_priority and ip_tos options on sockets when applicable."];
+so_priority(suite) ->
+ [];
+so_priority(Config) when is_list(Config) ->
+ ?line {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ ?line ok = inet:setopts(L,[{priority,1}]),
+ ?line case inet:getopts(L,[priority]) of
+ {ok,[{priority,1}]} ->
+ gen_tcp:close(L),
+ test_prio_put_get(),
+ test_prio_accept(),
+ test_prio_accept2(),
+ test_prio_accept3(),
+ test_prio_accept_async(),
+ test_prio_fail(),
+ test_prio_udp(),
+ ok;
+ _ ->
+ case os:type() of
+ {unix,linux} ->
+ case os:version() of
+ {X,Y,_} when (X > 2) or ((X =:= 2) and (Y >= 4)) ->
+ ?line ?t:fail({error,
+ "so_priority should work on this "
+ "OS, but does not"});
+ _ ->
+ {skip, "SO_PRIORITY not suppoorted"}
+ end;
+ _ ->
+ {skip, "SO_PRIORITY not suppoorted"}
+ end
+ end.
+
+%% Accept test utilities (suites are below)
+
+millis() ->
+ {A,B,C}=erlang:now(),
+ (A*1000000*1000)+(B*1000)+(C div 1000).
+
+collect_accepts(Tmo) ->
+ A = millis(),
+ receive
+ {accepted,P,Msg} ->
+ [{P,Msg}] ++ collect_accepts(Tmo-(millis() - A))
+ after Tmo ->
+ []
+ end.
+
+-define(EXPECT_ACCEPTS(Pattern,Timeout),
+ (fun() ->
+ case collect_accepts(Timeout) of
+ Pattern ->
+ ok;
+ Other ->
+ {error,{unexpected,{Other,process_info(self(),messages)}}}
+ end
+ end)()).
+
+collect_connects(Tmo) ->
+ A = millis(),
+ receive
+ {connected,P,Msg} ->
+ [{P,Msg}] ++ collect_connects(Tmo-(millis() - A))
+ after Tmo ->
+ []
+ end.
+
+-define(EXPECT_CONNECTS(Pattern,Timeout),
+ (fun() ->
+ case collect_connects(Timeout) of
+ Pattern ->
+ ok;
+ Other ->
+ {error,{unexpected,Other}}
+ end
+ end)()).
+
+mktmofun(Tmo,Parent,LS) ->
+ fun() -> Parent ! {accepted,self(), catch gen_tcp:accept(LS,Tmo)} end.
+
+%% Accept tests
+primitive_accept(suite) ->
+ [];
+primitive_accept(doc) ->
+ ["Test singular accept"];
+primitive_accept(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line {ok,PortNo}=inet:port(LS),
+ ?line Parent = self(),
+ ?line F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
+ ?line P = spawn(F),
+ ?line gen_tcp:connect("localhost",PortNo,[]),
+ ?line receive
+ {accepted,P,{ok,P0}} when is_port(P0) ->
+ ok;
+ {accepted,P,Other0} ->
+ {error,Other0}
+ after 500 ->
+ {error,timeout}
+ end.
+
+
+multi_accept_close_listen(suite) ->
+ [];
+multi_accept_close_listen(doc) ->
+ ["Closing listen socket when multi-accepting"];
+multi_accept_close_listen(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Parent = self(),
+ ?line F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
+ ?line spawn(F),
+ ?line spawn(F),
+ ?line spawn(F),
+ ?line spawn(F),
+ ?line gen_tcp:close(LS),
+ ?line ?EXPECT_ACCEPTS([{_,{error,closed}},{_,{error,closed}},
+ {_,{error,closed}},{_,{error,closed}}], 500).
+
+accept_timeout(suite) ->
+ [];
+accept_timeout(doc) ->
+ ["Single accept with timeout"];
+accept_timeout(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Parent = self(),
+ ?line F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS,1000)} end,
+ ?line P = spawn(F),
+ ?line ?EXPECT_ACCEPTS([{P,{error,timeout}}],2000).
+
+accept_timeouts_in_order(suite) ->
+ [];
+accept_timeouts_in_order(doc) ->
+ ["Check that multi-accept timeouts happen in the correct order"];
+accept_timeouts_in_order(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Parent = self(),
+ ?line P1 = spawn(mktmofun(1000,Parent,LS)),
+ ?line P2 = spawn(mktmofun(1200,Parent,LS)),
+ ?line P3 = spawn(mktmofun(1300,Parent,LS)),
+ ?line P4 = spawn(mktmofun(1400,Parent,LS)),
+ ?line ?EXPECT_ACCEPTS([{P1,{error,timeout}},{P2,{error,timeout}},
+ {P3,{error,timeout}},{P4,{error,timeout}}], 2000).
+
+accept_timeouts_in_order2(suite) ->
+ [];
+accept_timeouts_in_order2(doc) ->
+ ["Check that multi-accept timeouts happen in the correct order (more)"];
+accept_timeouts_in_order2(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Parent = self(),
+ ?line P1 = spawn(mktmofun(1400,Parent,LS)),
+ ?line P2 = spawn(mktmofun(1300,Parent,LS)),
+ ?line P3 = spawn(mktmofun(1200,Parent,LS)),
+ ?line P4 = spawn(mktmofun(1000,Parent,LS)),
+ ?line ?EXPECT_ACCEPTS([{P4,{error,timeout}},{P3,{error,timeout}},
+ {P2,{error,timeout}},{P1,{error,timeout}}], 2000).
+
+accept_timeouts_in_order3(suite) ->
+ [];
+accept_timeouts_in_order3(doc) ->
+ ["Check that multi-accept timeouts happen in the correct order (even more)"];
+accept_timeouts_in_order3(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Parent = self(),
+ ?line P1 = spawn(mktmofun(1200,Parent,LS)),
+ ?line P2 = spawn(mktmofun(1400,Parent,LS)),
+ ?line P3 = spawn(mktmofun(1300,Parent,LS)),
+ ?line P4 = spawn(mktmofun(1000,Parent,LS)),
+ ?line ?EXPECT_ACCEPTS([{P4,{error,timeout}},{P1,{error,timeout}},
+ {P3,{error,timeout}},{P2,{error,timeout}}], 2000).
+
+accept_timeouts_mixed(suite) ->
+ [];
+accept_timeouts_mixed(doc) ->
+ ["Check that multi-accept timeouts behave correctly when mixed with successful timeouts"];
+accept_timeouts_mixed(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Parent = self(),
+ ?line {ok,PortNo}=inet:port(LS),
+ ?line P1 = spawn(mktmofun(1000,Parent,LS)),
+ ?line wait_until_accepting(P1,500),
+ ?line P2 = spawn(mktmofun(2000,Parent,LS)),
+ ?line wait_until_accepting(P2,500),
+ ?line P3 = spawn(mktmofun(3000,Parent,LS)),
+ ?line wait_until_accepting(P3,500),
+ ?line P4 = spawn(mktmofun(4000,Parent,LS)),
+ ?line wait_until_accepting(P4,500),
+ ?line ok = ?EXPECT_ACCEPTS([{P1,{error,timeout}}],1500),
+ ?line {ok,_}=gen_tcp:connect("localhost",PortNo,[]),
+ ?line ok = ?EXPECT_ACCEPTS([{P2,{ok,Port0}}] when is_port(Port0),100),
+ ?line ok = ?EXPECT_ACCEPTS([{P3,{error,timeout}}],2000),
+ ?line gen_tcp:connect("localhost",PortNo,[]),
+ ?line ?EXPECT_ACCEPTS([{P4,{ok,Port1}}] when is_port(Port1),100).
+
+killing_acceptor(suite) ->
+ [];
+killing_acceptor(doc) ->
+ ["Check that single acceptor behaves as expected when killed"];
+killing_acceptor(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Pid = spawn(fun() -> erlang:display({accepted,self(),gen_tcp:accept(LS)}) end),
+ ?line receive after 100 ->
+ ok
+ end,
+ ?line {ok,L1} = prim_inet:getstatus(LS),
+ ?line true = lists:member(accepting, L1),
+ ?line exit(Pid,kill),
+ ?line receive after 100 ->
+ ok
+ end,
+ ?line {ok,L2} = prim_inet:getstatus(LS),
+ ?line false = lists:member(accepting, L2),
+ ok.
+
+killing_multi_acceptors(suite) ->
+ [];
+killing_multi_acceptors(doc) ->
+ ["Check that multi acceptors behaves as expected when killed"];
+killing_multi_acceptors(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Parent = self(),
+ ?line F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
+ ?line F2 = mktmofun(1000,Parent,LS),
+ ?line Pid = spawn(F),
+ ?line Pid2 = spawn(F2),
+ ?line receive after 100 ->
+ ok
+ end,
+ ?line {ok,L1} = prim_inet:getstatus(LS),
+ ?line true = lists:member(accepting, L1),
+ ?line exit(Pid,kill),
+ ?line receive after 100 ->
+ ok
+ end,
+ ?line {ok,L2} = prim_inet:getstatus(LS),
+ ?line true = lists:member(accepting, L2),
+ ?line ok = ?EXPECT_ACCEPTS([{Pid2,{error,timeout}}],1000),
+ ?line {ok,L3} = prim_inet:getstatus(LS),
+ ?line false = lists:member(accepting, L3),
+ ok.
+
+killing_multi_acceptors2(suite) ->
+ [];
+killing_multi_acceptors2(doc) ->
+ ["Check that multi acceptors behaves as expected when killed (more)"];
+killing_multi_acceptors2(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Parent = self(),
+ ?line {ok,PortNo}=inet:port(LS),
+ ?line F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
+ ?line F2 = mktmofun(1000,Parent,LS),
+ ?line Pid = spawn(F),
+ ?line Pid2 = spawn(F),
+ ?line receive after 100 ->
+ ok
+ end,
+ ?line {ok,L1} = prim_inet:getstatus(LS),
+ ?line true = lists:member(accepting, L1),
+ ?line exit(Pid,kill),
+ ?line receive after 100 ->
+ ok
+ end,
+ ?line {ok,L2} = prim_inet:getstatus(LS),
+ ?line true = lists:member(accepting, L2),
+ ?line exit(Pid2,kill),
+ ?line receive after 100 ->
+ ok
+ end,
+ ?line {ok,L3} = prim_inet:getstatus(LS),
+ ?line false = lists:member(accepting, L3),
+ ?line Pid3 = spawn(F2),
+ ?line receive after 100 ->
+ ok
+ end,
+ ?line {ok,L4} = prim_inet:getstatus(LS),
+ ?line true = lists:member(accepting, L4),
+ ?line gen_tcp:connect("localhost",PortNo,[]),
+ ?line ok = ?EXPECT_ACCEPTS([{Pid3,{ok,Port}}] when is_port(Port),100),
+ ?line {ok,L5} = prim_inet:getstatus(LS),
+ ?line false = lists:member(accepting, L5),
+ ok.
+
+several_accepts_in_one_go(suite) ->
+ [];
+several_accepts_in_one_go(doc) ->
+ ["checks that multi-accept works when more than one accept can be "
+ "done at once (wb test of inet_driver)"];
+several_accepts_in_one_go(Config) when is_list(Config) ->
+ ?line {ok,LS}=gen_tcp:listen(0,[]),
+ ?line Parent = self(),
+ ?line {ok,PortNo}=inet:port(LS),
+ ?line F1 = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
+ ?line F2 = fun() -> Parent ! {connected,self(),gen_tcp:connect("localhost",PortNo,[])} end,
+ ?line spawn(F1),
+ ?line spawn(F1),
+ ?line spawn(F1),
+ ?line spawn(F1),
+ ?line spawn(F1),
+ ?line spawn(F1),
+ ?line spawn(F1),
+ ?line spawn(F1),
+ ?line ok = ?EXPECT_ACCEPTS([],500),
+ ?line spawn(F2),
+ ?line spawn(F2),
+ ?line spawn(F2),
+ ?line spawn(F2),
+ ?line spawn(F2),
+ ?line spawn(F2),
+ ?line spawn(F2),
+ ?line spawn(F2),
+ ?line ok = ?EXPECT_ACCEPTS([{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}}],15000),
+ ?line ok = ?EXPECT_CONNECTS([{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}}],1000),
+ ok.
+
+
+flush(Msgs) ->
+ erlang:yield(),
+ receive Msg -> flush([Msg|Msgs])
+ after 0 -> lists:reverse(Msgs)
+ end.
+
+wait_until_accepting(Proc,0) ->
+ exit({timeout_waiting_for_accepting,Proc});
+wait_until_accepting(Proc,N) ->
+ case process_info(Proc,current_function) of
+ {current_function,{prim_inet,accept0,2}} ->
+ case process_info(Proc,status) of
+ {status,waiting} ->
+ ok;
+ _O1 ->
+ receive
+ after 5 ->
+ wait_until_accepting(Proc,N-1)
+ end
+ end;
+ _O2 ->
+ receive
+ after 5 ->
+ wait_until_accepting(Proc,N-1)
+ end
+ end.
+
+
+
+active_once_closed(suite) ->
+ [];
+active_once_closed(doc) ->
+ ["Check that active once and tcp_close messages behave as expected"];
+active_once_closed(Config) when is_list(Config) ->
+ (fun() ->
+ ?line {Loop,A} = setup_closed_ao(),
+ ?line Loop({{error,closed},{error,econnaborted}},
+ fun() -> gen_tcp:send(A,"Hello") end),
+ ?line ok = inet:setopts(A,[{active,once}]),
+ ?line ok = receive {tcp_closed, A} -> ok after 1000 -> error end,
+ ?line {error,einval} = inet:setopts(A,[{active,once}]),
+ ?line ok = receive {tcp_closed, A} -> error after 1000 -> ok end
+ end)(),
+ (fun() ->
+ ?line {Loop,A} = setup_closed_ao(),
+ ?line Loop({{error,closed},{error,econnaborted}},
+ fun() -> gen_tcp:send(A,"Hello") end),
+ ?line ok = inet:setopts(A,[{active,true}]),
+ ?line ok = receive {tcp_closed, A} -> ok after 1000 -> error end,
+ ?line {error,einval} = inet:setopts(A,[{active,true}]),
+ ?line ok = receive {tcp_closed, A} -> error after 1000 -> ok end
+ end)(),
+ (fun() ->
+ ?line {Loop,A} = setup_closed_ao(),
+ ?line Loop({{error,closed},{error,econnaborted}},
+ fun() -> gen_tcp:send(A,"Hello") end),
+ ?line ok = inet:setopts(A,[{active,true}]),
+ ?line ok = receive {tcp_closed, A} -> ok after 1000 -> error end,
+ ?line {error,einval} = inet:setopts(A,[{active,once}]),
+ ?line ok = receive {tcp_closed, A} -> error after 1000 -> ok end
+ end)(),
+ (fun() ->
+ ?line {Loop,A} = setup_closed_ao(),
+ ?line Loop({{error,closed},{error,econnaborted}},
+ fun() -> gen_tcp:send(A,"Hello") end),
+ ?line ok = inet:setopts(A,[{active,once}]),
+ ?line ok = receive {tcp_closed, A} -> ok after 1000 -> error end,
+ ?line {error,einval} = inet:setopts(A,[{active,true}]),
+ ?line ok = receive {tcp_closed, A} -> error after 1000 -> ok end
+ end)(),
+ (fun() ->
+ ?line {Loop,A} = setup_closed_ao(),
+ ?line Loop({{error,closed},{error,econnaborted}},
+ fun() -> gen_tcp:send(A,"Hello") end),
+ ?line ok = inet:setopts(A,[{active,false}]),
+ ?line ok = receive {tcp_closed, A} -> error after 1000 -> ok end,
+ ?line ok = inet:setopts(A,[{active,once}]),
+ ?line ok = receive {tcp_closed, A} -> ok after 1000 -> error end
+ end)().
+
+send_timeout(suite) ->
+ [];
+send_timeout(doc) ->
+ ["Test the send_timeout socket option"];
+send_timeout(Config) when is_list(Config) ->
+ %% Basic
+ BasicFun =
+ fun(AutoClose) ->
+ ?line {Loop,A,RNode} = setup_timeout_sink(1000, AutoClose),
+ ?line {error,timeout} =
+ Loop(fun() ->
+ Res = gen_tcp:send(A,<<1:10000>>),
+ %%erlang:display(Res),
+ Res
+ end),
+ %% Check that the socket is not busy/closed...
+ Error = after_send_timeout(AutoClose),
+ ?line {error,Error} = gen_tcp:send(A,<<"Hej">>),
+ ?line test_server:stop_node(RNode)
+ end,
+ BasicFun(false),
+ BasicFun(true),
+ %% Check timeout length
+ ?line Self = self(),
+ ?line Pid =
+ spawn(fun() ->
+ {Loop,A,RNode} = setup_timeout_sink(1000, true),
+ {error,timeout} =
+ Loop(fun() ->
+ Res = gen_tcp:send(A,<<1:10000>>),
+ %%erlang:display(Res),
+ Self ! Res,
+ Res
+ end),
+ test_server:stop_node(RNode)
+ end),
+ ?line Diff = get_max_diff(),
+ ?line io:format("Max time for send: ~p~n",[Diff]),
+ ?line true = (Diff > 500) and (Diff < 1500),
+ %% Let test_server slave die...
+ ?line Mon = erlang:monitor(process, Pid),
+ ?line receive {'DOWN',Mon,process,Pid,_} -> ok end,
+ %% Check that parallell writers do not hang forever
+ ParaFun =
+ fun(AutoClose) ->
+ ?line {Loop,A,RNode} = setup_timeout_sink(1000, AutoClose),
+ SenderFun = fun() ->
+ {error,Error} =
+ Loop(fun() ->
+ gen_tcp:send(A, <<1:10000>>)
+ end),
+ Self ! {error,Error}
+ end,
+ ?line spawn_link(SenderFun),
+ ?line spawn_link(SenderFun),
+ ?line receive
+ {error,timeout} -> ok
+ after 10000 ->
+ ?line exit(timeout)
+ end,
+ NextErr = after_send_timeout(AutoClose),
+ ?line receive
+ {error,NextErr} -> ok
+ after 10000 ->
+ ?line exit(timeout)
+ end,
+ ?line {error,NextErr} = gen_tcp:send(A,<<"Hej">>),
+ ?line test_server:stop_node(RNode)
+ end,
+ ParaFun(false),
+ ParaFun(true),
+ ok.
+
+after_send_timeout(AutoClose) ->
+ case AutoClose of
+ true -> enotconn;
+ false -> timeout
+ end.
+
+get_max_diff() ->
+ receive
+ ok ->
+ get_max_diff(0)
+ after 10000 ->
+ exit(timeout)
+ end.
+
+get_max_diff(Max) ->
+ T1 = millistamp(),
+ receive
+ ok ->
+ Diff = millistamp() - T1,
+ if
+ Diff > Max ->
+ get_max_diff(Diff);
+ true ->
+ get_max_diff(Max)
+ end;
+ {error,timeout} ->
+ Diff = millistamp() - T1,
+ if
+ Diff > Max ->
+ Diff;
+ true ->
+ Max
+ end
+ after 10000 ->
+ exit(timeout)
+ end.
+
+setup_closed_ao() ->
+ Dir = filename:dirname(code:which(?MODULE)),
+ {ok,R} = test_server:start_node(test_default_options_slave,slave,
+ [{args,"-pa " ++ Dir}]),
+ Host = list_to_atom(lists:nth(2,string:tokens(atom_to_list(node()),"@"))),
+ {ok, L} = gen_tcp:listen(0, [{active,false},{packet,2}]),
+ Fun = fun(F) ->
+ receive
+ {From,X} when is_function(X) ->
+ From ! {self(),X()}, F(F);
+ die -> ok
+ end
+ end,
+ Pid = rpc:call(R,erlang,spawn,[fun() -> Fun(Fun) end]),
+ {ok, Port} = inet:port(L),
+ Remote = fun(Fu) ->
+ Pid ! {self(), Fu},
+ receive {Pid,X} -> X
+ end
+ end,
+ {ok, C} = Remote(fun() ->
+ gen_tcp:connect(Host,Port,
+ [{active,false},{packet,2}])
+ end),
+ {ok,A} = gen_tcp:accept(L),
+ gen_tcp:send(A,"Hello"),
+ {ok, "Hello"} = Remote(fun() -> gen_tcp:recv(C,0) end),
+ ok = Remote(fun() -> gen_tcp:close(C) end),
+ Loop2 = fun(_,_,_,0) ->
+ {failure, timeout};
+ (L2,{MA,MB},F2,N) ->
+ case F2() of
+ MA -> MA;
+ MB -> MB;
+ Other -> io:format("~p~n",[Other]),
+ receive after 1000 -> ok end,
+ L2(L2,{MA,MB},F2,N-1)
+ end
+ end,
+ Loop = fun(Match2,F3) -> Loop2(Loop2,Match2,F3,10) end,
+ test_server:stop_node(R),
+ {Loop,A}.
+
+setup_timeout_sink(Timeout, AutoClose) ->
+ Dir = filename:dirname(code:which(?MODULE)),
+ {ok,R} = test_server:start_node(test_default_options_slave,slave,
+ [{args,"-pa " ++ Dir}]),
+ Host = list_to_atom(lists:nth(2,string:tokens(atom_to_list(node()),"@"))),
+ {ok, L} = gen_tcp:listen(0, [{active,false},{packet,2},
+ {send_timeout,Timeout},
+ {send_timeout_close,AutoClose}]),
+ Fun = fun(F) ->
+ receive
+ {From,X} when is_function(X) ->
+ From ! {self(),X()}, F(F);
+ die -> ok
+ end
+ end,
+ Pid = rpc:call(R,erlang,spawn,[fun() -> Fun(Fun) end]),
+ {ok, Port} = inet:port(L),
+ Remote = fun(Fu) ->
+ Pid ! {self(), Fu},
+ receive {Pid,X} -> X
+ end
+ end,
+ {ok, C} = Remote(fun() ->
+ gen_tcp:connect(Host,Port,
+ [{active,false},{packet,2}])
+ end),
+ {ok,A} = gen_tcp:accept(L),
+ gen_tcp:send(A,"Hello"),
+ {ok, "Hello"} = Remote(fun() -> gen_tcp:recv(C,0) end),
+ Loop2 = fun(_,_,0) ->
+ {failure, timeout};
+ (L2,F2,N) ->
+ Ret = F2(),
+ io:format("~p~n",[Ret]),
+ case Ret of
+ ok -> receive after 1 -> ok end,
+ L2(L2,F2,N-1);
+ Other -> Other
+ end
+ end,
+ Loop = fun(F3) -> Loop2(Loop2,F3,1000) end,
+ {Loop,A,R}.
+
+millistamp() ->
+ {Mega, Secs, Micros} = erlang:now(),
+ (Micros div 1000) + Secs * 1000 + Mega * 1000000000.
+
+has_superfluous_schedulers() ->
+ case {erlang:system_info(schedulers),
+ erlang:system_info(logical_processors)} of
+ {S, unknown} when S > 1 -> true;
+ {S, P} when S > P -> true;
+ _ -> false
+ end.
+
+
+otp_7731(suite) -> [];
+otp_7731(doc) ->
+ "Leaking message from inet_drv {inet_reply,P,ok} "
+ "when a socket sending resumes working after a send_timeout";
+otp_7731(Config) when is_list(Config) ->
+ ?line ServerPid = spawn_link(?MODULE, otp_7731_server, [self()]),
+ ?line receive {ServerPid, ready, PortNum} -> ok end,
+
+ ?line {ok, Socket} = gen_tcp:connect("localhost", PortNum,
+ [binary, {active, false}, {packet, raw},
+ {send_timeout, 1000}]),
+ otp_7731_send(Socket),
+ io:format("Sending complete...\n",[]),
+ ServerPid ! {self(), recv},
+ receive {ServerPid, ok} -> ok end,
+
+ io:format("Client waiting for leaking messages...\n",[]),
+
+ %% Now make sure inet_drv does not leak any internal messages.
+ receive Msg ->
+ ?line test_server:fail({unexpected, Msg})
+ after 1000 ->
+ ok
+ end,
+ io:format("No leaking messages. Done.\n",[]),
+ gen_tcp:close(Socket).
+
+otp_7731_send(Socket) ->
+ Bin = <<1:10000>>,
+ io:format("Client sending ~p bytes...\n",[size(Bin)]),
+ ?line case gen_tcp:send(Socket, Bin) of
+ ok -> otp_7731_send(Socket);
+ {error,timeout} -> ok
+ end.
+
+otp_7731_server(ClientPid) ->
+ ?line {ok, LSocket} = gen_tcp:listen(0, [binary, {packet, raw},
+ {active, false}]),
+ ?line {ok, {_, PortNum}} = inet:sockname(LSocket),
+ io:format("Listening on ~w with port number ~p\n", [LSocket, PortNum]),
+ ClientPid ! {self(), ready, PortNum},
+
+ {ok, CSocket} = gen_tcp:accept(LSocket),
+ gen_tcp:close(LSocket),
+
+ io:format("Server got connection, wait for recv order...\n",[]),
+
+ receive {ClientPid, recv} -> ok end,
+
+ io:format("Server start receiving...\n",[]),
+
+ otp_7731_recv(CSocket),
+
+ ClientPid ! {self(), ok},
+
+ io:format("Server finished, closing...\n",[]),
+ gen_tcp:close(CSocket).
+
+
+otp_7731_recv(Socket) ->
+ ?line case gen_tcp:recv(Socket, 0, 1000) of
+ {ok, Bin} ->
+ io:format("Server received ~p bytes\n",[size(Bin)]),
+ otp_7731_recv(Socket);
+ {error,timeout} ->
+ io:format("Server got receive timeout\n",[]),
+ ok
+ end.
+
+
+%% OTP-7615: TCP-ports hanging in CLOSING state when sending large
+%% buffer followed by a recv() that returns error due to closed
+%% connection.
+zombie_sockets(suite) -> [];
+zombie_sockets(doc) -> ["OTP-7615 Leaking closed ports."];
+zombie_sockets(Config) when is_list(Config) ->
+ register(zombie_collector,self()),
+ Calls = 10,
+ Server = spawn_link(?MODULE, zombie_server,[self(), Calls]),
+ ?line {Server, ready, PortNum} = receive Msg -> Msg end,
+ io:format("Ports before = ~p\n",[lists:sort(erlang:ports())]),
+ zombie_client_loop(Calls, PortNum),
+ Ports = lists:sort(zombie_collector(Calls,[])),
+ Server ! terminate,
+ io:format("Collected ports = ~p\n",[Ports]),
+ ?line [] = zombies_alive(Ports, 10),
+ timer:sleep(1000),
+ ok.
+
+zombie_client_loop(0, _) -> ok;
+zombie_client_loop(N, PortNum) when is_integer(PortNum) ->
+ ?line {ok, Socket} = gen_tcp:connect("localhost", PortNum,
+ [binary, {active, false}, {packet, raw}]),
+ ?line gen_tcp:close(Socket), % to make server recv fail
+ zombie_client_loop(N-1, PortNum).
+
+
+zombie_collector(0,Acc) ->
+ Acc;
+zombie_collector(N,Acc) ->
+ receive
+ {closed, Socket} ->
+ zombie_collector(N-1,[Socket|Acc]);
+ E ->
+ {unexpected, E, Acc}
+ end.
+
+zombies_alive(Ports, WaitSec) ->
+ Alive = lists:sort(erlang:ports()),
+ io:format("Alive = ~p\n",[Alive]),
+ Zombies = lists:filter(fun(P) -> lists:member(P, Alive) end, Ports),
+ case Zombies of
+ [] -> [];
+ _ ->
+ case WaitSec of
+ 0 -> Zombies;
+ _ -> timer:sleep(1000), % Wait some more for zombies to die
+ zombies_alive(Zombies, WaitSec-1)
+ end
+ end.
+
+zombie_server(Pid, Calls) ->
+ ?line {ok, LSocket} = gen_tcp:listen(0, [binary, {packet, raw},
+ {active, false}, {backlog, Calls}]),
+ ?line {ok, {_, PortNum}} = inet:sockname(LSocket),
+ io:format("Listening on ~w with port number ~p\n", [LSocket, PortNum]),
+ BigBin = list_to_binary(lists:duplicate(100*1024, 77)),
+ Pid ! {self(), ready, PortNum},
+ zombie_accept_loop(LSocket, BigBin, Calls),
+ ?line terminate = receive Msg -> Msg end.
+
+zombie_accept_loop(_, _, 0) ->
+ ok;
+zombie_accept_loop(Socket, BigBin, Calls) ->
+ ?line case gen_tcp:accept(Socket) of
+ {ok, NewSocket} ->
+ spawn_link(fun() -> zombie_serve_client(NewSocket, BigBin) end),
+ zombie_accept_loop(Socket, BigBin, Calls-1);
+ E ->
+ E
+ end.
+
+zombie_serve_client(Socket, Bin) ->
+ %%io:format("Got connection on ~p\n",[Socket]),
+ ?line gen_tcp:send(Socket, Bin),
+ %%io:format("Sent data, waiting for reply on ~p\n",[Socket]),
+ ?line case gen_tcp:recv(Socket, 4) of
+ {error,closed} -> ok;
+ {error,econnaborted} -> ok % may be returned on Windows
+ end,
+ %%io:format("Closing ~p\n",[Socket]),
+ ?line gen_tcp:close(Socket),
+ zombie_collector ! {closed, Socket}.
+
+
+
+otp_7816(suite) -> [];
+otp_7816(doc) ->
+ "Hanging send on windows when sending iolist with more than 16 binaries.";
+otp_7816(Config) when is_list(Config) ->
+ Client = self(),
+ ?line Server = spawn_link(fun()-> otp_7816_server(Client) end),
+ ?line receive {Server, ready, PortNum} -> ok end,
+
+ ?line {ok, Socket} = gen_tcp:connect("localhost", PortNum,
+ [binary, {active, false}, {packet, 4},
+ {send_timeout, 10}]),
+ %% We use the undocumented feature that sending can be resumed after
+ %% a send_timeout without any data loss if the peer starts to receive data.
+ %% Unless of course the 7816-bug is in affect, in which case the write event
+ %% for the socket is lost on windows and not all data is sent.
+
+ [otp_7816_send(Socket,18,BinSize,Server) || BinSize <- lists:seq(1000, 2000, 123)],
+
+ io:format("Sending complete...\n",[]),
+
+ ?line ok = gen_tcp:close(Socket),
+ Server ! {self(), closed},
+ ?line {Server, closed} = receive M -> M end.
+
+
+otp_7816_send(Socket, BinNr, BinSize, Server) ->
+ Data = lists:duplicate(BinNr, <<1:(BinSize*8)>>),
+ SentBytes = otp_7816_send_data(Socket, Data, 0) * BinNr * BinSize,
+ io:format("Client sent ~p bytes...\n",[SentBytes]),
+ Server ! {self(),recv,SentBytes},
+ ?line {Server, ok} = receive M -> M end.
+
+
+
+otp_7816_send_data(Socket, Data, Loops) ->
+ io:format("Client sending data...\n",[]),
+ case gen_tcp:send(Socket, Data) of
+ ok ->
+ otp_7816_send_data(Socket,Data, Loops+1);
+ {error,timeout} ->
+ Loops+1
+ end.
+
+
+otp_7816_server(Client) ->
+ ?line {ok, LSocket} = gen_tcp:listen(0, [binary, {packet, 4},
+ {active, false}]),
+ ?line {ok, {_, PortNum}} = inet:sockname(LSocket),
+ io:format("Listening on ~w with port number ~p\n", [LSocket, PortNum]),
+ Client ! {self(), ready, PortNum},
+
+ ?line {ok, CSocket} = gen_tcp:accept(LSocket),
+ io:format("Server got connection...\n",[]),
+ ?line gen_tcp:close(LSocket),
+
+ otp_7816_server_loop(CSocket),
+
+ io:format("Server terminating.\n",[]).
+
+
+otp_7816_server_loop(CSocket) ->
+ io:format("Server waiting for order...\n",[]),
+
+ receive
+ {Client, recv, RecvBytes} ->
+ io:format("Server start receiving...\n",[]),
+
+ ?line ok = otp_7816_recv(CSocket, RecvBytes),
+
+ Client ! {self(), ok},
+ otp_7816_server_loop(CSocket);
+
+ {Client, closed} ->
+ ?line {error, closed} = gen_tcp:recv(CSocket, 0, 1000),
+ Client ! {self(), closed}
+ end.
+
+
+otp_7816_recv(_, 0) ->
+ io:format("Server got all.\n",[]),
+ ok;
+otp_7816_recv(CSocket, BytesLeft) ->
+ ?line case gen_tcp:recv(CSocket, 0, 1000) of
+ {ok, Bin} when byte_size(Bin) =< BytesLeft ->
+ io:format("Server received ~p of ~p bytes.\n",[size(Bin), BytesLeft]),
+ otp_7816_recv(CSocket, BytesLeft - byte_size(Bin));
+ {error,timeout} ->
+ io:format("Server got receive timeout when expecting more data\n",[]),
+ error
+ end.
+
+otp_8102(doc) -> ["Receive a packet with a faulty packet header"];
+otp_8102(suite) -> [];
+otp_8102(Config) when is_list(Config) ->
+ ?line {ok, LSocket} = gen_tcp:listen(0, []),
+ ?line {ok, {_, PortNum}} = inet:sockname(LSocket),
+ io:format("Listening on ~w with port number ~p\n", [LSocket, PortNum]),
+
+ [otp_8102_do(LSocket, PortNum, otp_8102_packet(Type,Size))
+ || Size <- lists:seq(-10,-1),
+ Type <- [4, {cdr,big}, {cdr,little}]],
+
+ gen_tcp:close(LSocket),
+ ok.
+
+otp_8102_packet(4, Size) ->
+ {<<Size:32/big>>, 4};
+otp_8102_packet({cdr,big}, Size) ->
+ {<<"GIOP",0,0,0,0,Size:32/big>>, cdr};
+otp_8102_packet({cdr,little}, Size) ->
+ {<<"GIOP",0,0,1,0,Size:32/little>>, cdr}.
+
+otp_8102_do(LSocket, PortNum, {Bin,PType}) ->
+
+ io:format("Connect with packet option ~p ...\n",[PType]),
+ ?line {ok, RSocket} = gen_tcp:connect("localhost", PortNum, [binary,
+ {packet,PType},
+ {active,true}]),
+ ?line {ok, SSocket} = gen_tcp:accept(LSocket),
+
+ io:format("Got connection, sending ~p...\n",[Bin]),
+
+ ?line ok = gen_tcp:send(SSocket, Bin),
+
+ io:format("Sending complete...\n",[]),
+
+ ?line {tcp_error,RSocket,emsgsize} = receive M -> M end,
+
+ io:format("Got error msg, ok.\n",[]),
+ gen_tcp:close(SSocket),
+ gen_tcp:close(RSocket).
+