diff options
Diffstat (limited to 'lib/kernel/test/gen_tcp_misc_SUITE.erl')
-rw-r--r-- | lib/kernel/test/gen_tcp_misc_SUITE.erl | 2362 |
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). + |