summaryrefslogtreecommitdiff
path: root/lib/kernel
diff options
context:
space:
mode:
authorMicael Karlberg <bmk@erlang.org>2021-04-30 15:47:35 +0200
committerMicael Karlberg <bmk@erlang.org>2021-04-30 15:47:35 +0200
commitb0acad58bd52685009439cc563d1e5548b0fbb21 (patch)
tree38e8354eb03443304fc37a05e322c152bb2ed6b1 /lib/kernel
parent549d0c2f2092ce2b47a35c3aba8a96ca2770e8b5 (diff)
parentfa5d955e4e818a16a3bffdf061a50a3d72ffa033 (diff)
downloaderlang-b0acad58bd52685009439cc563d1e5548b0fbb21.tar.gz
Merge branch 'bmk/kernel/20210426/socket_monitor/OTP-17155'
OTP-17155
Diffstat (limited to 'lib/kernel')
-rw-r--r--lib/kernel/doc/src/inet.xml65
-rw-r--r--lib/kernel/doc/src/socket.xml63
-rw-r--r--lib/kernel/src/gen_tcp_socket.erl47
-rw-r--r--lib/kernel/src/inet.erl63
-rw-r--r--lib/kernel/src/inet_db.erl69
-rw-r--r--lib/kernel/src/socket.erl161
-rw-r--r--lib/kernel/test/gen_tcp_misc_SUITE.erl482
-rw-r--r--lib/kernel/test/kernel_test_lib.erl11
-rw-r--r--lib/kernel/test/kernel_test_lib.hrl3
-rw-r--r--lib/kernel/test/socket_SUITE.erl4688
10 files changed, 5620 insertions, 32 deletions
diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml
index 5a0529eb00..437381d3a9 100644
--- a/lib/kernel/doc/src/inet.xml
+++ b/lib/kernel/doc/src/inet.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2020</year>
+ <year>1997</year><year>2021</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -351,6 +351,36 @@ fe80::204:acff:fe17:bf38
</func>
<func>
+ <name name="cancel_monitor" arity="1" since="OTP @OTP-17155@"/>
+ <fsummary>Stop socket monitoring</fsummary>
+ <desc>
+ <p>If <c><anno>MRef</anno></c> is a reference that the
+ calling process obtained by calling
+ <seemfa marker="#monitor/1"><c>monitor/1</c></seemfa>,
+ this monitor is turned off.
+ If the monitoring is already turned off, nothing happens.</p>
+ <p>The returned value is one of the following:</p>
+ <taglist>
+ <tag><c>true</c></tag>
+ <item>
+ <p>The monitor was found and removed. In this case,
+ no <c>'DOWN'</c> message corresponding to this
+ monitor has been delivered and will not be delivered. </p>
+ </item>
+ <tag><c>false</c></tag>
+ <item>
+ <p>The monitor was not found and could not be removed.
+ This probably because a <c>'DOWN'</c> message corresponding
+ to this monitor has already been placed in the caller
+ message queue.</p>
+ </item>
+ </taglist>
+ <p>Failure: It is an error if <c><anno>MRef</anno></c> refers to a
+ monitor started by another process. </p>
+ </desc>
+ </func>
+
+ <func>
<name name="format_error" arity="1" since=""/>
<fsummary>Return a descriptive string for an error reason.</fsummary>
<desc>
@@ -696,6 +726,39 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
+ <name name="monitor" arity="1" since="OTP @OTP-17155@"/>
+ <fsummary>Start socket monitoring</fsummary>
+ <desc>
+ <p>Start monitor the socket <c><anno>Socket</anno></c>. </p>
+ <p>If the monitored socket does not exist or when the
+ monitor is triggered, a <c>'DOWN'</c> message is sent
+ that has the following pattern:</p>
+ <code type="none">
+ {'DOWN', MonitorRef, socket, Object, Info}
+ </code>
+ <p>In the monitor message <c>MonitorRef</c> and <c>Type</c> are the
+ same as described earlier, and:</p>
+ <taglist>
+ <tag><c>Object</c></tag>
+ <item>
+ <p>The monitored entity, socket, which triggered the event. </p>
+ </item>
+ <tag><c>Info</c></tag>
+ <item>
+ <p>Either the termination reason of the socket or <c>nosock</c>
+ (socket <c><anno>Socket</anno></c> did not exist at the time
+ of monitor creation). </p>
+ </item>
+ </taglist>
+
+ <p>Making several calls to <c>inet:monitor/1</c> for the same
+ <c><anno>Socket</anno></c> is not an error; it results in
+ as many independent monitoring instances. </p>
+
+ </desc>
+ </func>
+
+ <func>
<name name="ntoa" arity="1" since="OTP R16B02"/>
<fsummary>Convert IPv6/IPV4 address to ASCII.</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/socket.xml b/lib/kernel/doc/src/socket.xml
index 972cb7a15c..0707e5f3c0 100644
--- a/lib/kernel/doc/src/socket.xml
+++ b/lib/kernel/doc/src/socket.xml
@@ -1597,6 +1597,36 @@
</func>
<func>
+ <name name="cancel_monitor" arity="1" since="OTP @OTP-17155@"/>
+ <fsummary>Stop socket monitoring</fsummary>
+ <desc>
+ <p>If <c><anno>MRef</anno></c> is a reference that the
+ calling process obtained by calling
+ <seemfa marker="#monitor/1"><c>monitor/1</c></seemfa>,
+ this monitor is turned off.
+ If the monitoring is already turned off, nothing happens.</p>
+ <p>The returned value is one of the following:</p>
+ <taglist>
+ <tag><c>true</c></tag>
+ <item>
+ <p>The monitor was found and removed. In this case,
+ no <c>'DOWN'</c> message corresponding to this
+ monitor has been delivered and will not be delivered. </p>
+ </item>
+ <tag><c>false</c></tag>
+ <item>
+ <p>The monitor was not found and could not be removed.
+ This probably because a <c>'DOWN'</c> message corresponding
+ to this monitor has already been placed in the caller
+ message queue.</p>
+ </item>
+ </taglist>
+ <p>Failure: It is an error if <c><anno>MRef</anno></c> refers to a
+ monitor started by another process. </p>
+ </desc>
+ </func>
+
+ <func>
<name name="getopt" arity="2" clause_i="1" since="OTP @OTP-17154@"/>
<fsummary>
Get a socket option from the protocol level <c>otp</c>.
@@ -1817,6 +1847,39 @@
</func>
<func>
+ <name name="monitor" arity="1" since="OTP @OTP-17155@"/>
+ <fsummary>Start socket monitoring</fsummary>
+ <desc>
+ <p>Start monitor the socket <c><anno>Socket</anno></c>. </p>
+ <p>If the monitored socket does not exist or when the
+ monitor is triggered, a <c>'DOWN'</c> message is sent
+ that has the following pattern:</p>
+ <code type="none">
+ {'DOWN', MonitorRef, socket, Object, Info}
+ </code>
+ <p>In the monitor message <c>MonitorRef</c> and <c>Type</c> are the
+ same as described earlier, and:</p>
+ <taglist>
+ <tag><c>Object</c></tag>
+ <item>
+ <p>The monitored entity, socket, which triggered the event. </p>
+ </item>
+ <tag><c>Info</c></tag>
+ <item>
+ <p>Either the termination reason of the socket or <c>nosock</c>
+ (socket <c><anno>Socket</anno></c> did not exist at the time
+ of monitor creation). </p>
+ </item>
+ </taglist>
+
+ <p>Making several calls to <c>socket:monitor/1</c> for the same
+ <c><anno>Socket</anno></c> is not an error; it results in
+ as many independent monitoring instances. </p>
+
+ </desc>
+ </func>
+
+ <func>
<name name="number_of" arity="0" since="OTP 22.3"/>
<fsummary>Get the number of active sockets.</fsummary>
<desc>
diff --git a/lib/kernel/src/gen_tcp_socket.erl b/lib/kernel/src/gen_tcp_socket.erl
index ed6451a328..d395987f2c 100644
--- a/lib/kernel/src/gen_tcp_socket.erl
+++ b/lib/kernel/src/gen_tcp_socket.erl
@@ -21,18 +21,23 @@
-module(gen_tcp_socket).
-behaviour(gen_statem).
+-compile({no_auto_import, [monitor/1]}).
+
%% gen_tcp
-export([connect/4, listen/2, accept/2,
send/2, recv/3,
sendfile/4,
shutdown/2, close/1, controlling_process/2]).
%% inet
--export([setopts/2, getopts/2,
+-export([
+ monitor/1, cancel_monitor/1,
+ setopts/2, getopts/2,
sockname/1, peername/1,
- getstat/2]).
+ getstat/2
+ ]).
%% Utility
--export([info/1]).
+-export([info/1, socket_to_list/1]).
-ifdef(undefined).
-export([unrecv/2]).
@@ -490,13 +495,34 @@ controlling_process(S, NewOwner, Server, Msg) ->
NewOwner ! Msg,
controlling_process(S, NewOwner, Server).
+
%% -------------------------------------------------------------------------
%% Module inet backends
%% -------------------------------------------------------------------------
+monitor(?MODULE_socket(_Server, ESock) = Socket) ->
+ %% The socket that is part of the down message:
+ case socket_registry:monitor(ESock, #{msocket => Socket}) of
+ {error, Reason} ->
+ erlang:error({invalid, Reason});
+ MRef when is_reference(MRef) ->
+ MRef
+ end;
+monitor(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+cancel_monitor(MRef) when is_reference(MRef) ->
+ socket:cancel_monitor(MRef);
+cancel_monitor(MRef) ->
+ erlang:error(badarg, [MRef]).
+
+
+%% -------------------------------------------------------------------------
+
setopts(?MODULE_socket(Server, _Socket), Opts) when is_list(Opts) ->
call(Server, {setopts, Opts}).
+
%% -------------------------------------------------------------------------
getopts(?MODULE_socket(Server, _Socket), Opts) when is_list(Opts) ->
@@ -530,6 +556,15 @@ info(?MODULE_socket(Server, _Socket)) ->
call(Server, info).
+%% -------------------------------------------------------------------------
+
+socket_to_list(?MODULE_socket(_Server, Socket)) ->
+ "#Socket" ++ Id = socket:to_list(Socket),
+ "#InetSocket" ++ Id;
+socket_to_list(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
%%% ========================================================================
%%% Socket glue code
%%%
@@ -1250,8 +1285,8 @@ handle_event(
#controlling_process{owner = NewOwner, state = State},
{#params{owner = Owner, owner_mon = OwnerMon} = P, D}) ->
%%
- NewOwnerMon = monitor(process, NewOwner),
- true = demonitor(OwnerMon, [flush]),
+ NewOwnerMon = erlang:monitor(process, NewOwner),
+ true = erlang:demonitor(OwnerMon, [flush]),
{next_state, State,
{P#params{owner = NewOwner, owner_mon = NewOwnerMon}, D},
[{reply, From, ok}]};
@@ -1296,7 +1331,7 @@ handle_event({call, From}, {getopts, Opts}, State, {P, D}) ->
%% Call: setopts/1
handle_event({call, From}, {setopts, Opts}, State, {P, D}) ->
- %% ?DBG([{opts, Opts}, {state, State}, {d, D}]),
+ %% ?DBG([{setopts, Opts}, {state, State}, {d, D}]),
{Result, D_1} = state_setopts(P, D, State, Opts),
%% ?DBG([{result, Result}, {d1, D_1}]),
case Result of
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index c41e57345f..17c278923f 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -31,7 +31,7 @@
getif/1, getif/0, getiflist/0, getiflist/1,
ifget/3, ifget/2, ifset/3, ifset/2,
getstat/1, getstat/2,
- info/1,
+ info/1, socket_to_list/1,
ip/1, stats/0, options/0,
pushf/3, popf/1, close/1, gethostname/0, gethostname/1,
parse_ipv4_address/1, parse_ipv6_address/1, parse_ipv4strict_address/1,
@@ -75,6 +75,9 @@
%% timer interface
-export([start_timer/1, timeout/1, timeout/2, stop_timer/1]).
+%% Socket monitoring
+-export([monitor/1, cancel_monitor/1]).
+
-export_type([address_family/0, socket_protocol/0, hostent/0, hostname/0, ip4_address/0,
ip6_address/0, ip_address/0, port_number/0,
family_address/0, local_address/0,
@@ -208,6 +211,53 @@ close(Socket) ->
end.
+%% -- Socket monitor
+
+-spec monitor(Socket) -> reference() when
+ Socket :: socket().
+
+monitor({'$inet', GenSocketMod, _} = Socket) when is_atom(GenSocketMod) ->
+ MRef = GenSocketMod:?FUNCTION_NAME(Socket),
+ case inet_db:put_socket_type(MRef, {socket, GenSocketMod}) of
+ ok ->
+ MRef;
+ error ->
+ GenSocketMod:cancel_monitor(MRef),
+ erlang:error({invalid, Socket})
+ end;
+monitor(Socket) when is_port(Socket) ->
+ MRef = erlang:monitor(port, Socket),
+ case inet_db:put_socket_type(MRef, port) of
+ ok ->
+ MRef;
+ error ->
+ erlang:demonitor(MRef, [flush]),
+ erlang:error({invalid, Socket})
+ end;
+monitor(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% -- Cancel socket monitor
+
+-spec cancel_monitor(MRef) -> boolean() when
+ MRef :: reference().
+
+cancel_monitor(MRef) when is_reference(MRef) ->
+ case inet_db:take_socket_type(MRef) of
+ {ok, port} ->
+ erlang:demonitor(MRef, [info]);
+ {ok, {socket, GenSocketMod}} ->
+ GenSocketMod:?FUNCTION_NAME(MRef);
+ error -> % Assume it has the monitor has already been cancel'ed
+ false
+ end;
+cancel_monitor(MRef) ->
+ erlang:error(badarg, [MRef]).
+
+
+%% -- Socket peername
+
-spec peername(Socket :: socket()) ->
{ok,
{ip_address(), port_number()} |
@@ -605,6 +655,17 @@ gethostbyaddr_tm(Address,Timer) ->
gethostbyaddr_tm(Address, Timer, inet_db:res_option(lookup)).
+-spec socket_to_list(Socket) -> list() when
+ Socket :: socket().
+
+socket_to_list({'$inet', GenSocketMod, _} = Socket)
+ when is_atom(GenSocketMod) ->
+ GenSocketMod:to_list(Socket);
+socket_to_list(Socket) when is_port(Socket) ->
+ erlang:port_to_list(Socket).
+
+
+
-spec info(Socket) -> Info when
Socket :: socket(),
Info :: term().
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
index 17f31ed92e..939cbf6b17 100644
--- a/lib/kernel/src/inet_db.erl
+++ b/lib/kernel/src/inet_db.erl
@@ -21,7 +21,8 @@
-module(inet_db).
%% Store info about ip addresses, names, aliases host files resolver
-%% options
+%% options.
+%% Also miscellaneous "stuff" related to sockets.
%% If the macro DEBUG is defined during compilation,
%% debug printouts are done through erlang:display/1.
@@ -51,7 +52,8 @@
-export([tcp_module/0, set_tcp_module/1]).
-export([udp_module/0, set_udp_module/1]).
-export([sctp_module/0,set_sctp_module/1]).
--export([register_socket/2, unregister_socket/1, lookup_socket/1]).
+-export([register_socket/2, unregister_socket/1, lookup_socket/1,
+ put_socket_type/2, take_socket_type/1]).
%% Host name & domain
-export([set_hostname/1, set_domain/1]).
@@ -88,6 +90,7 @@
hosts_byaddr, %% hosts table
hosts_file_byname, %% hosts table from system file
hosts_file_byaddr, %% hosts table from system file
+ sockets, %% hosts table from system file
cache_timer %% timer reference for refresh
}).
-type state() :: #state{}.
@@ -815,6 +818,14 @@ lookup_socket(Socket) when is_port(Socket) ->
error:badarg -> {error,closed}
end.
+
+put_socket_type(MRef, Type) ->
+ call({put_socket_type, MRef, Type}).
+
+take_socket_type(MRef) ->
+ call({take_socket_type, MRef}).
+
+
%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------
@@ -872,6 +883,10 @@ lookup_socket(Socket) when is_port(Socket) ->
%% node_auth Ls - Default authenication
%% node_crypt Ls - Default encryption
%%
+%% Socket type (used for socket monitors)
+%% --------------------------------------
+%% reference() inet | {socket, Module} - Type of socket being monitored
+%%
-spec init([]) -> {'ok', state()}.
@@ -892,13 +907,16 @@ init([]) ->
HostsByaddr = ets:new(inet_hosts_byaddr, [named_table]),
HostsFileByname = ets:new(inet_hosts_file_byname, [named_table]),
HostsFileByaddr = ets:new(inet_hosts_file_byaddr, [named_table]),
- {ok, #state{db = Db,
- cache = Cache,
- hosts_byname = HostsByname,
- hosts_byaddr = HostsByaddr,
+ %% Miscellaneous stuff related to sockets (monitoring, ...)
+ Sockets = ets:new(inet_sockets, [protected, set, named_table]),
+ {ok, #state{db = Db,
+ cache = Cache,
+ hosts_byname = HostsByname,
+ hosts_byaddr = HostsByaddr,
hosts_file_byname = HostsFileByname,
hosts_file_byaddr = HostsFileByaddr,
- cache_timer = init_timer() }}.
+ sockets = Sockets,
+ cache_timer = init_timer() }}.
reset_db(Db) ->
ets:insert(
@@ -1164,6 +1182,18 @@ handle_call(Request, From, #state{db=Db}=State) ->
{add_rc_list, List} ->
handle_rc_list(List, From, State);
+ %% Store the type of socket this monitor (reference) refers to
+ {put_socket_type, MRef, Type} ->
+ Reply = handle_put_socket_type(State#state.sockets, MRef, Type),
+ {reply, Reply, State};
+
+ %% Take (in the 'maps' sence of the word) the socket type of
+ %% this socket monitor (reference).
+ {take_socket_type, MRef} ->
+ Reply = handle_take_socket_type(State#state.sockets, MRef),
+ {reply, Reply, State};
+
+
stop ->
{stop, normal, ok, State};
@@ -1171,6 +1201,7 @@ handle_call(Request, From, #state{db=Db}=State) ->
{reply, error, State}
end.
+
%%----------------------------------------------------------------------
%% Func: handle_cast/2
%% Returns: {noreply, State} |
@@ -1934,3 +1965,27 @@ lists_nth(_N, [], Default) ->
Default;
lists_nth(N, [_ | T], Default) ->
lists_nth(N - 1, T, Default).
+
+
+%%----------------------------------------------------------------------
+%% Socket related functions
+%%----------------------------------------------------------------------
+
+handle_put_socket_type(Db, MRef, Type) ->
+ Key = {type, MRef},
+ case ets:lookup(Db, Key) of
+ [_] -> % "Should" be impossible...
+ error;
+ [] ->
+ ets:insert(Db, {Key, Type}),
+ ok
+ end.
+
+handle_take_socket_type(Db, MRef) ->
+ Key = {type, MRef},
+ case ets:take(Db, Key) of
+ [{Key, Type}] ->
+ {ok, Type};
+ [] -> % Already demonitor'ed
+ error
+ end.
diff --git a/lib/kernel/src/socket.erl b/lib/kernel/src/socket.erl
index 8da1fb4adf..3d14627669 100644
--- a/lib/kernel/src/socket.erl
+++ b/lib/kernel/src/socket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2021. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,17 +20,26 @@
-module(socket).
--compile({no_auto_import,[error/1]}).
+-compile({no_auto_import, [error/1, monitor/1]}).
%% Administrative and "global" utility functions
-export([
+ %% (registry) Socket functions
number_of/0,
which_sockets/0, which_sockets/1,
+ %% (registry) Socket monitor functions
+ number_of_monitors/0, number_of_monitors/1,
+ which_monitors/1,
+ monitored_by/1,
+
debug/1, socket_debug/1, use_registry/1,
info/0, info/1,
+ monitor/1, cancel_monitor/1,
supports/0, supports/1, supports/2,
- is_supported/1, is_supported/2, is_supported/3
+ is_supported/1, is_supported/2, is_supported/3,
+
+ to_list/1
]).
-export([
@@ -164,7 +173,7 @@
-type socket_info() :: #{domain := domain() | integer(),
type := type() | integer(),
protocol := protocol() | integer(),
- ctrl := pid(),
+ owner := pid(),
ctype := normal | fromfd | {fromfd, integer()},
counters := socket_counters(),
num_readers := non_neg_integer(),
@@ -514,9 +523,10 @@
%% Messages sent from the nif-code to erlang processes:
-define(socket_msg(Socket, Tag, Info), {?socket_tag, (Socket), (Tag), (Info)}).
--type socket() :: ?socket(socket_handle()).
+-type socket() :: ?socket(socket_handle()).
-opaque socket_handle() :: reference().
+
%% Some flags are used for send, others for recv, and yet again
%% others are found in a cmsg(). They may occur in multiple locations..
-type msg_flag() ::
@@ -737,17 +747,18 @@ number_of() ->
%% Interface function to the socket registry
%% Returns a list of all the sockets, accoring to the filter rule.
%%
+
-spec which_sockets() -> [socket()].
which_sockets() ->
?REGISTRY:which_sockets(true).
-spec which_sockets(FilterRule) -> [socket()] when
- FilterRule :: 'inet' | 'inet6' |
- 'stream' | 'dgram' | 'seqpacket' |
- 'sctp' | 'tcp' | 'udp' |
- pid() |
- fun((socket_info()) -> boolean()).
+ FilterRule :: 'inet' | 'inet6' |
+ 'stream' | 'dgram' | 'seqpacket' |
+ 'sctp' | 'tcp' | 'udp' |
+ pid() |
+ fun((socket_info()) -> boolean()).
which_sockets(Domain)
when Domain =:= inet;
@@ -778,6 +789,73 @@ which_sockets(Other) ->
+
+%% *** number_of_monitors ***
+%%
+%% Interface function to the socket registry
+%% returns the number of existing socket monitors.
+%%
+
+-spec number_of_monitors() -> non_neg_integer().
+
+number_of_monitors() ->
+ ?REGISTRY:number_of_monitors().
+
+-spec number_of_monitors(pid()) -> non_neg_integer().
+
+number_of_monitors(Pid) when is_pid(Pid) ->
+ ?REGISTRY:number_of_monitors(Pid).
+
+
+%% *** which_monitors/1 ***
+%%
+%% Interface function to the socket registry
+%% Returns a list of all the monitors of the process or socket.
+%%
+
+-spec which_monitors(Pid) -> [reference()] when
+ Pid :: pid();
+ (Socket) -> [reference()] when
+ Socket :: socket().
+
+which_monitors(Pid) when is_pid(Pid) ->
+ ?REGISTRY:which_monitors(Pid);
+which_monitors(?socket(SockRef) = Socket) when is_reference(SockRef) ->
+ ?REGISTRY:which_monitors(Socket);
+which_monitors(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% *** monitor_by/1 ***
+%%
+%% Interface function to the socket registry
+%% Returns a list of all the process'es monitoring the socket.
+%%
+
+-spec monitored_by(Socket) -> [reference()] when
+ Socket :: socket().
+
+monitored_by(?socket(SockRef) = Socket) when is_reference(SockRef) ->
+ ?REGISTRY:monitored_by(Socket);
+monitored_by(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% *** to_list/1 ***
+%%
+%% This is intended to convert a socket() to a printable string.
+%%
+
+-spec to_list(Socket) -> list() when
+ Socket :: socket().
+
+to_list(?socket(SockRef)) when is_reference(SockRef) ->
+ "#Ref" ++ Id = erlang:ref_to_list(SockRef),
+ "#Socket" ++ Id;
+to_list(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
%% ===========================================================================
%%
%% Debug features
@@ -836,6 +914,69 @@ info(Socket) ->
%% ===========================================================================
%%
+%% monitor - Monitor a socket
+%%
+%% If a socket "dies", a down message, similar to erlang:monitor, will be
+%% sent to the requesting process:
+%%
+%% {'DOWN', MonitorRef, socket, Socket, Info}
+%%
+%% ===========================================================================
+
+-spec monitor(Socket) -> reference() when
+ Socket :: socket().
+
+%% Should it be possible to specify a modification of the 'Socket' part
+%% of the DOWN-message? The point would be to make it possible for
+%% a gen_tcp_socket-socket to use this and get the proper 'socket'
+%% as part of the message.
+
+monitor(?socket(SockRef) = Socket) when is_reference(SockRef) ->
+ case prim_socket:setopt(SockRef, {otp, use_registry}, true) of
+ ok ->
+ case socket_registry:monitor(Socket) of
+ {error, MReason} ->
+ erlang:error({invalid, MReason});
+ MRef when is_reference(MRef) ->
+ MRef
+ end;
+ {error, SReason} ->
+ erlang:error({invalid, SReason})
+ end;
+monitor(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% ===========================================================================
+%%
+%% cancel_monitor - Cancel a socket monitor
+%%
+%% If MRef is a reference that the socket obtained
+%% by calling monitor/1, this monitoring is turned off.
+%% If the monitoring is already turned off, nothing happens.
+%%
+%% ===========================================================================
+
+-spec cancel_monitor(MRef) -> boolean() when
+ MRef :: reference().
+
+cancel_monitor(MRef) when is_reference(MRef) ->
+ case socket_registry:cancel_monitor(MRef) of
+ ok ->
+ true;
+ {error, unknown_monitor} ->
+ false;
+ {error, not_owner} ->
+ erlang:error(badarg, [MRef]);
+ {error, Reason} ->
+ erlang:error({invalid, Reason})
+ end;
+cancel_monitor(MRef) ->
+ erlang:error(badarg, [MRef]).
+
+
+%% ===========================================================================
+%%
%% supports - get information about what the platform "supports".
%%
%% Generates a list of various info about what the plaform can support.
diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl
index d099cc0864..8cd7d1f21e 100644
--- a/lib/kernel/test/gen_tcp_misc_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl
@@ -22,7 +22,8 @@
-include_lib("common_test/include/ct.hrl").
-include("kernel_test_lib.hrl").
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+-export([
+ all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
controlling_process/1, controlling_process_self/1,
no_accept/1, close_with_pending_output/1, active_n/1,
@@ -58,7 +59,15 @@
active_once_closed/1, send_timeout/1, send_timeout_active/1,
otp_7731/1, zombie_sockets/1, otp_7816/1, otp_8102/1,
wrapping_oct/0, wrapping_oct/1, otp_9389/1, otp_13939/1,
- otp_12242/1, delay_send_error/1, bidirectional_traffic/1]).
+ otp_12242/1, delay_send_error/1, bidirectional_traffic/1,
+ socket_monitor1/1,
+ socket_monitor1_manys/1,
+ socket_monitor1_manyc/1,
+ socket_monitor1_demon_after/1,
+ socket_monitor2/1,
+ socket_monitor2_manys/1,
+ socket_monitor2_manyc/1
+ ]).
%% Internal exports.
-export([sender/4,
@@ -142,7 +151,8 @@ groups() ->
{partial_recv_and_close, [], partial_recv_and_close_cases()},
{pktoptions, [], pktoptions_cases()},
{accept, [], accept_cases()},
- {send_timeout, [], send_timeout_cases()}
+ {send_timeout, [], send_timeout_cases()},
+ {socket_monitor, [], socket_monitor_cases()}
].
inet_backend_default_cases() ->
@@ -177,7 +187,8 @@ all_cases() ->
otp_7816,
otp_8102, otp_9389,
otp_12242, delay_send_error,
- bidirectional_traffic
+ bidirectional_traffic,
+ {group, socket_monitor}
].
close_cases() ->
@@ -275,6 +286,17 @@ send_timeout_cases() ->
send_timeout_active
].
+socket_monitor_cases() ->
+ [
+ socket_monitor1,
+ socket_monitor1_manys,
+ socket_monitor1_manyc,
+ socket_monitor1_demon_after,
+ socket_monitor2,
+ socket_monitor2_manys,
+ socket_monitor2_manyc
+ ].
+
init_per_suite(Config0) ->
?P("init_per_suite -> entry with"
@@ -448,7 +470,7 @@ do_delay_send_1(Config) ->
{B1,B2,B3}.
do_delay_send_2(Config) ->
- {ok, LS} = ?LISTEN(Config, 0, []),
+ {ok, LS} = ?LISTEN(Config),
{ok, {{0,0,0,0},PortNum}} = inet:sockname(LS),
{ok, S} = ?CONNECT(Config, "localhost",PortNum,[]),
{ok, S2} = gen_tcp:accept(LS),
@@ -6633,6 +6655,456 @@ recv(Socket, Total, Control) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This is the most basic of tests.
+%% We create a listen socket, then spawns processes that create
+%% monitors to it...
+socket_monitor1(Config) when is_list(Config) ->
+ ct:timetrap(?MINS(1)),
+ ?TC_TRY(socket_monitor1,
+ fun() -> do_socket_monitor1(Config) end).
+
+do_socket_monitor1(Config) ->
+ ?P("begin"),
+ Self = self(),
+ Type = ?SOCKET_TYPE(Config),
+ {ok, LSock1} = ?LISTEN(Config),
+ F = fun(S, Fun) -> spawn_monitor(fun() -> Fun(S, Self) end) end,
+ F1 = fun(Socket, Parent) when is_pid(Parent) ->
+ ?P("[client] create monitor"),
+ MRef = inet:monitor(Socket),
+ Parent ! {self(), ready},
+ sm_await_socket_down(MRef, Socket, Type)
+ end,
+ ?P("spawn client"),
+ {Pid1, Mon1} = F(LSock1, F1),
+ ?P("await client ready"),
+ sm_await_client_ready(Pid1),
+ ?P("close socket"),
+ gen_tcp:close(LSock1),
+ ?P("await client termination"),
+ sm_await_down(Pid1, Mon1, ok),
+ ?P("done"),
+ ok.
+
+sm_await_socket_down(ExpMon, ExpSock, ExpType) ->
+ sm_await_socket_down(ExpMon, ExpSock, ExpType, "client").
+
+sm_await_socket_down(ExpMon, ExpSock, ExpType, Name) ->
+ receive
+ {'DOWN', Mon, Type, Sock, Info} when (Type =:= ExpType) andalso
+ (Mon =:= ExpMon) andalso
+ (Sock =:= ExpSock) ->
+ ?P("[~s] received expected (socket) down message: "
+ "~n Mon: ~p"
+ "~n Type: ~p"
+ "~n Sock: ~p"
+ "~n Info: ~p", [Name, Mon, Type, Sock, Info]),
+ exit(ok);
+
+ Any ->
+ ?P("[~s] received unexpected message: "
+ "~n ~p", [Name, Any]),
+ exit({unexpected_message, Any})
+ end.
+
+sm_await_client_ready(Pid) ->
+ sm_await_client_ready(Pid, "client").
+
+sm_await_client_ready(Pid, Name) ->
+ receive
+ {Pid, ready} ->
+ ?P("received ~s ready", [Name])
+ end.
+
+sm_await_down(Pid, Mon, ExpRes) ->
+ receive
+ {'DOWN', Mon, process, Pid, ExpRes} ->
+ ?P("received expected process down message from ~p", [Pid]),
+ ok;
+ {'DOWN', Mon, process, Pid, UnexpRes} ->
+ ?P("received unexpected process down message from ~p: "
+ "~n ~p", [Pid, UnexpRes]),
+ ct:fail({unexpected_down, UnexpRes})
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This is the most basic of tests.
+%% We create "many" listen socket(s), then spawns processes that create
+%% monitors to them...
+socket_monitor1_manys(Config) when is_list(Config) ->
+ ct:timetrap(?MINS(1)),
+ ?TC_TRY(socket_monitor1_manys,
+ fun() -> do_socket_monitor1_manys(Config) end).
+
+do_socket_monitor1_manys(Config) ->
+ ?P("begin"),
+ Self = self(),
+ Type = ?SOCKET_TYPE(Config),
+ ?P("[client] create socket(s)"),
+ {ok, LSock1} = ?LISTEN(Config),
+ {ok, LSock2} = ?LISTEN(Config),
+ {ok, LSock3} = ?LISTEN(Config),
+ {ok, LSock4} = ?LISTEN(Config),
+ {ok, LSock5} = ?LISTEN(Config),
+ F = fun(S, Fun) -> spawn_monitor(fun() -> Fun(S, Self) end) end,
+ F1 = fun(Sockets, Parent) when is_list(Sockets) andalso is_pid(Parent) ->
+ ?P("[client] create monitor(s)"),
+ Monitors = [{inet:monitor(Socket), Socket} ||
+ Socket <- Sockets],
+ Parent ! {self(), ready},
+ sm_await_socket_down2(Monitors, Type)
+ end,
+ ?P("spawn client"),
+ {Pid1, Mon1} = F([LSock1, LSock2, LSock3, LSock4, LSock5], F1),
+ ?P("await client ready"),
+ sm_await_client_ready(Pid1),
+ ?P("close socket(s)"),
+ gen_tcp:close(LSock1),
+ gen_tcp:close(LSock2),
+ gen_tcp:close(LSock3),
+ gen_tcp:close(LSock4),
+ gen_tcp:close(LSock5),
+ ?P("await client termination"),
+ sm_await_down(Pid1, Mon1, ok),
+ ?P("done"),
+ ok.
+
+
+sm_await_socket_down2(Monitors, ExpType) ->
+ sm_await_socket_down2(Monitors, ExpType, "client").
+
+sm_await_socket_down2([], _ExpType, Name) ->
+ ?P("[~s] all sockets down", [Name]),
+ exit(ok);
+sm_await_socket_down2(Mons, ExpType, Name) when is_list(Mons) ->
+ ?P("[~s] await socket down", [Name]),
+ receive
+ {'DOWN', Mon, Type, Sock, Info} when (Type =:= ExpType) ->
+ ?P("[~s] received expected (socket) down message: "
+ "~n Mon: ~p"
+ "~n Type: ~p"
+ "~n Sock: ~p"
+ "~n Info: ~p", [Name, Mon, Type, Sock, Info]),
+ case lists:keysearch(Mon, 1, Mons) of
+ {value, {Mon, Sock}} ->
+ Mons2 = lists:keydelete(Mon, 1, Mons),
+ sm_await_socket_down2(Mons2, ExpType, Name);
+ {value, Value} ->
+ ?P("[~s] Unexpected socket down: "
+ "~n Value: ~p", [Name, Value]),
+ ct:fail({unexpected_monitor, Mon, Value});
+ false ->
+ ct:fail({unknown_monitor, Mon})
+ end
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This is the most basic of tests.
+%% We create a listen socket, then spawn client process(es) that create
+%% monitors to it...
+socket_monitor1_manyc(Config) when is_list(Config) ->
+ ct:timetrap(?MINS(1)),
+ ?TC_TRY(socket_monitor1_manyc,
+ fun() -> do_socket_monitor1_manyc(Config) end).
+
+do_socket_monitor1_manyc(Config) ->
+ ?P("begin"),
+ Self = self(),
+ Type = ?SOCKET_TYPE(Config),
+ {ok, LSock1} = ?LISTEN(Config),
+ F = fun(S, Fun, Name) ->
+ spawn_monitor(fun() -> Fun(S, Name, Self) end)
+ end,
+ F1 = fun(Socket, Name, Parent) when is_list(Name) andalso is_pid(Parent) ->
+ ?P("[~s] monitor socket", [Name]),
+ MRef = inet:monitor(Socket),
+ Parent ! {self(), ready},
+ sm_await_socket_down(MRef, Socket, Type)
+ end,
+ ?P("spawn client(s)"),
+ {Pid1, Mon1} = F(LSock1, F1, "client1"),
+ {Pid2, Mon2} = F(LSock1, F1, "client2"),
+ {Pid3, Mon3} = F(LSock1, F1, "client3"),
+ {Pid4, Mon4} = F(LSock1, F1, "client4"),
+ {Pid5, Mon5} = F(LSock1, F1, "client5"),
+ ?P("await client(s) ready"),
+ sm_await_client_ready(Pid1, "client1"),
+ sm_await_client_ready(Pid2, "client2"),
+ sm_await_client_ready(Pid3, "client3"),
+ sm_await_client_ready(Pid4, "client4"),
+ sm_await_client_ready(Pid5, "client5"),
+ ?P("close socket"),
+ gen_tcp:close(LSock1),
+ ?P("await client(s) termination"),
+ sm_await_down(Pid1, Mon1, ok),
+ sm_await_down(Pid2, Mon2, ok),
+ sm_await_down(Pid3, Mon3, ok),
+ sm_await_down(Pid4, Mon4, ok),
+ sm_await_down(Pid5, Mon5, ok),
+ ?P("done"),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This is the most basic of tests.
+%% We create a listen socket, then spawns processes that create
+%% monitors to it...
+socket_monitor1_demon_after(Config) when is_list(Config) ->
+ ct:timetrap(?MINS(1)),
+ ?TC_TRY(socket_monitor1_demon_after,
+ fun() -> do_socket_monitor1_demon_after(Config) end).
+
+do_socket_monitor1_demon_after(Config) ->
+ ?P("begin"),
+ Self = self(),
+ Type = ?SOCKET_TYPE(Config),
+ {ok, LSock1} = ?LISTEN(Config),
+ F = fun(S, Fun) -> spawn_monitor(fun() -> Fun(S, Self) end) end,
+ F1 = fun(Socket, Parent) when is_pid(Parent) ->
+ ?P("[client] create monitor"),
+ MRef = inet:monitor(Socket),
+ ?P("[client] sleep some"),
+ ?SLEEP(?SECS(1)),
+ ?P("[client] cancel (socket) monitor"),
+ inet:cancel_monitor(MRef),
+ ?P("[client] announce ready"),
+ Parent ! {self(), ready},
+ sm_await_no_socket_down(MRef, Socket, Type)
+ end,
+ ?P("spawn client"),
+ {Pid1, Mon1} = F(LSock1, F1),
+ ?P("await client ready"),
+ sm_await_client_ready(Pid1),
+ ?P("close socket"),
+ gen_tcp:close(LSock1),
+ ?P("await client termination"),
+ sm_await_down(Pid1, Mon1, ok),
+ ?P("done"),
+ ok.
+
+
+sm_await_no_socket_down(ExpMon, ExpSock, ExpType) ->
+ sm_await_no_socket_down(ExpMon, ExpSock, ExpType, "client").
+
+sm_await_no_socket_down(ExpMon, ExpSock, ExpType, Name) ->
+ receive
+ {'DOWN', Mon, Type, Sock, Info} when (Type =:= ExpType) andalso
+ (Mon =:= ExpMon) andalso
+ (Sock =:= ExpSock) ->
+ ?P("[~s] received unexpected (socket) down message: "
+ "~n Mon: ~p"
+ "~n Type: ~p"
+ "~n Sock: ~p"
+ "~n Info: ~p", [Name, Mon, Type, Sock, Info]),
+ exit({unexpected_down, Mon, Type, Sock, Info});
+
+ Any ->
+ ?P("[~s] received unexpected message: "
+ "~n ~p", [Name, Any]),
+ exit({unexpected_message, Any})
+
+ after 1000 ->
+ ?P("[~s] expected message timeout", [Name]),
+ exit(ok)
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This is the most basic of tests.
+%% Spawn a process that creates a (listen) socket, then spawns processes
+%% that create monitors to it...
+socket_monitor2(Config) when is_list(Config) ->
+ ct:timetrap(?MINS(1)),
+ ?TC_TRY(socket_monitor2,
+ fun() -> do_socket_monitor2(Config) end).
+
+do_socket_monitor2(Config) ->
+ ?P("begin"),
+ Type = ?SOCKET_TYPE(Config),
+ Self = self(),
+ {OwnerPid, OwnerMon} =
+ spawn_monitor(fun() ->
+ ?P("[owner] create (listen) socket"),
+ {ok, L} = ?LISTEN(Config),
+ ?P("[owner] send (listen) socket to ctrl"),
+ Self ! {socket, L},
+ ?P("[owner] ready"),
+ receive
+ {Self, die} ->
+ exit(normal)
+ end
+ end),
+ LSock1 = receive
+ {socket, L} ->
+ ?P("received socket from owner"),
+ L;
+ {'DOWN', OwnerMon, process, OwnerPid, OwnerReason} ->
+ ?P("received unexpected owner termination: "
+ "~n ~p", [OwnerReason]),
+ ct:fail({unexpected_owner_termination, OwnerReason})
+ end,
+ F = fun(S, Fun) -> spawn_monitor(fun() -> Fun(S, Self) end) end,
+ F1 = fun(Socket, Parent) when is_pid(Parent) ->
+ ?P("[client] create monitor"),
+ MRef = inet:monitor(Socket),
+ Parent ! {self(), ready},
+ sm_await_socket_down(MRef, Socket, Type)
+ end,
+ ?P("spawn client"),
+ {Pid1, Mon1} = F(LSock1, F1),
+ ?P("spawn client"),
+ sm_await_client_ready(Pid1),
+ ?P("kill owner"),
+ exit(OwnerPid, kill),
+ ?P("await owner termination"),
+ sm_await_down(OwnerPid, OwnerMon, killed),
+ ?P("await client termination"),
+ sm_await_down(Pid1, Mon1, ok),
+ ?P("done"),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This is the most basic of tests.
+%% Spawn a process that creates "many" (listen) socket(s), then spawns
+%% a process that create monitors to them...
+
+socket_monitor2_manys(Config) when is_list(Config) ->
+ ct:timetrap(?MINS(1)),
+ ?TC_TRY(socket_monitor2_manys,
+ fun() -> do_socket_monitor2_manys(Config) end).
+
+do_socket_monitor2_manys(Config) ->
+ ?P("begin"),
+ Type = ?SOCKET_TYPE(Config),
+ Self = self(),
+ ?P("spawn owner"),
+ {OwnerPid, OwnerMon} =
+ spawn_monitor(fun() ->
+ ?P("[owner] create (listen) socket(s)"),
+ {ok, L1} = ?LISTEN(Config),
+ {ok, L2} = ?LISTEN(Config),
+ {ok, L3} = ?LISTEN(Config),
+ {ok, L4} = ?LISTEN(Config),
+ {ok, L5} = ?LISTEN(Config),
+ ?P("[owner] send (listen) socket(s) to ctrl"),
+ Self ! {socket, [L1, L2, L3, L4, L5]},
+ ?P("[owner] ready"),
+ receive
+ {Self, die} ->
+ exit(normal)
+ end
+ end),
+ ?P("await sockets (from owner)"),
+ LSocks = receive
+ {socket, Socks} ->
+ ?P("received socket(s) from owner"),
+ Socks;
+ {'DOWN', OwnerMon, process, OwnerPid, OwnerReason} ->
+ ?P("received unexpected owner termination: "
+ "~n ~p", [OwnerReason]),
+ ct:fail({unexpected_owner_termination, OwnerReason})
+ end,
+ F = fun(S, Fun) -> spawn_monitor(fun() -> Fun(S, Self) end) end,
+ F1 = fun(Sockets, Parent) when is_list(Sockets) andalso is_pid(Parent) ->
+ ?P("[client] create monitor(s)"),
+ Monitors = [{inet:monitor(Socket), Socket} ||
+ Socket <- Sockets],
+ ?P("[client] announce ready"),
+ Parent ! {self(), ready},
+ sm_await_socket_down2(Monitors, Type)
+ end,
+ ?P("spawn client"),
+ {Pid1, Mon1} = F(LSocks, F1),
+ ?P("await client ready"),
+ sm_await_client_ready(Pid1),
+ ?P("kill owner"),
+ exit(OwnerPid, kill),
+ ?P("await owner (~p) termination", [OwnerPid]),
+ sm_await_down(OwnerPid, OwnerMon, killed),
+ ?P("await client termination"),
+ sm_await_down(Pid1, Mon1, ok),
+ ?P("done"),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This is the most basic of tests.
+%% Spawn a process that creates a (listen) socket, then spawns (client)
+%% processes that create monitors to it...
+socket_monitor2_manyc(Config) when is_list(Config) ->
+ ct:timetrap(?MINS(1)),
+ ?TC_TRY(socket_monitor2_manyc,
+ fun() -> do_socket_monitor2_manyc(Config) end).
+
+do_socket_monitor2_manyc(Config) ->
+ ?P("begin"),
+ Type = ?SOCKET_TYPE(Config),
+ Self = self(),
+ {OwnerPid, OwnerMon} =
+ spawn_monitor(fun() ->
+ {ok, L} = ?LISTEN(Config),
+ Self ! {socket, L},
+ receive
+ {Self, die} ->
+ exit(normal)
+ end
+ end),
+ LSock1 = receive
+ {socket, L} ->
+ ?P("received socket from owner"),
+ L;
+ {'DOWN', OwnerMon, process, OwnerPid, OwnerReason} ->
+ ?P("received unexpected owner termination: "
+ "~n ~p", [OwnerReason]),
+ ct:fail({unexpected_owner_termination, OwnerReason})
+ end,
+ F = fun(S, Fun, Name) ->
+ spawn_monitor(fun() -> Fun(S, Name, Self) end)
+ end,
+ F1 = fun(Socket, Name, Parent) when is_list(Name) andalso is_pid(Parent) ->
+ ?P("[~s] create monitor", [Name]),
+ MRef = inet:monitor(Socket),
+ Parent ! {self(), ready},
+ sm_await_socket_down(MRef, Socket, Type, Name)
+ end,
+ ?P("spawn client(s)"),
+ {Pid1, Mon1} = F(LSock1, F1, "client1"),
+ {Pid2, Mon2} = F(LSock1, F1, "client2"),
+ {Pid3, Mon3} = F(LSock1, F1, "client3"),
+ {Pid4, Mon4} = F(LSock1, F1, "client4"),
+ {Pid5, Mon5} = F(LSock1, F1, "client5"),
+ ?P("await client(s) ready"),
+ sm_await_client_ready(Pid1, "client1"),
+ sm_await_client_ready(Pid2, "client2"),
+ sm_await_client_ready(Pid3, "client3"),
+ sm_await_client_ready(Pid4, "client4"),
+ sm_await_client_ready(Pid5, "client5"),
+ ?P("kill owner"),
+ exit(OwnerPid, kill),
+ ?P("await owner termination"),
+ sm_await_down(OwnerPid, OwnerMon, killed),
+ ?P("await client(s) termination"),
+ sm_await_down(Pid1, Mon1, ok),
+ sm_await_down(Pid2, Mon2, ok),
+ sm_await_down(Pid3, Mon3, ok),
+ sm_await_down(Pid4, Mon4, ok),
+ sm_await_down(Pid5, Mon5, ok),
+ ?P("done"),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
pi(Item) ->
{Item, Val} = process_info(self(), Item),
Val.
diff --git a/lib/kernel/test/kernel_test_lib.erl b/lib/kernel/test/kernel_test_lib.erl
index 268b97ca3f..36ae71067d 100644
--- a/lib/kernel/test/kernel_test_lib.erl
+++ b/lib/kernel/test/kernel_test_lib.erl
@@ -23,7 +23,8 @@
-export([init_per_suite/1,
end_per_suite/1]).
-export([tc_try/3]).
--export([listen/3,
+-export([socket_type/1,
+ listen/3,
connect/4, connect/5,
is_socket_backend/1,
inet_backend_opts/1,
@@ -1726,6 +1727,14 @@ proxy_call(F, Timeout, Default)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+socket_type(Config) ->
+ case is_socket_backend(Config) of
+ true ->
+ socket;
+ false ->
+ port
+ end.
+
listen(Config, Port, Opts) ->
InetBackendOpts = inet_backend_opts(Config),
gen_tcp:listen(Port, InetBackendOpts ++ Opts).
diff --git a/lib/kernel/test/kernel_test_lib.hrl b/lib/kernel/test/kernel_test_lib.hrl
index d49c31f4d6..9dc99fdd22 100644
--- a/lib/kernel/test/kernel_test_lib.hrl
+++ b/lib/kernel/test/kernel_test_lib.hrl
@@ -33,6 +33,9 @@
-define(TC_TRY(Case, TC), ?TC_TRY(Case, fun() -> ok end, TC)).
-define(TC_TRY(Case, Cond, TC), ?LIB:tc_try(Case, Cond, TC)).
+-define(SOCKET_TYPE(C), ?LIB:socket_type(C)).
+-define(LISTEN(C), ?LIB:listen(C, 0, [])).
+-define(LISTEN(C, P), ?LIB:listen(C, P, [])).
-define(LISTEN(C, P, O), ?LIB:listen(C, P, O)).
-define(CONNECT(__C__, __H__, __P__, __O__),
?LIB:connect(__C__, __H__, __P__, __O__)).
diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl
index 6f5e778047..9efe99229a 100644
--- a/lib/kernel/test/socket_SUITE.erl
+++ b/lib/kernel/test/socket_SUITE.erl
@@ -235,7 +235,19 @@
%% Socket Registry
reg_s_single_open_and_close_and_count/1,
reg_s_optional_open_and_close_and_count/1,
-
+
+
+ %% Socket Monitor
+ monitor_simple_open_and_close/1,
+ monitor_simple_open_and_exit/1,
+ monitor_simple_open_and_demon_and_close/1,
+ monitor_open_and_close_multi_socks/1,
+ monitor_open_and_exit_multi_socks/1,
+ monitor_open_and_demon_and_close_multi_socks/1,
+ monitor_open_and_close_multi_mon/1,
+ monitor_open_and_exit_multi_mon/1,
+ monitor_open_and_close_multi_socks_and_mon/1,
+ monitor_open_and_exit_multi_socks_and_mon/1,
%% *** Socket Closure ***
sc_cpe_socket_cleanup_tcp4/1,
@@ -685,6 +697,7 @@ suite() ->
all() ->
Groups = [{api, "ESOCK_TEST_API", include},
{reg, undefined, include},
+ {monitor, undefined, include},
{socket_close, "ESOCK_TEST_SOCK_CLOSE", include},
{traffic, "ESOCK_TEST_TRAFFIC", include},
{ttest, "ESOCK_TEST_TTEST", exclude},
@@ -737,6 +750,7 @@ groups() ->
%% {api_options_sctp, [], api_options_sctp_cases()},
{api_op_with_timeout, [], api_op_with_timeout_cases()},
{reg, [], reg_simple_cases()},
+ {monitor, [], monitor_cases()},
{socket_close, [], socket_close_cases()},
{sc_ctrl_proc_exit, [], sc_cp_exit_cases()},
{sc_local_close, [], sc_lc_cases()},
@@ -1080,6 +1094,22 @@ reg_simple_cases() ->
].
+%% Socket monitor test cases
+monitor_cases() ->
+ [
+ monitor_simple_open_and_close,
+ monitor_simple_open_and_exit,
+ monitor_simple_open_and_demon_and_close,
+ monitor_open_and_close_multi_socks,
+ monitor_open_and_exit_multi_socks,
+ monitor_open_and_demon_and_close_multi_socks,
+ monitor_open_and_close_multi_mon,
+ monitor_open_and_exit_multi_mon,
+ monitor_open_and_close_multi_socks_and_mon,
+ monitor_open_and_exit_multi_socks_and_mon
+ ].
+
+
%% These cases tests what happens when the socket is closed/shutdown,
%% locally or remotely.
socket_close_cases() ->
@@ -11084,6 +11114,8 @@ api_opt_simple_otp_meta_option() ->
i("await tcp evaluators"),
ok = ?SEV_AWAIT_FINISH([Helper, Main]).
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Perform some simple operations with the rcvbuf otp option
@@ -25679,6 +25711,4660 @@ reg_s_optional_open_and_close_and_count() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%
+%% SOCKET MONITOR %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create one socket, monitor from a different process then close socket.
+%% The process that did the monitor shall receive a socket DOWN.
+
+monitor_simple_open_and_close(suite) ->
+ [];
+monitor_simple_open_and_close(doc) ->
+ [];
+monitor_simple_open_and_close(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(monitor_simple_open_and_close,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_simple_open_and_close(InitState)
+ end).
+
+
+mon_simple_open_and_close(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close)
+ end},
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor owner",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket - create two",
+ cmd => fun(#{sock := Sock} = State) ->
+ MRef1 = socket:monitor(Sock),
+ MRef2 = socket:monitor(Sock),
+ ?SEV_IPRINT("Monitor(s): "
+ "~n 1: ~p"
+ "~n 2: ~p", [MRef1, MRef2]),
+ {ok, State#{mon1 => MRef1, mon2 => MRef2}}
+ end},
+ #{desc => "verify total number of monitors (=2)",
+ cmd => fun(_State) ->
+ 2 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=2)",
+ cmd => fun(_State) ->
+ 2 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "verify which monitors - (self) two",
+ cmd => fun(#{mon1 := MRef1,
+ mon2 := MRef2} = _State) ->
+ Mons = lists:sort([MRef1, MRef2]),
+ case lists:sort(socket:which_monitors(self())) of
+ Mons ->
+ ok;
+ SMons ->
+ ?SEV_EPRINT("Unexpected (self) monitors: "
+ "~n Expected: ~p"
+ "~n Actual: ~p",
+ [Mons, SMons]),
+ {error, unexpected_monitors}
+ end
+ end},
+ #{desc => "verify which monitors - (sock) two",
+ cmd => fun(#{sock := Sock,
+ mon1 := MRef1,
+ mon2 := MRef2} = _State) ->
+ Mons = lists:sort([MRef1, MRef2]),
+ case lists:sort(socket:which_monitors(Sock)) of
+ Mons ->
+ ok;
+ SMons ->
+ ?SEV_EPRINT("Unexpected (sock) monitors: "
+ "~n Expected: ~p"
+ "~n Actual: ~p",
+ [Mons, SMons]),
+ {error, unexpected_monitors}
+ end
+ end},
+ #{desc => "verify monitored by - only us",
+ cmd => fun(#{sock := Sock} = _State) ->
+ Self = self(),
+ [Self] = socket:monitored_by(Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "order owner to close",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await socket down 1",
+ cmd => fun(#{sock := Sock,
+ mon1 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~s"
+ "~n Info: ~p",
+ [MRef,
+ socket:to_list(Sock),
+ Info]),
+ {ok, maps:remove(mon1, State)}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "await socket down 2",
+ cmd => fun(#{sock := Sock,
+ mon2 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~s"
+ "~n Info: ~p",
+ [MRef,
+ socket:to_list(Sock),
+ Info]),
+ {ok, maps:remove(mon2, State)}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "verify which monitors - only one",
+ cmd => fun(#{sock := Sock} = _State) ->
+ [] = socket:which_monitors(self()),
+ [] = socket:which_monitors(Sock),
+ ok
+ end},
+ #{desc => "verify monitored by - none",
+ cmd => fun(#{sock := Sock} = _State) ->
+ [] = socket:monitored_by(Sock),
+ ok
+ end},
+
+ %% Cleanup
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = _State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create one socket, monitor from a different process then stop the
+%% owner process.
+%% The process that did the monitor shall receive a socket DOWN.
+
+monitor_simple_open_and_exit(suite) ->
+ [];
+monitor_simple_open_and_exit(doc) ->
+ [];
+monitor_simple_open_and_exit(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(monitor_simple_open_and_exit,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_simple_open_and_exit(InitState)
+ end).
+
+
+mon_simple_open_and_exit(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor owner",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ MRef = socket:monitor(Sock),
+ ?SEV_IPRINT("Monitor: ~p", [MRef]),
+ {ok, State#{mon => MRef}}
+ end},
+ #{desc => "verify total number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "verify which monitors - only one",
+ cmd => fun(#{sock := Sock,
+ mon := MRef} = _State) ->
+ [MRef] = socket:which_monitors(self()),
+ [MRef] = socket:which_monitors(Sock),
+ ok
+ end},
+ #{desc => "verify monitored by - only us",
+ cmd => fun(#{sock := Sock} = _State) ->
+ Self = self(),
+ [Self] = socket:monitored_by(Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = _State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "await socket down",
+ cmd => fun(#{sock := Sock,
+ mon := MRef} = _State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ ok
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "verify which monitors - only one",
+ cmd => fun(#{sock := Sock} = _State) ->
+ [] = socket:which_monitors(self()),
+ [] = socket:which_monitors(Sock),
+ ok
+ end},
+ #{desc => "verify monitored by - none",
+ cmd => fun(#{sock := Sock} = _State) ->
+ [] = socket:monitored_by(Sock),
+ ok
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create one socket, monitor from a different process, cancel_montor
+%% (demonitor) and then close socket.
+%% The process that did the monitor shall *not* receive a socket DOWN.
+
+monitor_simple_open_and_demon_and_close(suite) ->
+ [];
+monitor_simple_open_and_demon_and_close(doc) ->
+ [];
+monitor_simple_open_and_demon_and_close(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(monitor_simple_open_and_demon_and_close,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_simple_open_and_demon_and_close(InitState)
+ end).
+
+
+mon_simple_open_and_demon_and_close(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close)
+ end},
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor owner",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ MRef = socket:monitor(Sock),
+ ?SEV_IPRINT("Monitor: ~p", [MRef]),
+ {ok, State#{mon => MRef}}
+ end},
+ #{desc => "verify total number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "demonitor socket",
+ cmd => fun(#{mon := MRef} = State) ->
+ true = socket:cancel_monitor(MRef),
+ {ok, maps:remove(mon, State)}
+ end},
+
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "order owner to close",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+
+ #{desc => "await socket down",
+ cmd => fun(#{sock := Sock} = _State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_EPRINT("received UNEXPECTED down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ {error, unexpected_down}
+ after 5000 ->
+ ?SEV_IPRINT("expected socket down timeout"),
+ ok
+ end
+ end},
+
+ %% Cleanup
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = _State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create several sockets, monitor from a different process then close
+%% socket. The process that did the monitor shall receive a socket DOWN.
+
+monitor_open_and_close_multi_socks(suite) ->
+ [];
+monitor_open_and_close_multi_socks(doc) ->
+ [];
+monitor_open_and_close_multi_socks(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(monitor_open_and_close_multi_socks,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_open_and_close_multi_socks(InitState)
+ end).
+
+
+mon_open_and_close_multi_socks(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket 1",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock1 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 2",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock2 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 3",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock3 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 4",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock4 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 5",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock5 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_READY(Tester, init, Socks),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (close1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close1)
+ end},
+ #{desc => "close socket 1",
+ cmd => fun(#{sock1 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock1, State)}
+ end},
+
+ #{desc => "await continue (close2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close2)
+ end},
+ #{desc => "close socket 2",
+ cmd => fun(#{sock2 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock2, State)}
+ end},
+
+ #{desc => "await continue (close3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close3)
+ end},
+ #{desc => "close socket 3",
+ cmd => fun(#{sock3 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock3, State)}
+ end},
+
+ #{desc => "await continue (close4)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close4)
+ end},
+ #{desc => "close socket 4",
+ cmd => fun(#{sock4 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock4, State)}
+ end},
+
+ #{desc => "await continue (close5)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close5)
+ end},
+ #{desc => "close socket 5",
+ cmd => fun(#{sock5 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock5, State)}
+ end},
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor owner",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, [Sock1, Sock2, Sock3, Sock4, Sock5]} =
+ ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock1 => Sock1,
+ sock2 => Sock2,
+ sock3 => Sock3,
+ sock4 => Sock4,
+ sock5 => Sock5}}
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket",
+ cmd => fun(#{sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = State) ->
+ MRef1 = socket:monitor(Sock1),
+ MRef2 = socket:monitor(Sock2),
+ MRef3 = socket:monitor(Sock3),
+ MRef4 = socket:monitor(Sock4),
+ MRef5 = socket:monitor(Sock5),
+ ?SEV_IPRINT("Monitors:"
+ "~n 1: ~p"
+ "~n 2: ~p"
+ "~n 3: ~p"
+ "~n 4: ~p"
+ "~n 5: ~p",
+ [MRef1, MRef2, MRef3, MRef4, MRef5]),
+ {ok, State#{mon1 => MRef1,
+ mon2 => MRef2,
+ mon3 => MRef3,
+ mon4 => MRef4,
+ mon5 => MRef5}}
+ end},
+ #{desc => "verify total number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "order owner to close socket 1",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close1),
+ ok
+ end},
+ #{desc => "await socket 1 down",
+ cmd => fun(#{sock1 := Sock,
+ mon1 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~s"
+ "~n Info: ~p",
+ [MRef,
+ socket:to_list(Sock),
+ Info]),
+ State2 = maps:remove(sock1, State),
+ State3 = maps:remove(mon1, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=4)",
+ cmd => fun(_State) ->
+ 4 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=4)",
+ cmd => fun(_State) ->
+ 4 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "order owner to close socket 2",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close2),
+ ok
+ end},
+ #{desc => "await socket 2 down",
+ cmd => fun(#{sock2 := Sock,
+ mon2 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~s"
+ "~n Info: ~p",
+ [MRef,
+ socket:to_list(Sock),
+ Info]),
+ State2 = maps:remove(sock2, State),
+ State3 = maps:remove(mon2, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=3)",
+ cmd => fun(_State) ->
+ 3 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=3)",
+ cmd => fun(_State) ->
+ 3 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "order owner to close socket 3",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close3),
+ ok
+ end},
+ #{desc => "await socket 3 down",
+ cmd => fun(#{sock3 := Sock,
+ mon3 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~s"
+ "~n Info: ~p",
+ [MRef,
+ socket:to_list(Sock),
+ Info]),
+ State2 = maps:remove(sock3, State),
+ State3 = maps:remove(mon3, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=2)",
+ cmd => fun(_State) ->
+ 2 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=2)",
+ cmd => fun(_State) ->
+ 2 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "order owner to close socket 4",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close4),
+ ok
+ end},
+ #{desc => "await socket 4 down",
+ cmd => fun(#{sock4 := Sock,
+ mon4 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock4, State),
+ State3 = maps:remove(mon4, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "order owner to close socket 5",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close5),
+ ok
+ end},
+ #{desc => "await socket 5 down",
+ cmd => fun(#{sock5 := Sock,
+ mon5 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock5, State),
+ State3 = maps:remove(mon5, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ %% Cleanup
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = _State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create several sockets, monitor from a different process then exit
+%% the owner process.
+%% The process that did the monitor shall receive a socket DOWN.
+
+monitor_open_and_exit_multi_socks(suite) ->
+ [];
+monitor_open_and_exit_multi_socks(doc) ->
+ [];
+monitor_open_and_exit_multi_socks(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(monitor_open_and_exit_multi_socks,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_open_and_exit_multi_socks(InitState)
+ end).
+
+
+mon_open_and_exit_multi_socks(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket 1",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock1 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 2",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock2 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 3",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock3 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 4",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock4 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 5",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock5 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_READY(Tester, init, Socks),
+ ok
+ end},
+
+
+ %% The actual test
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor owner",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, [Sock1, Sock2, Sock3, Sock4, Sock5]} =
+ ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock1 => Sock1,
+ sock2 => Sock2,
+ sock3 => Sock3,
+ sock4 => Sock4,
+ sock5 => Sock5}}
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket",
+ cmd => fun(#{sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = State) ->
+ MRef1 = socket:monitor(Sock1),
+ MRef2 = socket:monitor(Sock2),
+ MRef3 = socket:monitor(Sock3),
+ MRef4 = socket:monitor(Sock4),
+ MRef5 = socket:monitor(Sock5),
+ ?SEV_IPRINT("Monitors:"
+ "~n 1: ~p"
+ "~n 2: ~p"
+ "~n 3: ~p"
+ "~n 4: ~p"
+ "~n 5: ~p",
+ [MRef1, MRef2, MRef3, MRef4, MRef5]),
+ {ok, State#{mon1 => MRef1,
+ mon2 => MRef2,
+ mon3 => MRef3,
+ mon4 => MRef4,
+ mon5 => MRef5}}
+ end},
+ #{desc => "verify total number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = _State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "await socket 1 down",
+ cmd => fun(#{sock1 := Sock,
+ mon1 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock1, State),
+ State3 = maps:remove(mon1, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "await socket 2 down",
+ cmd => fun(#{sock2 := Sock,
+ mon2 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock2, State),
+ State3 = maps:remove(mon2, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "await socket 3 down",
+ cmd => fun(#{sock3 := Sock,
+ mon3 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock3, State),
+ State3 = maps:remove(mon3, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "await socket 4 down",
+ cmd => fun(#{sock4 := Sock,
+ mon4 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock4, State),
+ State3 = maps:remove(mon4, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "await socket 5 down",
+ cmd => fun(#{sock5 := Sock,
+ mon5 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock5, State),
+ State3 = maps:remove(mon5, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create several sockets, monitor from a different process, demonitor
+%% two of them then close socket.
+%% The process that did the monitor shall receive a socket DOWN for
+%% the sockets that are still monitored.
+
+monitor_open_and_demon_and_close_multi_socks(suite) ->
+ [];
+monitor_open_and_demon_and_close_multi_socks(doc) ->
+ [];
+monitor_open_and_demon_and_close_multi_socks(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(monitor_open_and_demon_and_close_multi_socks,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_open_and_demon_and_close_multi_socks(InitState)
+ end).
+
+
+mon_open_and_demon_and_close_multi_socks(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket 1",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock1 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 2",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock2 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 3",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock3 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 4",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock4 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 5",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock5 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_READY(Tester, init, Socks),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (close1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close1)
+ end},
+ #{desc => "close socket 1",
+ cmd => fun(#{sock1 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock1, State)}
+ end},
+
+ #{desc => "await continue (close3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close3)
+ end},
+ #{desc => "close socket 3",
+ cmd => fun(#{sock3 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock3, State)}
+ end},
+
+ #{desc => "await continue (close5)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close5)
+ end},
+ #{desc => "close socket 5",
+ cmd => fun(#{sock5 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock5, State)}
+ end},
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor owner",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, [Sock1, Sock2, Sock3, Sock4, Sock5]} =
+ ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock1 => Sock1,
+ sock2 => Sock2,
+ sock3 => Sock3,
+ sock4 => Sock4,
+ sock5 => Sock5}}
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket",
+ cmd => fun(#{sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = State) ->
+ MRef1 = socket:monitor(Sock1),
+ MRef2 = socket:monitor(Sock2),
+ MRef3 = socket:monitor(Sock3),
+ MRef4 = socket:monitor(Sock4),
+ MRef5 = socket:monitor(Sock5),
+ ?SEV_IPRINT("Monitors:"
+ "~n 1: ~p"
+ "~n 2: ~p"
+ "~n 3: ~p"
+ "~n 4: ~p"
+ "~n 5: ~p",
+ [MRef1, MRef2, MRef3, MRef4, MRef5]),
+ {ok, State#{mon1 => MRef1,
+ mon2 => MRef2,
+ mon3 => MRef3,
+ mon4 => MRef4,
+ mon5 => MRef5}}
+ end},
+ #{desc => "verify total number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "demonitor socket(s)",
+ cmd => fun(#{mon2 := MRef2,
+ mon4 := MRef4} = State) ->
+ true = socket:cancel_monitor(MRef2),
+ true = socket:cancel_monitor(MRef4),
+ ?SEV_IPRINT("cancel socket monitor 2 and 4"),
+ State2 = maps:remove(mon2, State),
+ State3 = maps:remove(sock2, State2),
+ State4 = maps:remove(mon4, State3),
+ State5 = maps:remove(sock4, State4),
+ {ok, State5}
+ end},
+ #{desc => "verify total number of monitors (=3)",
+ cmd => fun(_State) ->
+ 3 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=3)",
+ cmd => fun(_State) ->
+ 3 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "order owner to close socket 1",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close1),
+ ok
+ end},
+ #{desc => "await socket 1 down",
+ cmd => fun(#{sock1 := Sock,
+ mon1 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock1, State),
+ State3 = maps:remove(mon1, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=2)",
+ cmd => fun(_State) ->
+ 2 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=2)",
+ cmd => fun(_State) ->
+ 2 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "order owner to close socket 3",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close3),
+ ok
+ end},
+ #{desc => "await socket 3 down",
+ cmd => fun(#{sock3 := Sock,
+ mon3 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock3, State),
+ State3 = maps:remove(mon3, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "order owner to close socket 5",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close5),
+ ok
+ end},
+ #{desc => "await socket 5 down",
+ cmd => fun(#{sock5 := Sock,
+ mon5 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(sock5, State),
+ State3 = maps:remove(mon5, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ %% Cleanup
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = _State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create one socket (by 'owner' process), monitor from several different
+%% processes, then close socket (from 'owner').
+%% The processes that did the monitor shall receive a socket DOWN.
+
+monitor_open_and_close_multi_mon(suite) ->
+ [];
+monitor_open_and_close_multi_mon(doc) ->
+ [];
+monitor_open_and_close_multi_mon(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(monitor_open_and_close_multi_mon,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_open_and_close_multi_mon(InitState)
+ end).
+
+
+mon_open_and_close_multi_mon(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close)
+ end},
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+ #{desc => "await continue (socket)",
+ cmd => fun(#{tester := Tester} = State) ->
+ {ok, Sock} =
+ ?SEV_AWAIT_CONTINUE(Tester, tester, socket),
+ ?SEV_IPRINT("Socket: ~p", [Sock]),
+ {ok, State#{sock => Sock}}
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (monitor)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, monitor)
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ MRef = socket:monitor(Sock),
+ ?SEV_IPRINT("Monitor: ~p", [MRef]),
+ {ok, State#{mon => MRef}}
+ end},
+ #{desc => "verify own number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "announce ready (monitor)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, monitor),
+ ok
+ end},
+
+ #{desc => "await continue (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, down)
+ end},
+
+ #{desc => "await socket down",
+ cmd => fun(#{sock := Sock,
+ mon := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon, State),
+ State3 = maps:remove(sock, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "announce ready (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, down),
+ ok
+ end},
+
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor 'owner'",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock => Sock}}
+ end},
+
+ #{desc => "monitor 'client 1'",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 1) start",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 1) ready",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, init)
+ end},
+ #{desc => "send socket to client 1",
+ cmd => fun(#{client1 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+ #{desc => "monitor 'client 2'",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 2) start",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 2) ready",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+ #{desc => "send socket to client 2",
+ cmd => fun(#{client2 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+ #{desc => "monitor 'client 3'",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 3) start",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 3) ready",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, init)
+ end},
+ #{desc => "send socket to client 3",
+ cmd => fun(#{client3 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+ #{desc => "monitor 'client 4'",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 4) start",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 4) ready",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, init)
+ end},
+ #{desc => "send socket to client 4",
+ cmd => fun(#{client4 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+ #{desc => "monitor 'client 5'",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 5) start",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 5) ready",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, init)
+ end},
+ #{desc => "send socket to client 5",
+ cmd => fun(#{client5 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 1 to monitor",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 1) ready",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, monitor)
+ end},
+ #{desc => "verify total number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 2 to monitor",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 2) ready",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, monitor)
+ end},
+ #{desc => "verify total number of monitors (=2)",
+ cmd => fun(_State) ->
+ 2 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 3 to monitor",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 3) ready",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, monitor)
+ end},
+ #{desc => "verify total number of monitors (=3)",
+ cmd => fun(_State) ->
+ 3 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 4 to monitor",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 4) ready",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, monitor)
+ end},
+ #{desc => "verify total number of monitors (=4)",
+ cmd => fun(_State) ->
+ 4 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 5 to monitor",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 5) ready",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, monitor)
+ end},
+ #{desc => "verify total number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(),
+ ok
+ end},
+ #{desc => "verify monitored by - none",
+ cmd => fun(#{sock := Sock,
+ client1 := Pid1,
+ client2 := Pid2,
+ client3 := Pid3,
+ client4 := Pid4,
+ client5 := Pid5} = _State) ->
+ Clients = lists:sort([Pid1, Pid2, Pid3, Pid4, Pid5]),
+ case lists:sort(socket:monitored_by(Sock)) of
+ Clients ->
+ ok;
+ SClients ->
+ ?SEV_EPRINT("Unexpected clients: "
+ "~n Expected: ~p"
+ "~n Actual: ~p",
+ [Clients, SClients]),
+ {error, unexpected_clients}
+ end
+ end},
+
+ #{desc => "order client 1 to await down",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 2 to await down",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 3 to await down",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 4 to await down",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 5 to await down",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+
+ #{desc => "order owner to close",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+
+ #{desc => "await (client 1) down received",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, down)
+ end},
+ #{desc => "await (client 2) down received",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, down)
+ end},
+ #{desc => "await (client 3) down received",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, down)
+ end},
+ #{desc => "await (client 4) down received",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, down)
+ end},
+ #{desc => "await (client 5) down received",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, down)
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+
+ %% Cleanup
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(owner, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 1) terminate",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 1) termination",
+ cmd => fun(#{client1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 2) terminate",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 2) termination",
+ cmd => fun(#{client2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 3) terminate",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 3) termination",
+ cmd => fun(#{client3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 4) terminate",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 4) termination",
+ cmd => fun(#{client4 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client4, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 5) terminate",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 5) termination",
+ cmd => fun(#{client5 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client5, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start client 1 evaluator"),
+ Client1 = ?SEV_START("client-1", ClientSeq, InitState),
+
+ i("start client 2 evaluator"),
+ Client2 = ?SEV_START("client-2", ClientSeq, InitState),
+
+ i("start client 3 evaluator"),
+ Client3 = ?SEV_START("client-3", ClientSeq, InitState),
+
+ i("start client 4 evaluator"),
+ Client4 = ?SEV_START("client-4", ClientSeq, InitState),
+
+ i("start client 5 evaluator"),
+ Client5 = ?SEV_START("client-5", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid,
+ client3 => Client3#ev.pid,
+ client4 => Client4#ev.pid,
+ client5 => Client5#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create one socket (by 'owner' process), monitor from several different
+%% processes, then close socket (from 'owner').
+%% The processes that did the monitor shall receive a socket DOWN.
+
+monitor_open_and_exit_multi_mon(suite) ->
+ [];
+monitor_open_and_exit_multi_mon(doc) ->
+ [];
+monitor_open_and_exit_multi_mon(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(monitor_open_and_exit_multi_mon,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_open_and_exit_multi_mon(InitState)
+ end).
+
+
+mon_open_and_exit_multi_mon(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+ #{desc => "await continue (socket)",
+ cmd => fun(#{tester := Tester} = State) ->
+ {ok, Sock} =
+ ?SEV_AWAIT_CONTINUE(Tester, tester, socket),
+ ?SEV_IPRINT("Socket: ~p", [Sock]),
+ {ok, State#{sock => Sock}}
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (monitor)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, monitor)
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ MRef = socket:monitor(Sock),
+ ?SEV_IPRINT("Monitor: ~p", [MRef]),
+ {ok, State#{mon => MRef}}
+ end},
+ #{desc => "verify own number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "announce ready (monitor)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, monitor),
+ ok
+ end},
+
+ #{desc => "await continue (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, down)
+ end},
+
+ #{desc => "await socket down",
+ cmd => fun(#{sock := Sock,
+ mon := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon, State),
+ State3 = maps:remove(sock, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "announce ready (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, down),
+ ok
+ end},
+
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor 'owner'",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock => Sock}}
+ end},
+
+ #{desc => "monitor 'client 1'",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 1) start",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 1) ready",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, init)
+ end},
+ #{desc => "send socket to client 1",
+ cmd => fun(#{client1 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+ #{desc => "monitor 'client 2'",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 2) start",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 2) ready",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+ #{desc => "send socket to client 2",
+ cmd => fun(#{client2 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+ #{desc => "monitor 'client 3'",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 3) start",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 3) ready",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, init)
+ end},
+ #{desc => "send socket to client 3",
+ cmd => fun(#{client3 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+ #{desc => "monitor 'client 4'",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 4) start",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 4) ready",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, init)
+ end},
+ #{desc => "send socket to client 4",
+ cmd => fun(#{client4 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+ #{desc => "monitor 'client 5'",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 5) start",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 5) ready",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, init)
+ end},
+ #{desc => "send socket to client 5",
+ cmd => fun(#{client5 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Sock),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 1 to monitor",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 1) ready",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, monitor)
+ end},
+ #{desc => "verify total number of monitors (=1)",
+ cmd => fun(_State) ->
+ 1 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 2 to monitor",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 2) ready",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, monitor)
+ end},
+ #{desc => "verify total number of monitors (=2)",
+ cmd => fun(_State) ->
+ 2 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 3 to monitor",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 3) ready",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, monitor)
+ end},
+ #{desc => "verify total number of monitors (=3)",
+ cmd => fun(_State) ->
+ 3 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 4 to monitor",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 4) ready",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, monitor)
+ end},
+ #{desc => "verify total number of monitors (=4)",
+ cmd => fun(_State) ->
+ 4 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 5 to monitor",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 5) ready",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, monitor)
+ end},
+ #{desc => "verify total number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 1 to await down",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 2 to await down",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 3 to await down",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 4 to await down",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 5 to await down",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+
+
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(owner, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "await (client 1) down received",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, down)
+ end},
+ #{desc => "await (client 2) down received",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, down)
+ end},
+ #{desc => "await (client 3) down received",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, down)
+ end},
+ #{desc => "await (client 4) down received",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, down)
+ end},
+ #{desc => "await (client 5) down received",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, down)
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ %% Cleanup
+ #{desc => "order (client 1) terminate",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 1) termination",
+ cmd => fun(#{client1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 2) terminate",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 2) termination",
+ cmd => fun(#{client2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 3) terminate",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 3) termination",
+ cmd => fun(#{client3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 4) terminate",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 4) termination",
+ cmd => fun(#{client4 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client4, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 5) terminate",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 5) termination",
+ cmd => fun(#{client5 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client5, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start client 1 evaluator"),
+ Client1 = ?SEV_START("client-1", ClientSeq, InitState),
+
+ i("start client 2 evaluator"),
+ Client2 = ?SEV_START("client-2", ClientSeq, InitState),
+
+ i("start client 3 evaluator"),
+ Client3 = ?SEV_START("client-3", ClientSeq, InitState),
+
+ i("start client 4 evaluator"),
+ Client4 = ?SEV_START("client-4", ClientSeq, InitState),
+
+ i("start client 5 evaluator"),
+ Client5 = ?SEV_START("client-5", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid,
+ client3 => Client3#ev.pid,
+ client4 => Client4#ev.pid,
+ client5 => Client5#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create several sockets (by 'owner' process), monitor each from several
+%% different processes, then close each socket (from 'owner').
+%% The processes that did the monitor shall receive one socket DOWN for
+%% each socket.
+
+monitor_open_and_close_multi_socks_and_mon(suite) ->
+ [];
+monitor_open_and_close_multi_socks_and_mon(doc) ->
+ [];
+monitor_open_and_close_multi_socks_and_mon(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(monitor_open_and_close_multi_socks_and_mon,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_open_and_close_multi_socks_and_mon(InitState)
+ end).
+
+
+mon_open_and_close_multi_socks_and_mon(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket 1",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock1 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 2",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock2 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 3",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock3 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 4",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock4 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 5",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock5 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_READY(Tester, init, Socks),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (close1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close1)
+ end},
+ #{desc => "close socket 1",
+ cmd => fun(#{sock1 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock1, State)}
+ end},
+
+ #{desc => "await continue (close2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close2)
+ end},
+ #{desc => "close socket 2",
+ cmd => fun(#{sock2 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock2, State)}
+ end},
+
+ #{desc => "await continue (close3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close3)
+ end},
+ #{desc => "close socket 3",
+ cmd => fun(#{sock3 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock3, State)}
+ end},
+
+ #{desc => "await continue (close4)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close4)
+ end},
+ #{desc => "close socket 4",
+ cmd => fun(#{sock4 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock4, State)}
+ end},
+
+ #{desc => "await continue (close5)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close5)
+ end},
+ #{desc => "close socket 5",
+ cmd => fun(#{sock5 := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock5, State)}
+ end},
+
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+ #{desc => "await continue (socket)",
+ cmd => fun(#{tester := Tester} = State) ->
+ {ok, [Sock1, Sock2, Sock3, Sock4, Sock5]} =
+ ?SEV_AWAIT_CONTINUE(Tester, tester, socket),
+ ?SEV_IPRINT("Sockets: "
+ "~n 1: ~p"
+ "~n 2: ~p"
+ "~n 3: ~p"
+ "~n 4: ~p"
+ "~n 5: ~p",
+ [Sock1, Sock2, Sock3, Sock4, Sock5]),
+ {ok, State#{sock1 => Sock1,
+ sock2 => Sock2,
+ sock3 => Sock3,
+ sock4 => Sock4,
+ sock5 => Sock5}}
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (monitor)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, monitor)
+ end},
+
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket",
+ cmd => fun(#{sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = State) ->
+ MRef1 = socket:monitor(Sock1),
+ MRef2 = socket:monitor(Sock2),
+ MRef3 = socket:monitor(Sock3),
+ MRef4 = socket:monitor(Sock4),
+ MRef5 = socket:monitor(Sock5),
+ ?SEV_IPRINT("Monitors: "
+ "~n 1: ~p"
+ "~n 2: ~p"
+ "~n 3: ~p"
+ "~n 4: ~p"
+ "~n 5: ~p",
+ [MRef1, MRef2, MRef3, MRef4, MRef5]),
+ {ok, State#{mon1 => MRef1,
+ mon2 => MRef2,
+ mon3 => MRef3,
+ mon4 => MRef4,
+ mon5 => MRef5}}
+ end},
+ #{desc => "verify own number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+ #{desc => "announce ready (monitor)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, monitor),
+ ok
+ end},
+
+ #{desc => "await continue (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, down)
+ end},
+
+ #{desc => "await socket 1 down",
+ cmd => fun(#{sock1 := Sock,
+ mon1 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon1, State),
+ State3 = maps:remove(sock1, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, down),
+ ok
+ end},
+
+ #{desc => "await socket 2 down",
+ cmd => fun(#{sock2 := Sock,
+ mon2 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon2, State),
+ State3 = maps:remove(sock2, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, down),
+ ok
+ end},
+
+ #{desc => "await socket 3 down",
+ cmd => fun(#{sock3 := Sock,
+ mon3 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon3, State),
+ State3 = maps:remove(sock3, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, down),
+ ok
+ end},
+
+ #{desc => "await socket 4 down",
+ cmd => fun(#{sock4 := Sock,
+ mon4 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon4, State),
+ State3 = maps:remove(sock4, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, down),
+ ok
+ end},
+
+ #{desc => "await socket 5 down",
+ cmd => fun(#{sock5 := Sock,
+ mon5 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon5, State),
+ State3 = maps:remove(sock5, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, down),
+ ok
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor 'owner'",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, [Sock1, Sock2, Sock3, Sock4, Sock5]} =
+ ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock1 => Sock1,
+ sock2 => Sock2,
+ sock3 => Sock3,
+ sock4 => Sock4,
+ sock5 => Sock5}}
+ end},
+
+ #{desc => "monitor 'client 1'",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 1) start",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 1) ready",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, init)
+ end},
+ #{desc => "send socket to client 1",
+ cmd => fun(#{client1 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+ #{desc => "monitor 'client 2'",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 2) start",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 2) ready",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+ #{desc => "send socket to client 2",
+ cmd => fun(#{client2 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+ #{desc => "monitor 'client 3'",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 3) start",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 3) ready",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, init)
+ end},
+ #{desc => "send socket to client 3",
+ cmd => fun(#{client3 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+ #{desc => "monitor 'client 4'",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 4) start",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 4) ready",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, init)
+ end},
+ #{desc => "send socket to client 4",
+ cmd => fun(#{client4 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+ #{desc => "monitor 'client 5'",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 5) start",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 5) ready",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, init)
+ end},
+ #{desc => "send socket to client 5",
+ cmd => fun(#{client5 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 1 to monitor",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 1) ready",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, monitor)
+ end},
+ #{desc => "verify total number of monitors (=1*5)",
+ cmd => fun(_State) ->
+ 1*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 2 to monitor",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 2) ready",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, monitor)
+ end},
+ #{desc => "verify total number of monitors (=2*5)",
+ cmd => fun(_State) ->
+ 2*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 3 to monitor",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 3) ready",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, monitor)
+ end},
+ #{desc => "verify total number of monitors (=3*5)",
+ cmd => fun(_State) ->
+ 3*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 4 to monitor",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 4) ready",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, monitor)
+ end},
+ #{desc => "verify total number of monitors (=4*5)",
+ cmd => fun(_State) ->
+ 4*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 5 to monitor",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 5) ready",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, monitor)
+ end},
+ #{desc => "verify total number of monitors (=5*5)",
+ cmd => fun(_State) ->
+ 5*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 1 to await down",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 2 to await down",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 3 to await down",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 4 to await down",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 5 to await down",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order owner to close socket 1",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close1),
+ ok
+ end},
+ #{desc => "await (client 1) down received",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, down)
+ end},
+ #{desc => "await (client 2) down received",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, down)
+ end},
+ #{desc => "await (client 3) down received",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, down)
+ end},
+ #{desc => "await (client 4) down received",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, down)
+ end},
+ #{desc => "await (client 5) down received",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, down)
+ end},
+ #{desc => "verify total number of monitors (=5*4)",
+ cmd => fun(_State) ->
+ 5*4 = socket:number_of_monitors(),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order owner to close socket 2",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close2),
+ ok
+ end},
+ #{desc => "await (client 1) down received",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, down)
+ end},
+ #{desc => "await (client 2) down received",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, down)
+ end},
+ #{desc => "await (client 3) down received",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, down)
+ end},
+ #{desc => "await (client 4) down received",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, down)
+ end},
+ #{desc => "await (client 5) down received",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, down)
+ end},
+ #{desc => "verify total number of monitors (=5*3)",
+ cmd => fun(_State) ->
+ 5*3 = socket:number_of_monitors(),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order owner to close socket 3",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close3),
+ ok
+ end},
+ #{desc => "await (client 1) down received",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, down)
+ end},
+ #{desc => "await (client 2) down received",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, down)
+ end},
+ #{desc => "await (client 3) down received",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, down)
+ end},
+ #{desc => "await (client 4) down received",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, down)
+ end},
+ #{desc => "await (client 5) down received",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, down)
+ end},
+ #{desc => "verify total number of monitors (=5*2)",
+ cmd => fun(_State) ->
+ 5*2 = socket:number_of_monitors(),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order owner to close socket 4",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close4),
+ ok
+ end},
+ #{desc => "await (client 1) down received",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, down)
+ end},
+ #{desc => "await (client 2) down received",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, down)
+ end},
+ #{desc => "await (client 3) down received",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, down)
+ end},
+ #{desc => "await (client 4) down received",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, down)
+ end},
+ #{desc => "await (client 5) down received",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, down)
+ end},
+ #{desc => "verify total number of monitors (=5*1)",
+ cmd => fun(_State) ->
+ 5*1 = socket:number_of_monitors(),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order owner to close socket 5",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close5),
+ ok
+ end},
+ #{desc => "await (client 1) down received",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, down)
+ end},
+ #{desc => "await (client 2) down received",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, down)
+ end},
+ #{desc => "await (client 3) down received",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, down)
+ end},
+ #{desc => "await (client 4) down received",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, down)
+ end},
+ #{desc => "await (client 5) down received",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, down)
+ end},
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% Cleanup
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(owner, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 1) terminate",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 1) termination",
+ cmd => fun(#{client1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 2) terminate",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 2) termination",
+ cmd => fun(#{client2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 3) terminate",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 3) termination",
+ cmd => fun(#{client3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 4) terminate",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 4) termination",
+ cmd => fun(#{client4 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client4, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 5) terminate",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 5) termination",
+ cmd => fun(#{client5 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client5, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start client 1 evaluator"),
+ Client1 = ?SEV_START("client-1", ClientSeq, InitState),
+
+ i("start client 2 evaluator"),
+ Client2 = ?SEV_START("client-2", ClientSeq, InitState),
+
+ i("start client 3 evaluator"),
+ Client3 = ?SEV_START("client-3", ClientSeq, InitState),
+
+ i("start client 4 evaluator"),
+ Client4 = ?SEV_START("client-4", ClientSeq, InitState),
+
+ i("start client 5 evaluator"),
+ Client5 = ?SEV_START("client-5", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid,
+ client3 => Client3#ev.pid,
+ client4 => Client4#ev.pid,
+ client5 => Client5#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create several sockets (by 'owner' process), monitor each from several
+%% different processes, then exit the owner.
+%% The processes that did the monitor shall receive one socket DOWN for
+%% each socket.
+
+monitor_open_and_exit_multi_socks_and_mon(suite) ->
+ [];
+monitor_open_and_exit_multi_socks_and_mon(doc) ->
+ [];
+monitor_open_and_exit_multi_socks_and_mon(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(monitor_open_and_exit_multi_socks_and_mon,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = mon_open_and_exit_multi_socks_and_mon(InitState)
+ end).
+
+
+mon_open_and_exit_multi_socks_and_mon(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "create socket 1",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock1 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 2",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock2 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 3",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock3 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 4",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock4 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create socket 5",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock5 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_READY(Tester, init, Socks),
+ ok
+ end},
+
+ %% The actual test
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ 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 => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+ #{desc => "await continue (socket)",
+ cmd => fun(#{tester := Tester} = State) ->
+ {ok, [Sock1, Sock2, Sock3, Sock4, Sock5]} =
+ ?SEV_AWAIT_CONTINUE(Tester, tester, socket),
+ ?SEV_IPRINT("Sockets: "
+ "~n 1: ~p"
+ "~n 2: ~p"
+ "~n 3: ~p"
+ "~n 4: ~p"
+ "~n 5: ~p",
+ [Sock1, Sock2, Sock3, Sock4, Sock5]),
+ {ok, State#{sock1 => Sock1,
+ sock2 => Sock2,
+ sock3 => Sock3,
+ sock4 => Sock4,
+ sock5 => Sock5}}
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (monitor)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, monitor)
+ end},
+
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "monitor socket",
+ cmd => fun(#{sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = State) ->
+ MRef1 = socket:monitor(Sock1),
+ MRef2 = socket:monitor(Sock2),
+ MRef3 = socket:monitor(Sock3),
+ MRef4 = socket:monitor(Sock4),
+ MRef5 = socket:monitor(Sock5),
+ ?SEV_IPRINT("Monitors: "
+ "~n 1: ~p"
+ "~n 2: ~p"
+ "~n 3: ~p"
+ "~n 4: ~p"
+ "~n 5: ~p",
+ [MRef1, MRef2, MRef3, MRef4, MRef5]),
+ {ok, State#{mon1 => MRef1,
+ mon2 => MRef2,
+ mon3 => MRef3,
+ mon4 => MRef4,
+ mon5 => MRef5}}
+ end},
+ #{desc => "verify own number of monitors (=5)",
+ cmd => fun(_State) ->
+ 5 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "announce ready (monitor)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, monitor),
+ ok
+ end},
+
+ #{desc => "await continue (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, down)
+ end},
+
+ #{desc => "await socket 1 down",
+ cmd => fun(#{sock1 := Sock,
+ mon1 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon1, State),
+ State3 = maps:remove(sock1, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+
+ #{desc => "await socket 2 down",
+ cmd => fun(#{sock2 := Sock,
+ mon2 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon2, State),
+ State3 = maps:remove(sock2, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+
+ #{desc => "await socket 3 down",
+ cmd => fun(#{sock3 := Sock,
+ mon3 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon3, State),
+ State3 = maps:remove(sock3, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+
+ #{desc => "await socket 4 down",
+ cmd => fun(#{sock4 := Sock,
+ mon4 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon4, State),
+ State3 = maps:remove(sock4, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+
+ #{desc => "await socket 5 down",
+ cmd => fun(#{sock5 := Sock,
+ mon5 := MRef} = State) ->
+ receive
+ {'DOWN', MRef, socket, Sock, Info} ->
+ ?SEV_IPRINT("received expected down: "
+ "~n MRef: ~p"
+ "~n Socket: ~p"
+ "~n Info: ~p",
+ [MRef, Sock, Info]),
+ State2 = maps:remove(mon5, State),
+ State3 = maps:remove(sock5, State2),
+ {ok, State3}
+ after 5000 ->
+ ?SEV_EPRINT("socket down timeout"),
+ {error, timeout}
+ end
+ end},
+ #{desc => "verify own number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(self()),
+ ok
+ end},
+ #{desc => "announce ready (down)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, down),
+ ok
+ end},
+
+
+ #{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},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor 'owner'",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, [Sock1, Sock2, Sock3, Sock4, Sock5]} =
+ ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock1 => Sock1,
+ sock2 => Sock2,
+ sock3 => Sock3,
+ sock4 => Sock4,
+ sock5 => Sock5}}
+ end},
+
+ #{desc => "monitor 'client 1'",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 1) start",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 1) ready",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, init)
+ end},
+ #{desc => "send socket to client 1",
+ cmd => fun(#{client1 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+ #{desc => "monitor 'client 2'",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 2) start",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 2) ready",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+ #{desc => "send socket to client 2",
+ cmd => fun(#{client2 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+ #{desc => "monitor 'client 3'",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 3) start",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 3) ready",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, init)
+ end},
+ #{desc => "send socket to client 3",
+ cmd => fun(#{client3 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+ #{desc => "monitor 'client 4'",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 4) start",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 4) ready",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, init)
+ end},
+ #{desc => "send socket to client 4",
+ cmd => fun(#{client4 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+ #{desc => "monitor 'client 5'",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order (client 5) start",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (client 5) ready",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, init)
+ end},
+ #{desc => "send socket to client 5",
+ cmd => fun(#{client5 := Pid,
+ sock1 := Sock1,
+ sock2 := Sock2,
+ sock3 := Sock3,
+ sock4 := Sock4,
+ sock5 := Sock5} = _State) ->
+ Socks = [Sock1, Sock2, Sock3, Sock4, Sock5],
+ ?SEV_ANNOUNCE_CONTINUE(Pid, socket, Socks),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 1 to monitor",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 1) ready",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, monitor)
+ end},
+ #{desc => "verify total number of monitors (=1*5)",
+ cmd => fun(_State) ->
+ 1*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 2 to monitor",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 2) ready",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, monitor)
+ end},
+ #{desc => "verify total number of monitors (=2*5)",
+ cmd => fun(_State) ->
+ 2*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 3 to monitor",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 3) ready",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, monitor)
+ end},
+ #{desc => "verify total number of monitors (=3*5)",
+ cmd => fun(_State) ->
+ 3*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 4 to monitor",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 4) ready",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, monitor)
+ end},
+ #{desc => "verify total number of monitors (=4*5)",
+ cmd => fun(_State) ->
+ 4*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 5 to monitor",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, monitor),
+ ok
+ end},
+ #{desc => "await (client 5) ready",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, monitor)
+ end},
+ #{desc => "verify total number of monitors (=5*5)",
+ cmd => fun(_State) ->
+ 5*5 = socket:number_of_monitors(),
+ ok
+ end},
+
+ #{desc => "order client 1 to await down",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 2 to await down",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 3 to await down",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 4 to await down",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+ #{desc => "order client 5 to await down",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, down),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(owner, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "await (client 1) ready",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, down)
+ end},
+ #{desc => "await (client 2) ready",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, down)
+ end},
+ #{desc => "await (client 3) ready",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, down)
+ end},
+ #{desc => "await (client 4) ready",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client4, down)
+ end},
+ #{desc => "await (client 5) ready",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client5, down)
+ end},
+
+ #{desc => "verify total number of monitors (=0)",
+ cmd => fun(_State) ->
+ 0 = socket:number_of_monitors(),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% Cleanup
+ #{desc => "order (client 1) terminate",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 1) termination",
+ cmd => fun(#{client1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 2) terminate",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 2) termination",
+ cmd => fun(#{client2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 3) terminate",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 3) termination",
+ cmd => fun(#{client3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 4) terminate",
+ cmd => fun(#{client4 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 4) termination",
+ cmd => fun(#{client4 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client4, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order (client 5) terminate",
+ cmd => fun(#{client5 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (client 5) termination",
+ cmd => fun(#{client5 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client5, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start client 1 evaluator"),
+ Client1 = ?SEV_START("client-1", ClientSeq, InitState),
+
+ i("start client 2 evaluator"),
+ Client2 = ?SEV_START("client-2", ClientSeq, InitState),
+
+ i("start client 3 evaluator"),
+ Client3 = ?SEV_START("client-3", ClientSeq, InitState),
+
+ i("start client 4 evaluator"),
+ Client4 = ?SEV_START("client-4", ClientSeq, InitState),
+
+ i("start client 5 evaluator"),
+ Client5 = ?SEV_START("client-5", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid,
+ client3 => Client3#ev.pid,
+ client4 => Client4#ev.pid,
+ client5 => Client5#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
%% SOCKET CLOSURE %%
%% %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%