aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMicael Karlberg <[email protected]>2018-11-22 14:55:13 +0100
committerMicael Karlberg <[email protected]>2018-11-22 14:55:13 +0100
commitc44851291637659ef096818eda4692c81ed052ae (patch)
treefdc1ee006ceeb74ad9bb89ea0803ac73b19e18e7
parent0182f3ddb6a0243444bd1ef279221e0acc010233 (diff)
parent03b6d593970e1783cdbb82729edb0ba27eb29ae4 (diff)
downloadotp-c44851291637659ef096818eda4692c81ed052ae.tar.gz
otp-c44851291637659ef096818eda4692c81ed052ae.tar.bz2
otp-c44851291637659ef096818eda4692c81ed052ae.zip
Merge branch 'bmk/20181115/nififying_inet_tests/OTP-14831' into bmk/20180918/nififying_inet/OTP-14831
-rw-r--r--erts/emulator/test/socket_SUITE.erl1493
1 files changed, 1339 insertions, 154 deletions
diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl
index 4a381843a9..f2aace40de 100644
--- a/erts/emulator/test/socket_SUITE.erl
+++ b/erts/emulator/test/socket_SUITE.erl
@@ -73,6 +73,7 @@
sc_cpe_socket_cleanup_tcp6/1,
sc_cpe_socket_cleanup_udp4/1,
sc_cpe_socket_cleanup_udp6/1,
+
sc_lc_recv_response_tcp4/1,
sc_lc_recv_response_tcp6/1,
sc_lc_recvfrom_response_udp4/1,
@@ -83,11 +84,17 @@
sc_lc_recvmsg_response_udp6/1,
sc_lc_acceptor_response_tcp4/1,
sc_lc_acceptor_response_tcp6/1,
+
sc_rc_recv_response_tcp4/1,
sc_rc_recv_response_tcp6/1,
sc_rc_recvmsg_response_tcp4/1,
sc_rc_recvmsg_response_tcp6/1,
+ sc_rs_recv_send_shutdown_receive_tcp4/1,
+ sc_rs_recv_send_shutdown_receive_tcp6/1,
+ sc_rs_recvmsg_send_shutdown_receive_tcp4/1,
+ sc_rs_recvmsg_send_shutdown_receive_tcp6/1,
+
%% Traffic
traffic_send_and_recv_chunks_tcp4/1,
traffic_send_and_recv_chunks_tcp6/1,
@@ -131,6 +138,7 @@
-define(BASIC_REQ, <<"hejsan">>).
-define(BASIC_REP, <<"hoppsan">>).
+-define(DATA, <<"HOPPSAN">>). % Temporary
-define(FAIL(R), exit(R)).
-define(SLEEP(T), receive after T -> ok end).
@@ -174,6 +182,7 @@ groups() ->
{sc_ctrl_proc_exit, [], sc_cp_exit_cases()},
{sc_local_close, [], sc_lc_cases()},
{sc_remote_close, [], sc_rc_cases()},
+ {sc_remote_shutdown, [], sc_rs_cases()},
{traffic, [], traffic_cases()}
%% {tickets, [], ticket_cases()}
].
@@ -227,13 +236,14 @@ api_op_with_timeout_cases() ->
api_to_recvmsg_tcp6
].
-%% These cases tests what happens when the socket is closed, locally or
-%% remotely.
+%% These cases tests what happens when the socket is closed/shutdown,
+%% locally or remotely.
socket_closure_cases() ->
[
{group, sc_ctrl_proc_exit},
{group, sc_local_close},
- {group, sc_remote_close}
+ {group, sc_remote_close},
+ {group, sc_remote_shutdown}
].
%% These cases are all about socket cleanup after the controlling process
@@ -274,6 +284,17 @@ sc_rc_cases() ->
sc_rc_recvmsg_response_tcp6
].
+%% These cases tests what happens when the socket is shutdown/closed remotely
+%% after writing and reading is ongoing.
+sc_rs_cases() ->
+ [
+ sc_rs_recv_send_shutdown_receive_tcp4,
+ sc_rs_recv_send_shutdown_receive_tcp6,
+
+ sc_rs_recvmsg_send_shutdown_receive_tcp4,
+ sc_rs_recvmsg_send_shutdown_receive_tcp6
+ ].
+
traffic_cases() ->
[
@@ -512,9 +533,9 @@ api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) ->
%% type => tos,
%% data => reliability},
%% CMsgHdrs = [CMsgHdr],
- MsgHdr = #{addr => Dest,
- %% ctrl => CMsgHdrs,
- iov => [Data]},
+ MsgHdr = #{addr => Dest,
+ %% ctrl => CMsgHdrs,
+ iov => [Data]},
socket:sendmsg(Sock, MsgHdr)
end,
Recv = fun(Sock) ->
@@ -1571,7 +1592,10 @@ api_to_connect_tcp4(_Config) when is_list(_Config) ->
tc_try(api_to_connect_tcp4,
fun() ->
?TT(?SECS(10)),
- InitState = #{domain => inet, timeout => 5000},
+ InitState = #{domain => inet,
+ backlog => 1,
+ timeout => 5000,
+ connect_limit => 3},
ok = api_to_connect_tcp(InitState)
end).
@@ -1589,7 +1613,10 @@ api_to_connect_tcp6(_Config) when is_list(_Config) ->
fun() ->
not_yet_implemented(),
?TT(?SECS(10)),
- InitState = #{domain => inet6, timeout => 5000},
+ InitState = #{domain => inet6,
+ backlog => 1,
+ timeout => 5000,
+ connect_limit => 3},
ok = api_to_connect_tcp(InitState)
end).
@@ -1609,8 +1636,9 @@ api_to_connect_tcp(InitState) ->
%% *** Wait for start order part ***
#{desc => "await start (from tester)",
cmd => fun(State) ->
- Tester = ?SEV_AWAIT_START(),
- {ok, State#{tester => Tester}}
+ {Tester, Backlog} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ backlog => Backlog}}
end},
#{desc => "monitor tester",
cmd => fun(#{tester := Tester} = _State) ->
@@ -1644,8 +1672,8 @@ api_to_connect_tcp(InitState) ->
end
end},
#{desc => "make listen socket (with backlog = 1)",
- cmd => fun(#{lsock := LSock}) ->
- socket:listen(LSock, 1)
+ cmd => fun(#{lsock := LSock, backlog := Backlog}) ->
+ socket:listen(LSock, Backlog)
end},
#{desc => "announce ready (init)",
cmd => fun(#{tester := Tester, lport := Port}) ->
@@ -1675,79 +1703,178 @@ api_to_connect_tcp(InitState) ->
?SEV_FINISH_NORMAL
],
- TesterSeq =
+ ClientSeq =
[
- %% *** Init part ***
- #{desc => "monitor server",
- cmd => fun(#{server := Server} = _State) ->
- _MRef = erlang:monitor(process, Server),
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
ok
end},
+
+ %% *** Init part ***
#{desc => "which local address",
cmd => fun(#{domain := Domain} = State) ->
LAddr = which_local_addr(Domain),
LSA = #{family => Domain, addr => LAddr},
{ok, State#{local_sa => LSA}}
end},
- #{desc => "create socket 1",
- cmd => fun(#{domain := Domain} = State) ->
- case socket:open(Domain, stream, tcp) of
- {ok, Sock} ->
- {ok, State#{sock1 => Sock}};
- {error, _} = ERROR ->
- ERROR
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
end
end},
- #{desc => "create socket 2",
- cmd => fun(#{domain := Domain} = State) ->
- case socket:open(Domain, stream, tcp) of
- {ok, Sock} ->
- {ok, State#{sock2 => Sock}};
- {error, _} = ERROR ->
- ERROR
- end
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
end},
- #{desc => "create socket 3",
- cmd => fun(#{domain := Domain} = State) ->
- case socket:open(Domain, stream, tcp) of
- {ok, Sock} ->
- {ok, State#{sock3 => Sock}};
+ #{desc => "start remote client on client node",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = api_toc_tcp_client_start(Node),
+ ?SEV_IPRINT("remote client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client,
+ server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]) of
+ {ok, {ConTimeout, ConLimit}} ->
+ {ok, State#{connect_timeout => ConTimeout,
+ connect_limit => ConLimit}};
{error, _} = ERROR ->
ERROR
end
end},
- #{desc => "bind socket 1 to local address",
- cmd => fun(#{sock1 := Sock, local_sa := LSA} = _State) ->
- case socket:bind(Sock, LSA) of
- {ok, _} ->
- ok;
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := RClient,
+ connect_timeout := ConTimeout,
+ connect_limit := ConLimit}) ->
+ ?SEV_ANNOUNCE_CONTINUE(RClient, connect,
+ {ConTimeout, ConLimit}),
+ ok
+ end},
+ #{desc => "await remote client ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_READY(RClient, rclient, connect,
+ [{tester, Tester}]) of
+ {ok, ok = _Result} ->
+ {ok, maps:remove(connect_limit, State)};
+ {ok, Result} ->
+ Result;
{error, _} = ERROR ->
ERROR
end
end},
- #{desc => "bind socket 2 to local address",
- cmd => fun(#{sock2 := Sock, local_sa := LSA} = _State) ->
- case socket:bind(Sock, LSA) of
- {ok, _} ->
- ok;
+
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, RClient}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
{error, _} = ERROR ->
ERROR
end
end},
- #{desc => "bind socket 3 to local address",
- cmd => fun(#{sock3 := Sock, local_sa := LSA} = _State) ->
- case socket:bind(Sock, LSA) of
- {ok, _} ->
- ok;
- {error, _} = ERROR ->
- ERROR
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ State1 = maps:remove(node_id, State),
+ State2 = maps:remove(node, State1),
+ {ok, State2}
end
end},
- %% *** Synchronize with the server ***
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Server} = _State) ->
+ _MRef = erlang:monitor(process, Server),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Client} = _State) ->
+ _MRef = erlang:monitor(process, Client),
+ ok
+ end},
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
#{desc => "order server start",
- cmd => fun(#{server := Server}) ->
- ?SEV_ANNOUNCE_START(Server),
+ cmd => fun(#{server := Server,
+ backlog := Backlog}) ->
+ ?SEV_ANNOUNCE_START(Server, Backlog),
ok
end},
#{desc => "await server ready (init)",
@@ -1756,95 +1883,241 @@ api_to_connect_tcp(InitState) ->
ServerSA = LSA#{port => Port},
{ok, State#{server_sa => ServerSA}}
end},
+ #{desc => "order client start",
+ cmd => fun(#{client := Client,
+ server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, init),
+ ok
+ end},
- %% *** Connect sequence ***
- #{desc => "order (server) start",
- cmd => fun(#{sock1 := Sock1,
- sock2 := Sock2,
- sock3 := Sock3,
- server_sa := SSA,
- timeout := To}) ->
- Socks = [Sock1, Sock2, Sock3],
- api_to_connect_tcp_await_timeout(Socks, To, SSA)
+ %% The actual test
+ %% The server does nothing (this is the point), no accept,
+ %% the client tries to connect.
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Client,
+ timeout := Timeout,
+ connect_limit := ConLimit} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect,
+ {Timeout, ConLimit}),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}]) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
end},
+
%% *** Terminate server ***
- #{desc => "order (server) terminate",
+ #{desc => "order client terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client down",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server terminate",
cmd => fun(#{server := Server} = _State) ->
?SEV_ANNOUNCE_TERMINATE(Server),
ok
end},
- #{desc => "await (server) down",
+ #{desc => "await server down",
cmd => fun(#{server := Server} = State) ->
?SEV_AWAIT_TERMINATION(Server),
State1 = maps:remove(server, State),
State2 = maps:remove(server_sa, State1),
{ok, State2}
end},
- #{desc => "close socket 3",
- cmd => fun(#{sock3 := Sock} = State) ->
- sock_close(Sock),
- {ok, maps:remove(sock3, State)}
-
- end},
- #{desc => "close socket 2",
- cmd => fun(#{sock2 := Sock} = State) ->
- sock_close(Sock),
- {ok, maps:remove(sock2, State)}
-
- end},
- #{desc => "close socket 1",
- cmd => fun(#{sock1 := Sock} = State) ->
- sock_close(Sock),
- {ok, maps:remove(sock1, State)}
-
- end},
%% *** We are done ***
?SEV_FINISH_NORMAL
],
i("create server evaluator"),
- ServerInitState = InitState,
+ ServerInitState = #{domain => maps:get(domain, InitState)},
Server = ?SEV_START("server", ServerSeq, ServerInitState),
+ i("create client evaluator"),
+ ClientInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
i("create tester evaluator"),
- TesterInitState = InitState#{server => Server#ev.pid},
+ TesterInitState = InitState#{server => Server#ev.pid,
+ client => Client#ev.pid},
Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
i("await evaluator(s)"),
- ok = ?SEV_AWAIT_FINISH([Server, Tester]).
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+api_toc_tcp_client_start(Node) ->
+ Self = self(),
+ GL = group_leader(),
+ Fun = fun() -> api_toc_tcp_client(Self, GL) end,
+ erlang:spawn(Node, Fun).
+
+api_toc_tcp_client(Parent, GL) ->
+ api_toc_tcp_client_init(Parent, GL),
+ ServerSA = api_toc_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ api_toc_tcp_client_announce_ready(Parent, init),
+ {To, ConLimit} = api_toc_tcp_client_await_continue(Parent, connect),
+ Result = api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit),
+ api_toc_tcp_client_announce_ready(Parent, connect, Result),
+ Reason = sc_rs_tcp_client_await_terminate(Parent),
+ exit(Reason).
+api_toc_tcp_client_init(Parent, GL) ->
+ %% i("api_toc_tcp_client_init -> entry"),
+ _MRef = erlang:monitor(process, Parent),
+ group_leader(self(), GL),
+ ok.
+
+api_toc_tcp_client_await_start(Parent) ->
+ %% i("api_toc_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+api_toc_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+api_toc_tcp_client_announce_ready(Parent, Slogan, Result) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Result).
+
+api_toc_tcp_client_await_continue(Parent, Slogan) ->
+ %% i("api_toc_tcp_client_await_continue -> entry"),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ok;
+ {ok, Extra} ->
+ Extra;
+ {error, Reason} ->
+ exit({await_continue, Slogan, Reason})
+ end.
-api_to_connect_tcp_await_timeout(Socks, To, ServerSA) ->
- api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1).
+api_toc_tcp_client_await_terminate(Parent) ->
+ %% i("api_toc_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
-api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) ->
- ?FAIL(unexpected_success);
-api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) ->
- ?SEV_IPRINT("~w: try connect", [ID]),
+api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ NewSock = fun() ->
+ S = case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ Sock;
+ {error, OReason} ->
+ ?FAIL({open, OReason})
+ end,
+ case socket:bind(S, LSA) of
+ {ok, _} ->
+ S;
+ {error, BReason} ->
+ ?FAIL({bind, BReason})
+ end
+ end,
+ api_to_connect_tcp_await_timeout(1, ConLimit, To, ServerSA, NewSock, []).
+
+api_to_connect_tcp_await_timeout(ID, ConLimit, _To, _ServerSA, _NewSock, Acc)
+ when (ID > ConLimit) ->
+ api_to_connect_tcp_await_timeout3(Acc),
+ {error, {connect_limit_reached, ID, ConLimit}};
+api_to_connect_tcp_await_timeout(ID, ConLimit, To, ServerSA, NewSock, Acc) ->
+ case api_to_connect_tcp_await_timeout2(ID, To, ServerSA, NewSock) of
+ ok ->
+ %% ?SEV_IPRINT("success when number of socks: ~w", [length(Acc)]),
+ api_to_connect_tcp_await_timeout3(Acc),
+ ok;
+ {ok, Sock} ->
+ %% ?SEV_IPRINT("~w: unexpected success (connect)", [ID]),
+ api_to_connect_tcp_await_timeout(ID+1, ConLimit,
+ To, ServerSA, NewSock,
+ [Sock|Acc]);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+api_to_connect_tcp_await_timeout2(ID, To, ServerSA, NewSock) ->
+ Sock = NewSock(),
+ %% ?SEV_IPRINT("~w: try connect", [ID]),
Start = t(),
case socket:connect(Sock, ServerSA, To) of
{error, timeout} ->
- ?SEV_IPRINT("expected timeout (~w)", [ID]),
Stop = t(),
TDiff = tdiff(Start, Stop),
if
(TDiff >= To) ->
+ (catch socket:close(Sock)),
ok;
true ->
- {error, {unexpected_timeout, TDiff, To}}
+ (catch socket:close(Sock)),
+ ?FAIL({unexpected_timeout, TDiff, To})
end;
- {error, econnreset = Reason} ->
- ?SEV_IPRINT("failed connecting: ~p - giving up", [Reason]),
+ {error, econnreset = _Reason} ->
+ (catch socket:close(Sock)),
ok;
{error, Reason} ->
- ?SEV_EPRINT("failed connecting: ~p", [Reason]),
+ (catch socket:close(Sock)),
?FAIL({connect, Reason});
ok ->
- ?SEV_IPRINT("unexpected success (~w) - try next", [ID]),
- api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1)
+ {ok, Sock}
end.
+
+api_to_connect_tcp_await_timeout3([]) ->
+ ok;
+api_to_connect_tcp_await_timeout3([Sock|Socka]) ->
+ (catch socket:close(Sock)),
+ api_to_connect_tcp_await_timeout3(Socka).
+
+%% api_to_connect_tcp_await_timeout(Socks, To, ServerSA) ->
+%% api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1).
+
+%% api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) ->
+%% ?FAIL(unexpected_success);
+%% api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) ->
+%% ?SEV_IPRINT("~w: try connect", [ID]),
+%% Start = t(),
+%% case socket:connect(Sock, ServerSA, To) of
+%% {error, timeout} ->
+%% ?SEV_IPRINT("expected timeout (~w)", [ID]),
+%% Stop = t(),
+%% TDiff = tdiff(Start, Stop),
+%% if
+%% (TDiff >= To) ->
+%% ok;
+%% true ->
+%% {error, {unexpected_timeout, TDiff, To}}
+%% end;
+%% {error, econnreset = Reason} ->
+%% ?SEV_IPRINT("failed connecting: ~p - giving up", [Reason]),
+%% ok;
+%% {error, Reason} ->
+%% ?SEV_EPRINT("failed connecting: ~p", [Reason]),
+%% ?FAIL({connect, Reason});
+%% ok ->
+%% ?SEV_IPRINT("unexpected success (~w) - try next", [ID]),
+%% api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1)
+%% end.
@@ -4921,7 +5194,7 @@ sc_rc_receive_response_tcp(InitState) ->
ERROR
end
end},
- #{desc => "await handle 1 ready (init)",
+ #{desc => "await handler 1 ready (init)",
cmd => fun(#{tester := Tester,
handler1 := Handler1} = _State) ->
?SEV_AWAIT_READY(Handler1, handler1, init,
@@ -4940,7 +5213,7 @@ sc_rc_receive_response_tcp(InitState) ->
ERROR
end
end},
- #{desc => "await handle 2 ready (init)",
+ #{desc => "await handler 2 ready (init)",
cmd => fun(#{tester := Tester,
handler1 := Handler1,
handler2 := Handler2} = _State) ->
@@ -4961,7 +5234,7 @@ sc_rc_receive_response_tcp(InitState) ->
ERROR
end
end},
- #{desc => "await handle 3 ready (init)",
+ #{desc => "await handler 3 ready (init)",
cmd => fun(#{tester := Tester,
handler1 := Handler1,
handler2 := Handler2,
@@ -5030,7 +5303,7 @@ sc_rc_receive_response_tcp(InitState) ->
?SEV_ANNOUNCE_READY(Tester, recv_closed),
ok
end},
-
+
%% Termination
#{desc => "await terminate (from tester)",
cmd => fun(#{tester := Tester} = State) ->
@@ -5530,15 +5803,6 @@ sc_rc_receive_response_tcp(InitState) ->
Tester]).
-local_host() ->
- try net_adm:localhost() of
- Host when is_list(Host) ->
- list_to_atom(Host)
- catch
- C:E:S ->
- erlang:raise(C, E, S)
- end.
-
sc_rc_tcp_client_start(Node) ->
Self = self(),
GL = group_leader(),
@@ -5668,17 +5932,17 @@ sc_rc_tcp_handler_recv(Recv, Sock) ->
{error, unexpected_success};
{error, Reason} = ERROR ->
?SEV_IPRINT("receive error: "
- "~n ~p", [Reason]),
+ "~n ~p", [Reason]),
ERROR
catch
C:E:S ->
?SEV_IPRINT("receive failure: "
- "~n Class: ~p"
- "~n Error: ~p"
- "~n Stack: ~p", [C, E, S]),
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
{error, {recv, C, E, S}}
end.
-
+
sc_rc_tcp_handler_announce_ready(Parent, Slogan, Result) ->
?SEV_IPRINT("announce ready"),
?SEV_ANNOUNCE_READY(Parent, Slogan, Result),
@@ -5731,6 +5995,905 @@ sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv4.
+%%
+%% To minimize the chance of "weirdness", we should really have test cases
+%% where the two sides of the connection is on different machines. But for
+%% now, we will make do with different VMs on the same host.
+%%
+
+sc_rs_recv_send_shutdown_receive_tcp4(suite) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp4(doc) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recv_send_shutdown_receive_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv6.
+
+sc_rs_recv_send_shutdown_receive_tcp6(suite) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp6(doc) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recv_send_shutdown_receive_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_rs_send_shutdown_receive_tcp(InitState) ->
+ %% The connection is handled by a handler processes.
+ %% This are created (on the fly) and handled internally
+ %% by the server!
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ i("get local address for ~p", [Domain]),
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{lsock := LSock, recv := Recv} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: try start handler"),
+ Handler =
+ sc_rs_tcp_handler_start(Recv, Sock),
+ ?SEV_IPRINT("handler started"),
+ {ok, State#{csock => Sock,
+ handler => Handler}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await handler ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_READY(Handler, handler, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await continue (first recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "order handler to receive (first)",
+ cmd => fun(#{handler := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await ready from handler (first recv)",
+ cmd => fun(#{tester := Tester, handler := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ ?SEV_IPRINT("first recv: ~p", [Result]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (first recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+ #{desc => "await continue (second recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "order handler to receive (second)",
+ cmd => fun(#{handler := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await ready from handler (second recv)",
+ cmd => fun(#{tester := Tester, handler := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ ?SEV_IPRINT("second recv: ~p", [Result]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (second recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler to terminate",
+ cmd => fun(#{handler := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler termination",
+ cmd => fun(#{handler := Pid} = State) ->
+ ?SEV_AWAIT_TERMINATION(Pid),
+ State1 = maps:remove(csock, State),
+ State2 = maps:remove(handler, State1),
+ {ok, State2}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start remote client on client node",
+ cmd => fun(#{node := Node,
+ send := Send} = State) ->
+ Pid = sc_rs_tcp_client_start(Node, Send),
+ ?SEV_IPRINT("client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client, server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client process ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, connect,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ #{desc => "await continue (send)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, send,
+ [{rclient, Client}]) of
+ {ok, Data} ->
+ {ok, State#{rclient_data => Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to send",
+ cmd => fun(#{rclient := Client,
+ rclient_data := Data}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Data),
+ ok
+ end},
+ #{desc => "await remote client ready (closed)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+
+ #{desc => "await continue (shutdown)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, shutdown,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to shutdown",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, shutdown),
+ ok
+ end},
+ #{desc => "await remote client ready (shiutdown)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, shutdown,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (shutdown)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, shutdown),
+ ok
+ end},
+
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to close",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, close),
+ ok
+ end},
+ #{desc => "await remote client ready (closed)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, close,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, Client}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ State1 = maps:remove(node_id, State),
+ State2 = maps:remove(node, State1),
+ {ok, State2}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client(s)
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}]),
+ ok
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept,
+ [{client, Client}]),
+ ok
+ end},
+
+ #{desc => "order client continue (send)",
+ cmd => fun(#{client := Pid,
+ data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send, Data),
+ ok
+ end},
+ #{desc => "await client ready (send)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]),
+ ok
+ end},
+
+ #{desc => "order client continue (shutdown)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, shutdown),
+ ok
+ end},
+ #{desc => "await client ready (shutdown)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, shutdown,
+ [{server, Server}]),
+ ok
+ end},
+
+ #{desc => "order server continue (first recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (first recv)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]),
+ ok
+ end},
+
+ #{desc => "order server continue (second recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (second recv)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order client continue (close)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await client ready (close)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, close,
+ [{server, Server}]),
+ ok
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState),
+ recv => maps:get(recv, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator"),
+ ClientInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState),
+ send => maps:get(send, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid,
+ data => maps:get(data, InitState)},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+sc_rs_tcp_client_start(Node, Send) ->
+ Self = self(),
+ GL = group_leader(),
+ Fun = fun() -> sc_rs_tcp_client(Self, Send, GL) end,
+ erlang:spawn(Node, Fun).
+
+
+sc_rs_tcp_client(Parent, Send, GL) ->
+ sc_rs_tcp_client_init(Parent, GL),
+ ServerSA = sc_rs_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ Sock = sc_rs_tcp_client_create(Domain),
+ sc_rs_tcp_client_bind(Sock, Domain),
+ sc_rs_tcp_client_announce_ready(Parent, init),
+ sc_rs_tcp_client_await_continue(Parent, connect),
+ sc_rs_tcp_client_connect(Sock, ServerSA),
+ sc_rs_tcp_client_announce_ready(Parent, connect),
+ Data = sc_rs_tcp_client_await_continue(Parent, send),
+ sc_rs_tcp_client_send(Sock, Send, Data),
+ sc_rs_tcp_client_announce_ready(Parent, send),
+ sc_rs_tcp_client_await_continue(Parent, shutdown),
+ sc_rs_tcp_client_shutdown(Sock),
+ sc_rs_tcp_client_announce_ready(Parent, shutdown),
+ sc_rs_tcp_client_await_continue(Parent, close),
+ sc_rs_tcp_client_close(Sock),
+ sc_rs_tcp_client_announce_ready(Parent, close),
+ Reason = sc_rs_tcp_client_await_terminate(Parent),
+ exit(Reason).
+
+sc_rs_tcp_client_init(Parent, GL) ->
+ i("sc_rs_tcp_client_init -> entry"),
+ _MRef = erlang:monitor(process, Parent),
+ group_leader(self(), GL),
+ ok.
+
+sc_rs_tcp_client_await_start(Parent) ->
+ i("sc_rs_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+sc_rs_tcp_client_create(Domain) ->
+ i("sc_rs_tcp_client_create -> entry"),
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+sc_rs_tcp_client_bind(Sock, Domain) ->
+ i("sc_rs_tcp_client_bind -> entry"),
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+sc_rs_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+
+sc_rs_tcp_client_await_continue(Parent, Slogan) ->
+ i("sc_rs_tcp_client_await_continue -> entry"),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ok;
+ {ok, Extra} ->
+ Extra;
+ {error, Reason} ->
+ exit({await_continue, Slogan, Reason})
+ end.
+
+
+sc_rs_tcp_client_connect(Sock, ServerSA) ->
+ i("sc_rs_tcp_client_connect -> entry"),
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({connect, Reason})
+ end.
+
+sc_rs_tcp_client_send(Sock, Send, Data) ->
+ i("sc_rs_tcp_client_send -> entry"),
+ case Send(Sock, Data) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({send, Reason})
+ end.
+
+sc_rs_tcp_client_shutdown(Sock) ->
+ i("sc_rs_tcp_client_shutdown -> entry"),
+ case socket:shutdown(Sock, write) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({shutdown, Reason})
+ end.
+
+sc_rs_tcp_client_close(Sock) ->
+ i("sc_rs_tcp_client_close -> entry"),
+ case socket:close(Sock) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({close, Reason})
+ end.
+
+sc_rs_tcp_client_await_terminate(Parent) ->
+ i("sc_rs_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+
+%% The handlers run on the same node as the server (the local node).
+
+sc_rs_tcp_handler_start(Recv, Sock) ->
+ Self = self(),
+ Fun = fun() -> sc_rs_tcp_handler(Self, Recv, Sock) end,
+ {Pid, _} = erlang:spawn_monitor(Fun),
+ Pid.
+
+sc_rs_tcp_handler(Parent, Recv, Sock) ->
+ sc_rs_tcp_handler_init(Parent),
+ sc_rs_tcp_handler_await(Parent, recv),
+ ok = sc_rs_tcp_handler_recv(Recv, Sock, true),
+ sc_rs_tcp_handler_announce_ready(Parent, recv, received),
+ sc_rs_tcp_handler_await(Parent, recv),
+ ok = sc_rs_tcp_handler_recv(Recv, Sock, false),
+ sc_rs_tcp_handler_announce_ready(Parent, recv, closed),
+ Reason = sc_rs_tcp_handler_await(Parent, terminate),
+ exit(Reason).
+
+sc_rs_tcp_handler_init(Parent) ->
+ put(sname, "handler"),
+ _MRef = erlang:monitor(process, Parent),
+ ?SEV_IPRINT("started"),
+ ?SEV_ANNOUNCE_READY(Parent, init),
+ ok.
+
+sc_rs_tcp_handler_await(Parent, terminate) ->
+ ?SEV_IPRINT("await terminate"),
+ ?SEV_AWAIT_TERMINATE(Parent, tester);
+sc_rs_tcp_handler_await(Parent, Slogan) ->
+ ?SEV_IPRINT("await ~w", [Slogan]),
+ ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan).
+
+%% This hould actually work - we leave it for now
+sc_rs_tcp_handler_recv(Recv, Sock, First) ->
+ ?SEV_IPRINT("recv"),
+ try Recv(Sock) of
+ {ok, _} when (First =:= true) ->
+ ok;
+ {error, closed} when (First =:= false) ->
+ ok;
+ {ok, _} ->
+ ?SEV_IPRINT("unexpected success"),
+ {error, unexpected_success};
+ {error, Reason} = ERROR ->
+ ?SEV_IPRINT("receive error: "
+ "~n ~p", [Reason]),
+ ERROR
+ catch
+ C:E:S ->
+ ?SEV_IPRINT("receive failure: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {recv, C, E, S}}
+ end.
+
+sc_rs_tcp_handler_announce_ready(Parent, Slogan, Result) ->
+ ?SEV_IPRINT("announce ready"),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Result),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv4.
+
+sc_rs_recvmsg_send_shutdown_receive_tcp4(suite) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp4(doc) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ Send = fun(Sock, Data) when is_binary(Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv6.
+
+sc_rs_recvmsg_send_shutdown_receive_tcp6(suite) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp6(doc) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ Send = fun(Sock, Data) when is_binary(Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test that the send and recv functions
%% behave as expected when sending and/or reading chunks.
%% First send data in one "big" chunk, and read it in "small" chunks.
@@ -5853,7 +7016,7 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
cmd => fun(#{csock := Sock} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 1 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 1 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)]}};
{error, _} = ERROR ->
@@ -5861,11 +7024,11 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "recv chunk 2",
- cmd => fun(#{csock := Sock,
+ cmd => fun(#{csock := Sock,
chunks := Chunks} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 2 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 2 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)|Chunks]}};
{error, _} = ERROR ->
@@ -5873,11 +7036,11 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "recv chunk 3",
- cmd => fun(#{csock := Sock,
+ cmd => fun(#{csock := Sock,
chunks := Chunks} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 3 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 3 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)|Chunks]}};
{error, _} = ERROR ->
@@ -5885,11 +7048,11 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "recv chunk 4",
- cmd => fun(#{csock := Sock,
+ cmd => fun(#{csock := Sock,
chunks := Chunks} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 4 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 4 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)|Chunks]}};
{error, _} = ERROR ->
@@ -5897,11 +7060,11 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "recv chunk 5",
- cmd => fun(#{csock := Sock,
+ cmd => fun(#{csock := Sock,
chunks := Chunks} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 5 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 5 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)|Chunks]}};
{error, _} = ERROR ->
@@ -5909,11 +7072,11 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "recv chunk 6",
- cmd => fun(#{csock := Sock,
+ cmd => fun(#{csock := Sock,
chunks := Chunks} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 6 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 6 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)|Chunks]}};
{error, _} = ERROR ->
@@ -5921,11 +7084,11 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "recv chunk 7",
- cmd => fun(#{csock := Sock,
+ cmd => fun(#{csock := Sock,
chunks := Chunks} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 7 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 7 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)|Chunks]}};
{error, _} = ERROR ->
@@ -5933,11 +7096,11 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "recv chunk 8",
- cmd => fun(#{csock := Sock,
+ cmd => fun(#{csock := Sock,
chunks := Chunks} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 8 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 8 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)|Chunks]}};
{error, _} = ERROR ->
@@ -5945,11 +7108,11 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "recv chunk 9",
- cmd => fun(#{csock := Sock,
+ cmd => fun(#{csock := Sock,
chunks := Chunks} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 9 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 9 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)|Chunks]}};
{error, _} = ERROR ->
@@ -5957,11 +7120,11 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "recv chunk 10",
- cmd => fun(#{csock := Sock,
+ cmd => fun(#{csock := Sock,
chunks := Chunks} = State) ->
case socket:recv(Sock, 100) of
{ok, Chunk} ->
- ?SEV_IPRINT("recv of chunk 10 of ~p bytes",
+ ?SEV_IPRINT("recv of chunk 10 of ~p bytes",
[size(Chunk)]),
{ok, State#{chunks => [b2l(Chunk)|Chunks]}};
{error, _} = ERROR ->
@@ -5969,7 +7132,7 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end
end},
#{desc => "announce ready (recv-many-small)",
- cmd => fun(#{tester := Tester,
+ cmd => fun(#{tester := Tester,
chunks := Chunks} = State) ->
Data = lists:flatten(lists:reverse(Chunks)),
?SEV_ANNOUNCE_READY(Tester, recv_many_small, Data),
@@ -5987,12 +7150,10 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
end},
#{desc => "recv (one big)",
cmd => fun(#{tester := Tester, csock := Sock, size := Size} = _State) ->
- %% ok = socket:setopt(Sock, otp, debug, true),
case socket:recv(Sock, Size) of
{ok, Data} ->
- %% ok = socket:setopt(Sock, otp, debug, false),
- ?SEV_ANNOUNCE_READY(Tester,
- recv_one_big,
+ ?SEV_ANNOUNCE_READY(Tester,
+ recv_one_big,
b2l(Data)),
ok;
{error, _} = ERROR ->
@@ -6030,7 +7191,7 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
#{desc => "await start",
cmd => fun(State) ->
{Tester, ServerSA} = ?SEV_AWAIT_START(),
- {ok, State#{tester => Tester,
+ {ok, State#{tester => Tester,
server_sa => ServerSA}}
end},
#{desc => "monitor tester",
@@ -6044,7 +7205,7 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
cmd => fun(#{host := Host} = State) ->
case start_node(Host, client) of
{ok, Node} ->
- ?SEV_IPRINT("(remote) client node ~p started",
+ ?SEV_IPRINT("(remote) client node ~p started",
[Node]),
{ok, State#{node => Node}};
{error, Reason, _} ->
@@ -6075,7 +7236,7 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
#{desc => "await remote client ready",
cmd => fun(#{tester := Tester,
rclient := Client} = _State) ->
- ?SEV_AWAIT_READY(Client, rclient, init,
+ ?SEV_AWAIT_READY(Client, rclient, init,
[{tester, Tester}])
end},
#{desc => "announce ready (init)",
@@ -6088,7 +7249,7 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
#{desc => "await continue (connect)",
cmd => fun(#{tester := Tester,
rclient := Client} = _State) ->
- ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
[{rclient, Client}]),
ok
end},
@@ -6100,7 +7261,7 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
#{desc => "await client process ready (connect)",
cmd => fun(#{tester := Tester,
rclient := Client} = _State) ->
- ?SEV_AWAIT_READY(Client, rclient, connect,
+ ?SEV_AWAIT_READY(Client, rclient, connect,
[{tester, Tester}])
end},
#{desc => "announce ready (connect)",
@@ -6112,8 +7273,8 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
#{desc => "await continue (send-one-big)",
cmd => fun(#{tester := Tester,
rclient := Client} = State) ->
- case ?SEV_AWAIT_CONTINUE(Tester, tester,
- send_one_big,
+ case ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send_one_big,
[{rclient, Client}]) of
{ok, Data} ->
{ok, State#{data => Data}};
@@ -6129,7 +7290,7 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
#{desc => "await client process ready (send)",
cmd => fun(#{tester := Tester,
rclient := Client} = _State) ->
- case ?SEV_AWAIT_READY(Client, rclient, send,
+ case ?SEV_AWAIT_READY(Client, rclient, send,
[{tester, Tester}]) of
{ok, Result} ->
Result;
@@ -6146,8 +7307,8 @@ traffic_send_and_recv_chunks_tcp(InitState) ->
#{desc => "await continue (send-many-small)",
cmd => fun(#{tester := Tester,
rclient := Client} = State) ->
- case ?SEV_AWAIT_CONTINUE(Tester, tester,
- send_many_small,
+ case ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send_many_small,
[{rclient, Client}]) of
{ok, Data} ->
{ok, State#{data => Data}};
@@ -6751,6 +7912,7 @@ traffic_ping_pong_small_send_and_recv_tcp4(_Config) when is_list(_Config) ->
end).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test that the send and recv functions
%% by repeatedly sending a meassage between two entities.
@@ -9150,6 +10312,29 @@ sock_close(Sock) ->
end.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+local_host() ->
+ try net_adm:localhost() of
+ Host when is_list(Host) ->
+ list_to_atom(Host)
+ catch
+ C:E:S ->
+ erlang:raise(C, E, S)
+ end.
+
+
+%% This gets the local address (not 127.0...)
+%% We should really implement this using the (new) net module,
+%% but until that gets the necessary functionality...
+which_local_addr(Domain) ->
+ case inet:getifaddrs() of
+ {ok, IFL} ->
+ which_addr(Domain, IFL);
+ {error, Reason} ->
+ ?FAIL({inet, getifaddrs, Reason})
+ end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%