aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bootstrap/lib/compiler/ebin/compile.beambin38976 -> 39028 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet.beambin24024 -> 24048 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/local_tcp.beambin2352 -> 2396 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/local_udp.beambin1436 -> 1476 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_parse.beambin82864 -> 82652 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_pp.beambin27344 -> 26920 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/supervisor.beambin23348 -> 23616 bytes
-rw-r--r--erts/doc/src/absform.xml4
-rw-r--r--erts/doc/src/erl_nif.xml34
-rw-r--r--erts/doc/src/erlang.xml239
-rw-r--r--erts/emulator/beam/bif.c410
-rw-r--r--erts/emulator/beam/erl_bif_info.c100
-rw-r--r--erts/emulator/beam/erl_bif_port.c6
-rw-r--r--erts/emulator/beam/erl_port.h58
-rw-r--r--erts/emulator/beam/erl_process.c21
-rw-r--r--erts/emulator/beam/io.c390
-rw-r--r--erts/emulator/beam/register.c51
-rw-r--r--erts/emulator/beam/register.h2
-rw-r--r--erts/emulator/test/monitor_SUITE.erl12
-rw-r--r--erts/emulator/test/port_SUITE.erl434
-rw-r--r--erts/emulator/test/port_SUITE_data/Makefile.src2
-rw-r--r--erts/emulator/test/port_SUITE_data/sleep_failure_drv.c76
-rw-r--r--erts/preloaded/src/erlang.erl19
-rw-r--r--lib/common_test/doc/src/ct.xml2
-rw-r--r--lib/common_test/src/ct_logs.erl5
-rw-r--r--lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl68
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE.erl32
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl9
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl15
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ns.erl2
-rw-r--r--lib/common_test/test/ct_test_support.erl6
-rw-r--r--lib/common_test/test/telnet_server.erl42
-rw-r--r--lib/dialyzer/src/dialyzer_contracts.erl161
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl13
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl122
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/exact2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/guard_update2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/map_in_guard22
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/typeflow2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/typesig4
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/maps12
-rw-r--r--lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl11
-rw-r--r--lib/diameter/src/base/diameter_config.erl125
-rw-r--r--lib/diameter/src/base/diameter_config_sup.erl58
-rw-r--r--lib/diameter/src/base/diameter_lib.erl33
-rw-r--r--lib/diameter/src/base/diameter_reg.erl423
-rw-r--r--lib/diameter/src/base/diameter_service.erl49
-rw-r--r--lib/diameter/src/base/diameter_session.erl5
-rw-r--r--lib/diameter/src/base/diameter_sup.erl3
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl4
-rw-r--r--lib/diameter/src/modules.mk3
-rw-r--r--lib/diameter/src/transport/diameter_sctp.erl37
-rw-r--r--lib/diameter/src/transport/diameter_tcp.erl31
-rw-r--r--lib/diameter/test/diameter_codec_test.erl13
-rw-r--r--lib/diameter/test/diameter_gen_sctp_SUITE.erl6
-rw-r--r--lib/diameter/test/diameter_reg_SUITE.erl19
-rw-r--r--lib/diameter/test/diameter_relay_SUITE.erl12
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl6
-rw-r--r--lib/diameter/test/diameter_transport_SUITE.erl9
-rw-r--r--lib/diameter/test/diameter_util.erl24
-rw-r--r--lib/edoc/src/edoc_layout.erl16
-rw-r--r--lib/edoc/src/edoc_parser.yrl6
-rw-r--r--lib/erl_docgen/src/docgen_otp_specs.erl16
-rw-r--r--lib/hipe/cerl/erl_types.erl904
-rw-r--r--lib/inets/test/ftp_SUITE.erl63
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap2.xmlsrc2
-rw-r--r--lib/stdlib/doc/src/erl_id_trans.xml3
-rw-r--r--lib/stdlib/doc/src/erl_parse.xml8
-rw-r--r--lib/stdlib/src/erl_expand_records.erl5
-rw-r--r--lib/stdlib/src/erl_lint.erl8
-rw-r--r--lib/stdlib/src/erl_parse.yrl39
-rw-r--r--lib/stdlib/src/erl_pp.erl24
-rw-r--r--lib/stdlib/src/lib.erl2
-rw-r--r--lib/stdlib/src/ms_transform.erl7
-rw-r--r--lib/stdlib/src/proplists.erl5
-rw-r--r--lib/stdlib/src/qlc.erl7
-rw-r--r--lib/stdlib/src/qlc_pt.erl18
-rw-r--r--lib/stdlib/src/sofs.erl3
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl5
-rw-r--r--lib/syntax_tools/src/Makefile2
-rw-r--r--lib/syntax_tools/src/erl_prettypr.erl39
-rw-r--r--lib/syntax_tools/src/erl_recomment.erl8
-rw-r--r--lib/syntax_tools/src/erl_syntax.erl9
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl9
-rw-r--r--lib/tools/src/xref_base.erl215
-rw-r--r--lib/tools/src/xref_utils.erl8
-rw-r--r--system/doc/reference_manual/typespec.xml18
87 files changed, 3042 insertions, 1627 deletions
diff --git a/bootstrap/lib/compiler/ebin/compile.beam b/bootstrap/lib/compiler/ebin/compile.beam
index b309544c39..8003d590c9 100644
--- a/bootstrap/lib/compiler/ebin/compile.beam
+++ b/bootstrap/lib/compiler/ebin/compile.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet.beam b/bootstrap/lib/kernel/ebin/inet.beam
index 8eaf2fbe74..b771df5608 100644
--- a/bootstrap/lib/kernel/ebin/inet.beam
+++ b/bootstrap/lib/kernel/ebin/inet.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/local_tcp.beam b/bootstrap/lib/kernel/ebin/local_tcp.beam
index 153aad2936..9ff39fd7fa 100644
--- a/bootstrap/lib/kernel/ebin/local_tcp.beam
+++ b/bootstrap/lib/kernel/ebin/local_tcp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/local_udp.beam b/bootstrap/lib/kernel/ebin/local_udp.beam
index 29c9b01faa..44a4bee902 100644
--- a/bootstrap/lib/kernel/ebin/local_udp.beam
+++ b/bootstrap/lib/kernel/ebin/local_udp.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_parse.beam b/bootstrap/lib/stdlib/ebin/erl_parse.beam
index 9b04739d58..33a0753965 100644
--- a/bootstrap/lib/stdlib/ebin/erl_parse.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_parse.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_pp.beam b/bootstrap/lib/stdlib/ebin/erl_pp.beam
index b3343c9ccb..012c28ac81 100644
--- a/bootstrap/lib/stdlib/ebin/erl_pp.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_pp.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/supervisor.beam b/bootstrap/lib/stdlib/ebin/supervisor.beam
index ba6a64d594..3ae911ab29 100644
--- a/bootstrap/lib/stdlib/ebin/supervisor.beam
+++ b/bootstrap/lib/stdlib/ebin/supervisor.beam
Binary files differ
diff --git a/erts/doc/src/absform.xml b/erts/doc/src/absform.xml
index bfabb7f042..0b04f8f70e 100644
--- a/erts/doc/src/absform.xml
+++ b/erts/doc/src/absform.xml
@@ -152,9 +152,11 @@
<list type="bulleted">
<item>If L is an atom literal, then
Rep(L) = <c>{atom,LINE,L}</c>.</item>
+ <item>If L is a character literal, then
+ Rep(L) = <c>{char,LINE,L}</c>.</item>
<item>If L is a float literal, then
Rep(L) = <c>{float,LINE,L}</c>.</item>
- <item>If L is an integer or character literal, then
+ <item>If L is an integer literal, then
Rep(L) = <c>{integer,LINE,L}</c>.</item>
<item>If L is a string literal consisting of the characters
<c>C_1</c>, ..., <c>C_k</c>, then
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml
index b2e2254a65..123d353432 100644
--- a/erts/doc/src/erl_nif.xml
+++ b/erts/doc/src/erl_nif.xml
@@ -1812,23 +1812,23 @@ enif_map_iterator_destroy(env, &amp;iter);
</p></desc>
</func>
<func><name><ret>int</ret><nametext>enif_thread_type(void)</nametext></name>
- <fsummary>Determine type of current thread</fsummary>
- <desc>
- <p>Determine the type of currently executing thread. A positive value
- indicates a scheduler thread while a negative value or zero indicates
- another type of thread. Currently the following specific types exist
- (which may be extended in the future):</p>
- <taglist>
- <tag><c>ERL_NIF_THR_UNDEFINED</c></tag>
- <value><p>Undefined thread that is not a scheduler thread.</p></value>
- <tag><c>ERL_NIF_THR_NORMAL_SCHEDULER</c></tag>
- <value><p>A normal scheduler thread.</p></value>
- <tag><c>ERL_NIF_THR_DIRTY_CPU_SCHEDULER</c></tag>
- <value><p>A dirty CPU scheduler thread.</p></value>
- <tag><c>ERL_NIF_THR_DIRTY_IO_SCHEDULER</c></tag>
- <value><p>A dirty I/O scheduler thread.</p></value>
- </taglist>
- </desc>
+ <fsummary>Determine type of current thread</fsummary>
+ <desc>
+ <p>Determine the type of currently executing thread. A positive value
+ indicates a scheduler thread while a negative value or zero indicates
+ another type of thread. Currently the following specific types exist
+ (which may be extended in the future):</p>
+ <taglist>
+ <tag><c>ERL_NIF_THR_UNDEFINED</c></tag>
+ <item><p>Undefined thread that is not a scheduler thread.</p></item>
+ <tag><c>ERL_NIF_THR_NORMAL_SCHEDULER</c></tag>
+ <item><p>A normal scheduler thread.</p></item>
+ <tag><c>ERL_NIF_THR_DIRTY_CPU_SCHEDULER</c></tag>
+ <item><p>A dirty CPU scheduler thread.</p></item>
+ <tag><c>ERL_NIF_THR_DIRTY_IO_SCHEDULER</c></tag>
+ <item><p>A dirty I/O scheduler thread.</p></item>
+ </taglist>
+ </desc>
</func>
<func>
<name><ret>ErlNifTime</ret><nametext>enif_time_offset(ErlNifTimeUnit time_unit)</nametext></name>
diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml
index e0c3fed0c2..fa13e4c142 100644
--- a/erts/doc/src/erlang.xml
+++ b/erts/doc/src/erlang.xml
@@ -2916,107 +2916,105 @@ os_prompt% </pre>
<func>
<name name="monitor" arity="2" clause_i="1"/>
<name name="monitor" arity="2" clause_i="2"/>
+ <name name="monitor" arity="2" clause_i="3"/>
<fsummary>Starts monitoring.</fsummary>
<type name="registered_name"/>
<type name="registered_process_identifier"/>
<type name="monitor_process_identifier"/>
+ <type name="monitor_port_identifier"/>
<desc>
- <p>Send a monitor request of type <c><anno>Type</anno></c> to the
- entity identified by <c><anno>Item</anno></c>. The caller of
- <c>monitor/2</c> will later be notified by a monitor message on the
- following format if the monitored state is changed:</p>
+ <p>Sends a monitor request of type <c><anno>Type</anno></c> to the
+ entity identified by <c><anno>Item</anno></c>. If the monitored entity
+ does not exist or when it dies, the caller of <c>monitor/2</c> will
+ be notified by a message on the following format:</p>
<code type="none">{Tag, <anno>MonitorRef</anno>, <anno>Type</anno>, Object, Info}</code>
<note><p>The monitor request is an asynchronous signal. That is, it
takes time before the signal reaches its destination.</p></note>
- <p>Valid <c><anno>Type</anno></c>s:</p>
- <taglist>
- <tag><marker id="monitor_process"/><c>process</c></tag>
- <item>
- <p>Monitor the existence of the process identified by
- <c><anno>Item</anno></c>. Valid
- <c><anno>Item</anno></c>s in combination with the
- <c>process <anno>Type</anno></c> can be any of the following:</p>
- <taglist>
- <tag><c>pid()</c></tag>
- <item>
- <p>The process identifier of the process to monitor.</p>
- </item>
- <tag><c>{RegisteredName, Node}</c></tag>
- <item>
- <p>A tuple consisting of a registered name of a process and
- a node name. The process residing on the node <c>Node</c>
- with the registered name <c>{RegisteredName, Node}</c> will
- be monitored.</p>
- </item>
- <tag><c>RegisteredName</c></tag>
- <item>
- <p>The process locally registered as <c>RegisteredName</c>
- will become monitored.</p>
- </item>
- </taglist>
- <note><p>When a registered name is used, the
- process that has the registered name when the
- monitor request reach its destination will be monitored.
- The monitor is not effected if the registered name is
- unregistered, or unregistered and later registered on another
- process.</p></note>
- <p>The monitor is triggered either when the monitored process
- terminates, is non existing, or if the connection to it is
- lost. In the case the connection to it is lost, we do not know
- if it still exist or not. After this type of monitor has been
- triggered, the monitor is automatically removed.</p>
- <p>When the monitor is triggered a <c>'DOWN'</c> message is
- sent to the monitoring process. A <c>'DOWN'</c> message has
- the following pattern:</p>
- <code type="none">{'DOWN', MonitorRef, Type, Object, Info}</code>
- <p>Here <c>MonitorRef</c> and <c>Type</c> are the same as
- described earlier, and:</p>
- <taglist>
- <tag><c>Object</c></tag>
- <item>
- <p>equals:</p>
- <taglist>
- <tag><c><anno>Item</anno></c></tag>
- <item>If <c><anno>Item</anno></c> is specified by a
- process identifier.</item>
- <tag><c>{RegisteredName, Node}</c></tag>
- <item>If <c><anno>Item</anno></c> is specified as
- <c>RegisteredName</c>, or <c>{RegisteredName, Node}</c>
- where <c>Node</c> corresponds to the node that the
- monitored process resides on.</item>
- </taglist>
- </item>
- <tag><c>Info</c></tag>
- <item>
- <p>Either the exit reason of the process, <c>noproc</c>
- (non-existing process), or <c>noconnection</c> (no
- connection to the node where the monitored process
- resides).</p></item>
- </taglist>
- <p>The monitoring is turned off when the <c>'DOWN'</c>
- message is sent or when
- <seealso marker="#demonitor/1">demonitor/1</seealso>
- is called.</p>
- <p>If an attempt is made to monitor a process on an older node
- (where remote process monitoring is not implemented or
- where remote process monitoring by registered name is not
- implemented), the call fails with <c>badarg</c>.</p>
- <note>
- <p>The format of the <c>'DOWN'</c> message changed in ERTS
- version 5.2 (OTP R9B) for monitoring
- <em>by registered name</em>. Element <c>Object</c> of
- the <c>'DOWN'</c> message could in earlier versions
- sometimes be the process identifier of the monitored process and sometimes
- be the registered name. Now element <c>Object</c> is
- always a tuple consisting of the registered name and
- the node name. Processes on new nodes (ERTS version 5.2
- or higher) always get <c>'DOWN'</c> messages on
- the new format even if they are monitoring processes on old
- nodes. Processes on old nodes always get <c>'DOWN'</c>
- messages on the old format.</p>
- </note>
- </item>
- <tag><marker id="monitor_time_offset"/><c>time_offset</c></tag>
+
+ <p><c><anno>Type</anno></c> can be one of the following atoms:
+ <c>process</c>, <c>port</c> or <c>time_offset</c>.</p>
+
+ <p>A monitor is triggered only once, after that it is removed from
+ both monitoring process and the monitored entity.
+ Monitors are fired when the monitored process or port terminates,
+ does not exist at the moment of creation, or if the connection to
+ it is lost. In the case with connection, we lose knowledge about
+ the fact if it still exists or not. The monitoring is also turned off
+ when <seealso marker="#demonitor/1">demonitor/1</seealso>
+ is called.</p>
+
+ <p>When monitoring by name please note, that the <c>RegisteredName</c>
+ is resolved to <c>pid()</c> or <c>port()</c> only once
+ at the moment of monitor instantiation, later changes to the name
+ registration will not affect the existing monitor.</p>
+
+ <p>When a monitor is triggered, a <c>'DOWN'</c> message that has the
+ following pattern <c>{'DOWN', MonitorRef, Type, Object, Info}</c>
+ is sent to the monitoring process.</p>
+
+ <p>In 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, which triggered the event. When monitoring
+ a local process or port, <c>Object</c> will be equal to the
+ <c>pid()</c> or <c>port()</c> that was being monitored. When
+ monitoring process or port by name, <c>Object</c> will have format
+ <c>{RegisteredName, Node}</c> where <c>RegisteredName</c> is the
+ name which has been used with <c>monitor/2</c> call and
+ <c>Node</c> is local or remote node name (for ports monitored by
+ name, <c>Node</c> is always local node name).</p>
+ </item>
+ <tag><c>Info</c></tag>
+ <item>
+ <p>Either the exit reason of the process, <c>noproc</c>
+ (process or port did not exist at the time of monitor creation),
+ or <c>noconnection</c> (no connection to the node where the
+ monitored process resides). </p></item>
+ </taglist>
+
+ <p>If an attempt is made to monitor a process on an older node
+ (where remote process monitoring is not implemented or
+ where remote process monitoring by registered name is not
+ implemented), the call fails with <c>badarg</c>.</p>
+ <note>
+ <p>The format of the <c>'DOWN'</c> message changed in ERTS
+ version 5.2 (OTP R9B) for monitoring
+ <em>by registered name</em>. Element <c>Object</c> of
+ the <c>'DOWN'</c> message could in earlier versions
+ sometimes be the process identifier of the monitored process and sometimes
+ be the registered name. Now element <c>Object</c> is
+ always a tuple consisting of the registered name and
+ the node name. Processes on new nodes (ERTS version 5.2
+ or higher) always get <c>'DOWN'</c> messages on
+ the new format even if they are monitoring processes on old
+ nodes. Processes on old nodes always get <c>'DOWN'</c>
+ messages on the old format.</p>
+ </note>
+
+ <taglist>
+ <tag>Monitoring a <marker id="monitor_process"/><c>process</c></tag>
+ <item>
+ <p>Creates monitor between the current process and another
+ process identified by <c><anno>Item</anno></c>, which can be a
+ <c>pid()</c> (local or remote), an atom <c>RegisteredName</c> or
+ a tuple <c>{RegisteredName, Node}</c> for a registered process,
+ located elsewhere.</p>
+ </item>
+
+ <tag>Monitoring a <marker id="monitor_port"/><c>port</c></tag>
+ <item>
+ <p>Creates monitor between the current process and a port
+ identified by <c><anno>Item</anno></c>, which can be a
+ <c>port()</c> (only local), an atom <c>RegisteredName</c> or
+ a tuple <c>{RegisteredName, Node}</c> for a registered port,
+ located on this node. Note, that attempt to monitor a remote port
+ will result in <c>badarg</c>.</p>
+ </item>
+
+ <tag>Monitoring a
+ <marker id="monitor_time_offset"/><c>time_offset</c></tag>
<item>
<p>Monitor changes in
<seealso marker="#time_offset/0">time offset</seealso>
@@ -3072,15 +3070,17 @@ os_prompt% </pre>
Note that you can observe the change of the time offset
when calling <c>erlang:time_offset()</c> before you
get the <c>'CHANGE'</c> message.</p>
-
</item>
</taglist>
+
<p>Making several calls to <c>monitor/2</c> for the same
- <c><anno>Item</anno></c> and/or <c><anno>Type</anno></c> is not
- an error; it results in as many independent monitoring instances.</p>
+ <c><anno>Item</anno></c> and/or <c><anno>Type</anno></c> is not
+ an error; it results in as many independent monitoring instances.</p>
+
<p>The monitor functionality is expected to be extended. That is,
- other <c><anno>Type</anno></c>s and <c><anno>Item</anno></c>s
- are expected to be supported in a future release.</p>
+ other <c><anno>Type</anno></c>s and <c><anno>Item</anno></c>s
+ are expected to be supported in a future release.</p>
+
<note>
<p>If or when <c>monitor/2</c> is extended, other
possible values for <c>Tag</c>, <c>Object</c> and
@@ -4150,6 +4150,22 @@ os_prompt% </pre>
<func>
<name name="port_info" arity="2" clause_i="8"/>
+ <fsummary>Which processes are monitoring this port.</fsummary>
+ <desc>
+ <p>Returns list of pids that are monitoring given port at the
+ moment.</p>
+ <p>If the port identified by <c><anno>Port</anno></c> is not open,
+ <c>undefined</c> is returned. If the port is closed and the
+ calling process was previously linked to the port, the exit
+ signal from the port is guaranteed to be delivered before
+ <c>port_info/2</c> returns <c>undefined</c>.</p>
+ <p>Failure: <c>badarg</c> if <c><anno>Port</anno></c> is not a local
+ port identifier, or an atom.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="port_info" arity="2" clause_i="9"/>
<fsummary>Information about the name of a port.</fsummary>
<desc>
<p><c><anno>Name</anno></c> is the command name set by
@@ -4165,7 +4181,7 @@ os_prompt% </pre>
</func>
<func>
- <name name="port_info" arity="2" clause_i="9"/>
+ <name name="port_info" arity="2" clause_i="10"/>
<fsummary>Information about the OS pid of a port.</fsummary>
<desc>
<p><c><anno>OsPid</anno></c> is the process identifier (or equivalent)
@@ -4184,7 +4200,7 @@ os_prompt% </pre>
</func>
<func>
- <name name="port_info" arity="2" clause_i="10"/>
+ <name name="port_info" arity="2" clause_i="11"/>
<fsummary>Information about the output of a port.</fsummary>
<desc>
<p><c><anno>Bytes</anno></c> is the total number of bytes written
@@ -4203,7 +4219,7 @@ os_prompt% </pre>
</func>
<func>
- <name name="port_info" arity="2" clause_i="11"/>
+ <name name="port_info" arity="2" clause_i="12"/>
<fsummary>Information about the parallelism hint of a port.</fsummary>
<desc>
<p><c><anno>Boolean</anno></c> corresponds to the port parallelism
@@ -4214,7 +4230,7 @@ os_prompt% </pre>
</func>
<func>
- <name name="port_info" arity="2" clause_i="12"/>
+ <name name="port_info" arity="2" clause_i="13"/>
<fsummary>Information about the queue size of a port.</fsummary>
<desc>
<p><c><anno>Bytes</anno></c> is the total number
@@ -4231,7 +4247,7 @@ os_prompt% </pre>
</func>
<func>
- <name name="port_info" arity="2" clause_i="13"/>
+ <name name="port_info" arity="2" clause_i="14"/>
<fsummary>Information about the registered name of a port.</fsummary>
<desc>
<p><c><anno>RegisteredName</anno></c> is the registered name of
@@ -4865,10 +4881,19 @@ os_prompt% </pre>
<p>A list of monitors (started by <c>monitor/2</c>)
that are active for the process. For a local process
monitor or a remote process monitor by a process
- identifier, the list item is <c>{process, <anno>Pid</anno>}</c>.
- For a remote process
- monitor by name, the list item is
- <c>{process, {<anno>RegName</anno>, <anno>Node</anno>}}</c>.</p>
+ identifier, the list consists of:</p>
+ <taglist>
+ <tag><c>{process, <anno>Pid</anno>}</c></tag>
+ <item>Process is monitored by pid.</item>
+ <tag><c>{process, {<anno>RegName</anno>, <anno>Node</anno>}}</c></tag>
+ <item>Local or remote process is monitored by name.</item>
+ <tag><c>{port, PortId}</c></tag>
+ <item>Local port is monitored by port id.</item>
+ <tag><c>{port, {<anno>RegName</anno>, <anno>Node</anno>}}</c></tag>
+ <item>Local port is monitored by name. Please note, that
+ remote port monitors are not supported, so <c>Node</c> will
+ always be the local node name.</item>
+ </taglist>
</item>
<tag><c>{message_queue_data, <anno>MQD</anno>}</c></tag>
<item>
diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c
index b18910e2c7..fc14061a44 100644
--- a/erts/emulator/beam/bif.c
+++ b/erts/emulator/beam/bif.c
@@ -282,20 +282,17 @@ res_no_proc: {
}
}
-#define ERTS_DEMONITOR_FALSE 2
-#define ERTS_DEMONITOR_TRUE 1
-#define ERTS_DEMONITOR_BADARG 0
-#define ERTS_DEMONITOR_YIELD_TRUE -1
-#define ERTS_DEMONITOR_INTERNAL_ERROR -2
-
-static int
+/* This function is allowed to return range of values handled by demonitor/1-2
+ * Namely: atoms true, false, yield, internal_error, badarg or THE_NON_VALUE
+ */
+static Eterm
remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to)
{
ErtsDSigData dsd;
ErtsMonitor *dmon;
ErtsMonitor *mon;
int code;
- int res;
+ Eterm res = am_false;
#ifndef ERTS_SMP
int stale_mon = 0;
#endif
@@ -328,7 +325,7 @@ remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to)
mon = erts_remove_monitor(&ERTS_P_MONITORS(c_p), ref);
erts_smp_proc_unlock(c_p, ERTS_PROC_LOCK_LINK);
- res = ERTS_DEMONITOR_TRUE;
+ res = am_true;
break;
case ERTS_DSIG_PREP_CONNECTED:
@@ -352,7 +349,7 @@ remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to)
* This is possible when smp support is enabled.
* 'DOWN' message just arrived.
*/
- res = ERTS_DEMONITOR_TRUE;
+ res = am_true;
}
else {
/*
@@ -367,16 +364,13 @@ remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to)
: mon->pid),
ref,
0);
- res = (code == ERTS_DSIG_SEND_YIELD
- ? ERTS_DEMONITOR_YIELD_TRUE
- : ERTS_DEMONITOR_TRUE);
+ res = (code == ERTS_DSIG_SEND_YIELD ? am_yield : am_true);
erts_destroy_monitor(dmon);
-
}
break;
default:
ASSERT(! "Invalid dsig prepare result");
- return ERTS_DEMONITOR_INTERNAL_ERROR;
+ return am_internal_error;
}
#ifndef ERTS_SMP
@@ -404,27 +398,96 @@ remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to)
return res;
}
-static int demonitor(Process *c_p, Eterm ref, Eterm *multip)
+static ERTS_INLINE void
+demonitor_local_process(Process *c_p, Eterm ref, Eterm to, Eterm *res)
+{
+ Process *rp = erts_pid2proc_opt(c_p,
+ ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK,
+ to,
+ ERTS_PROC_LOCK_LINK,
+ ERTS_P2P_FLG_ALLOW_OTHER_X);
+ ErtsMonitor *mon = erts_remove_monitor(&ERTS_P_MONITORS(c_p), ref);
+
+#ifndef ERTS_SMP
+ ASSERT(mon);
+#else
+ if (!mon)
+ *res = am_false;
+ else
+#endif
+ {
+ *res = am_true;
+ erts_destroy_monitor(mon);
+ }
+ if (rp) {
+ ErtsMonitor *rmon;
+ rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), ref);
+ if (rp != c_p)
+ erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
+ if (rmon != NULL)
+ erts_destroy_monitor(rmon);
+ }
+ else {
+ ERTS_SMP_ASSERT_IS_NOT_EXITING(c_p);
+ }
+}
+
+static ERTS_INLINE BIF_RETTYPE
+demonitor_local_port(Process *origin, Eterm ref, Eterm target)
{
- ErtsMonitor *mon = NULL; /* The monitor entry to delete */
- Process *rp; /* Local target process */
- Eterm to = NIL; /* Monitor link traget */
- DistEntry *dep = NULL; /* Target's distribution entry */
- int deref_de = 0;
- int res;
- int unlock_link = 1;
+ BIF_RETTYPE res = am_false;
+ Port *port = erts_port_lookup_raw(target);
+
+ if (!port) {
+ BIF_ERROR(origin, BADARG);
+ }
+ erts_smp_proc_unlock(origin, ERTS_PROC_LOCK_LINK);
+
+ if (port) {
+ Eterm trap_ref;
+ switch (erts_port_demonitor(origin, ERTS_PORT_DEMONITOR_NORMAL,
+ port, ref, &trap_ref)) {
+ case ERTS_PORT_OP_DROPPED:
+ case ERTS_PORT_OP_BADARG:
+ break;
+ case ERTS_PORT_OP_SCHEDULED:
+ BIF_TRAP3(await_port_send_result_trap, origin, trap_ref,
+ am_busy_port, am_true);
+ /* the busy_port atom will never be returned, because it cannot be
+ * returned from erts_port_(de)monitor, but just in case if in future
+ * internal API changes - you may see this atom */
+ default:
+ break;
+ }
+ }
+ else {
+ ERTS_SMP_ASSERT_IS_NOT_EXITING(origin);
+ }
+ BIF_RET(res);
+}
+/* Can return atom true, false, yield, internal_error, badarg or
+ * THE_NON_VALUE if error occured or trap has been set up
+ */
+static
+BIF_RETTYPE demonitor(Process *c_p, Eterm ref, Eterm *multip)
+{
+ ErtsMonitor *mon = NULL; /* The monitor entry to delete */
+ Eterm to = NIL; /* Monitor link traget */
+ DistEntry *dep = NULL; /* Target's distribution entry */
+ int deref_de = 0;
+ BIF_RETTYPE res = am_false;
+ int unlock_link = 1;
erts_smp_proc_lock(c_p, ERTS_PROC_LOCK_LINK);
if (is_not_internal_ref(ref)) {
- res = ERTS_DEMONITOR_BADARG;
+ res = am_badarg;
goto done; /* Cannot be this monitor's ref */
}
mon = erts_lookup_monitor(ERTS_P_MONITORS(c_p), ref);
if (!mon) {
- res = ERTS_DEMONITOR_FALSE;
goto done;
}
@@ -432,70 +495,50 @@ static int demonitor(Process *c_p, Eterm ref, Eterm *multip)
case MON_TIME_OFFSET:
*multip = am_true;
erts_demonitor_time_offset(ref);
- res = ERTS_DEMONITOR_TRUE;
+ res = am_true;
break;
case MON_ORIGIN:
to = mon->pid;
*multip = am_false;
if (is_atom(to)) {
- /* Monitoring a name at node to */
- ASSERT(is_node_name_atom(to));
- dep = erts_sysname_to_connected_dist_entry(to);
- ASSERT(dep != erts_this_dist_entry);
- if (dep)
- deref_de = 1;
+ /* Monitoring a name at node to */
+ ASSERT(is_node_name_atom(to));
+ dep = erts_sysname_to_connected_dist_entry(to);
+ ASSERT(dep != erts_this_dist_entry);
+ if (dep)
+ deref_de = 1;
+ } else if (is_port(to)) {
+ if (port_dist_entry(to) != erts_this_dist_entry) {
+ goto badarg;
+ }
+ res = demonitor_local_port(c_p, ref, to);
+ unlock_link = 0;
+ goto done;
} else {
- ASSERT(is_pid(to));
- dep = pid_dist_entry(to);
+ ASSERT(is_pid(to));
+ dep = pid_dist_entry(to);
}
if (dep != erts_this_dist_entry) {
- res = remote_demonitor(c_p, dep, ref, to);
- /* remote_demonitor() unlocks link lock on c_p */
- unlock_link = 0;
+ res = remote_demonitor(c_p, dep, ref, to);
+ /* remote_demonitor() unlocks link lock on c_p */
+ unlock_link = 0;
}
else { /* Local monitor */
- if (deref_de) {
- deref_de = 0;
- erts_deref_dist_entry(dep);
- }
- dep = NULL;
- rp = erts_pid2proc_opt(c_p,
- ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK,
- to,
- ERTS_PROC_LOCK_LINK,
- ERTS_P2P_FLG_ALLOW_OTHER_X);
- mon = erts_remove_monitor(&ERTS_P_MONITORS(c_p), ref);
-#ifndef ERTS_SMP
- ASSERT(mon);
-#else
- if (!mon)
- res = ERTS_DEMONITOR_FALSE;
- else
-#endif
- {
- res = ERTS_DEMONITOR_TRUE;
- erts_destroy_monitor(mon);
- }
- if (rp) {
- ErtsMonitor *rmon;
- rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), ref);
- if (rp != c_p)
- erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
- if (rmon != NULL)
- erts_destroy_monitor(rmon);
- }
- else {
- ERTS_SMP_ASSERT_IS_NOT_EXITING(c_p);
- }
-
+ if (deref_de) {
+ deref_de = 0;
+ erts_deref_dist_entry(dep);
+ }
+ dep = NULL;
+ demonitor_local_process(c_p, ref, to, &res);
}
break;
- default:
- res = ERTS_DEMONITOR_BADARG;
+ default /* case */ :
+badarg:
+ res = am_badarg; /* will be converted to error by caller */
*multip = am_false;
break;
}
- done:
+done:
if (unlock_link)
erts_smp_proc_unlock(c_p, ERTS_PROC_LOCK_LINK);
@@ -506,21 +549,20 @@ static int demonitor(Process *c_p, Eterm ref, Eterm *multip)
}
ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN == erts_proc_lc_my_proc_locks(c_p));
- return res;
+ BIF_RET(res);
}
BIF_RETTYPE demonitor_1(BIF_ALIST_1)
{
Eterm multi;
switch (demonitor(BIF_P, BIF_ARG_1, &multi)) {
- case ERTS_DEMONITOR_FALSE:
- case ERTS_DEMONITOR_TRUE:
- BIF_RET(am_true);
- case ERTS_DEMONITOR_YIELD_TRUE:
- ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
- case ERTS_DEMONITOR_BADARG:
- BIF_ERROR(BIF_P, BADARG);
- case ERTS_DEMONITOR_INTERNAL_ERROR:
+ case am_false:
+ case am_true: BIF_RET(am_true);
+ case THE_NON_VALUE: BIF_RET(THE_NON_VALUE);
+ case am_yield: ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
+ case am_badarg: BIF_ERROR(BIF_P, BADARG);
+
+ case am_internal_error:
default:
ASSERT(! "demonitor(): internal error");
BIF_ERROR(BIF_P, EXC_INTERNAL_ERROR);
@@ -529,11 +571,11 @@ BIF_RETTYPE demonitor_1(BIF_ALIST_1)
BIF_RETTYPE demonitor_2(BIF_ALIST_2)
{
- Eterm res = am_true;
- Eterm multi = am_false;
- int info = 0;
- int flush = 0;
- Eterm list = BIF_ARG_2;
+ BIF_RETTYPE res = am_true;
+ Eterm multi = am_false;
+ int info = 0;
+ int flush = 0;
+ Eterm list = BIF_ARG_2;
while (is_list(list)) {
Eterm* consp = list_val(list);
@@ -554,24 +596,27 @@ BIF_RETTYPE demonitor_2(BIF_ALIST_2)
goto badarg;
switch (demonitor(BIF_P, BIF_ARG_1, &multi)) {
- case ERTS_DEMONITOR_FALSE:
+ case THE_NON_VALUE:
+ /* If other error occured or trap has been set up - pass through */
+ BIF_RET(THE_NON_VALUE);
+ case am_false:
if (info)
res = am_false;
if (flush) {
- flush_messages:
+flush_messages:
BIF_TRAP3(flush_monitor_messages_trap, BIF_P,
BIF_ARG_1, multi, res);
}
- case ERTS_DEMONITOR_TRUE:
+ case am_true:
if (multi == am_true && flush)
goto flush_messages;
BIF_RET(res);
- case ERTS_DEMONITOR_YIELD_TRUE:
+ case am_yield:
ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
- case ERTS_DEMONITOR_BADARG:
- badarg:
+ case am_badarg:
+badarg:
BIF_ERROR(BIF_P, BADARG);
- case ERTS_DEMONITOR_INTERNAL_ERROR:
+ case am_internal_error:
default:
ASSERT(! "demonitor(): internal error");
BIF_ERROR(BIF_P, EXC_INTERNAL_ERROR);
@@ -615,14 +660,13 @@ erts_queue_monitor_message(Process *p,
erts_queue_message(p, *p_locksp, msgp, tup, am_system);
}
-static BIF_RETTYPE
+static Eterm
local_pid_monitor(Process *p, Eterm target, Eterm mon_ref, int boolean)
{
- BIF_RETTYPE ret;
- Process *rp;
+ Eterm ret = mon_ref;
+ Process *rp;
ErtsProcLocks p_locks = ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK;
- ERTS_BIF_PREP_RET(ret, mon_ref);
if (target == p->common.id) {
return ret;
}
@@ -658,40 +702,112 @@ local_pid_monitor(Process *p, Eterm target, Eterm mon_ref, int boolean)
}
static BIF_RETTYPE
-local_name_monitor(Process *p, Eterm target_name)
+local_port_monitor(Process *origin, Eterm target)
{
- BIF_RETTYPE ret;
- Eterm mon_ref;
- ErtsProcLocks p_locks = ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK;
- Process *rp;
+ BIF_RETTYPE ref = erts_make_ref(origin);
+ Port *port = erts_sig_lookup_port(origin, target);
+ ErtsProcLocks p_locks = ERTS_PROC_LOCK_MAIN;
- mon_ref = erts_make_ref(p);
- ERTS_BIF_PREP_RET(ret, mon_ref);
- erts_smp_proc_lock(p, ERTS_PROC_LOCK_LINK);
- rp = erts_whereis_process(p, p_locks, target_name, ERTS_PROC_LOCK_LINK,
- ERTS_P2P_FLG_ALLOW_OTHER_X);
- if (!rp) {
- DeclareTmpHeap(lhp,3,p);
+ if (!port) {
+res_no_proc:
+ /* Send the DOWN message immediately. Ref is made on the fly because
+ * caller has never seen it yet. */
+ erts_queue_monitor_message(origin, &p_locks, ref,
+ am_port, target, am_noproc);
+ }
+ else {
+ switch (erts_port_monitor(origin, port, target, &ref)) {
+ case ERTS_PORT_OP_DROPPED:
+ case ERTS_PORT_OP_BADARG:
+ goto res_no_proc;
+ case ERTS_PORT_OP_SCHEDULED:
+ BIF_TRAP3(await_port_send_result_trap, origin, ref,
+ am_busy_port, ref);
+ /* the busy_port atom will never be returned, because it cannot be
+ * returned from erts_port_monitor, but just in case if in future
+ * internal API changes - you may see this atom */
+ default:
+ break;
+ }
+ }
+ erts_smp_proc_unlock(origin, p_locks & ~ERTS_PROC_LOCK_MAIN);
+ BIF_RET(ref);
+}
+
+/* Type = process | port :: atom(), 1st argument passed to erlang:monitor/2
+ */
+static BIF_RETTYPE
+local_name_monitor(Process *self, Eterm type, Eterm target_name)
+{
+ BIF_RETTYPE ret = erts_make_ref(self);
+
+ ErtsProcLocks p_locks = ERTS_PROC_LOCK_MAIN | ERTS_PROC_LOCK_LINK;
+ Process *proc = NULL;
+ Port *port = NULL;
+
+ erts_smp_proc_lock(self, ERTS_PROC_LOCK_LINK);
+
+ erts_whereis_name(self, p_locks, target_name,
+ &proc, ERTS_PROC_LOCK_LINK,
+ ERTS_P2P_FLG_ALLOW_OTHER_X,
+ &port, 0);
+
+ /* If the name is not registered,
+ * or if we asked for proc and got a port,
+ * or if we asked for port and got a proc,
+ * we just send the 'DOWN' message.
+ */
+ if ((!proc && !port) ||
+ (type == am_process && port) ||
+ (type == am_port && proc)) {
+ DeclareTmpHeap(lhp,3,self);
Eterm item;
- UseTmpHeap(3,p);
- erts_smp_proc_unlock(p, ERTS_PROC_LOCK_LINK);
+ UseTmpHeap(3,self);
+
+ erts_smp_proc_unlock(self, ERTS_PROC_LOCK_LINK);
p_locks &= ~ERTS_PROC_LOCK_LINK;
+
item = TUPLE2(lhp, target_name, erts_this_dist_entry->sysname);
- erts_queue_monitor_message(p, &p_locks,
- mon_ref, am_process, item, am_noproc);
- UnUseTmpHeap(3,p);
- }
- else if (rp != p) {
- erts_add_monitor(&ERTS_P_MONITORS(p), MON_ORIGIN, mon_ref, rp->common.id,
- target_name);
- erts_add_monitor(&ERTS_P_MONITORS(rp), MON_TARGET, mon_ref, p->common.id,
- target_name);
- erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
+ erts_queue_monitor_message(self, &p_locks,
+ ret,
+ type, /* = process|port :: atom() */
+ item, am_noproc);
+ UnUseTmpHeap(3,self);
+ }
+ else if (port) {
+ erts_smp_proc_unlock(self, p_locks & ~ERTS_PROC_LOCK_MAIN);
+ p_locks &= ~ERTS_PROC_LOCK_MAIN;
+
+ switch (erts_port_monitor(self, port, target_name, &ret)) {
+ case ERTS_PORT_OP_DONE:
+ return ret;
+ case ERTS_PORT_OP_SCHEDULED: { /* Scheduled a signal */
+ ASSERT(is_internal_ref(ret));
+ BIF_TRAP3(await_port_send_result_trap, self,
+ ret, am_true, ret);
+ /* bif_trap returns */
+ } break;
+ default:
+ goto badarg;
+ }
+ }
+ else if (proc != self) {
+ erts_add_monitor(&ERTS_P_MONITORS(self), MON_ORIGIN, ret,
+ proc->common.id, target_name);
+ erts_add_monitor(&ERTS_P_MONITORS(proc), MON_TARGET, ret,
+ self->common.id, target_name);
+ erts_smp_proc_unlock(proc, ERTS_PROC_LOCK_LINK);
}
- erts_smp_proc_unlock(p, p_locks & ~ERTS_PROC_LOCK_MAIN);
-
- return ret;
+ if (p_locks) {
+ erts_smp_proc_unlock(self, p_locks & ~ERTS_PROC_LOCK_MAIN);
+ }
+ BIF_RET(ret);
+badarg:
+ if (p_locks) {
+ erts_smp_proc_unlock(self, p_locks & ~ERTS_PROC_LOCK_MAIN);
+ }
+ BIF_ERROR(self, BADARG);
}
static BIF_RETTYPE
@@ -758,7 +874,7 @@ remote_monitor(Process *p, Eterm bifarg1, Eterm bifarg2,
break;
}
- return ret;
+ BIF_RET(ret);
}
BIF_RETTYPE monitor_2(BIF_ALIST_2)
@@ -772,8 +888,9 @@ BIF_RETTYPE monitor_2(BIF_ALIST_2)
switch (BIF_ARG_1) {
case am_time_offset: {
Eterm ref;
- if (BIF_ARG_2 != am_clock_service)
- goto error;
+ if (BIF_ARG_2 != am_clock_service) {
+ goto badarg;
+ }
ref = erts_make_ref(BIF_P);
erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_LINK);
erts_add_monitor(&ERTS_P_MONITORS(BIF_P), MON_TIME_OFFSET,
@@ -783,46 +900,57 @@ BIF_RETTYPE monitor_2(BIF_ALIST_2)
BIF_RET(ref);
}
case am_process:
+ case am_port:
break;
default:
- goto error;
+ goto badarg;
}
- if (is_internal_pid(target)) {
- local_pid:
- ret = local_pid_monitor(BIF_P, target, erts_make_ref(BIF_P), 0);
- } else if (is_external_pid(target)) {
+ if (is_internal_pid(target) && BIF_ARG_1 == am_process) {
+local_pid:
+ ret = local_pid_monitor(BIF_P, target, erts_make_ref(BIF_P), 0);
+ } else if (is_external_pid(target) && BIF_ARG_1 == am_process) {
dep = external_pid_dist_entry(target);
if (dep == erts_this_dist_entry)
goto local_pid;
ret = remote_monitor(BIF_P, BIF_ARG_1, BIF_ARG_2, dep, target, 0);
+ } else if (is_internal_port(target) && BIF_ARG_1 == am_port) {
+local_port:
+ ret = local_port_monitor(BIF_P, target);
+ } else if (is_external_port(target) && BIF_ARG_1 == am_port) {
+ dep = external_port_dist_entry(target);
+ if (dep == erts_this_dist_entry) {
+ goto local_port;
+ }
+ goto badarg; /* No want remote port */
} else if (is_atom(target)) {
- ret = local_name_monitor(BIF_P, target);
+ ret = local_name_monitor(BIF_P, BIF_ARG_1, target);
} else if (is_tuple(target)) {
Eterm *tp = tuple_val(target);
Eterm remote_node;
Eterm name;
- if (arityval(*tp) != 2)
- goto error;
+ if (arityval(*tp) != 2) {
+ goto badarg;
+ }
remote_node = tp[2];
name = tp[1];
if (!is_atom(remote_node) || !is_atom(name)) {
- goto error;
+ goto badarg;
}
if (!erts_is_alive && remote_node != am_Noname) {
- goto error; /* Remote monitor from (this) undistributed node */
+ goto badarg; /* Remote monitor from (this) undistributed node */
}
dep = erts_sysname_to_connected_dist_entry(remote_node);
if (dep == erts_this_dist_entry) {
deref_de = 1;
- ret = local_name_monitor(BIF_P, name);
+ ret = local_name_monitor(BIF_P, BIF_ARG_1, name);
} else {
if (dep)
deref_de = 1;
ret = remote_monitor(BIF_P, BIF_ARG_1, BIF_ARG_2, dep, name, 1);
}
} else {
- error:
+badarg:
ERTS_BIF_PREP_ERROR(ret, BIF_P, BADARG);
}
if (deref_de) {
diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c
index b410578d37..3fb866733c 100644
--- a/erts/emulator/beam/erl_bif_info.c
+++ b/erts/emulator/beam/erl_bif_info.c
@@ -361,8 +361,13 @@ erts_print_system_version(int to, void *arg, Process *c_p)
}
typedef struct {
+ /* {Entity,Node} = {monitor.Name,monitor.Pid} for external by name
+ * {Entity,Node} = {monitor.Pid,NIL} for external/external by pid
+ * {Entity,Node} = {monitor.Name,erlang:node()} for internal by name */
Eterm entity;
Eterm node;
+ /* pid is actual target being monitored, no matter pid/port or name */
+ Eterm pid;
} MonitorInfo;
typedef struct {
@@ -420,21 +425,27 @@ static void collect_one_origin_monitor(ErtsMonitor *mon, void *vmicp)
EXTEND_MONITOR_INFOS(micp);
if (is_atom(mon->pid)) { /* external by name */
micp->mi[micp->mi_i].entity = mon->name;
- micp->mi[micp->mi_i].node = mon->pid;
- micp->sz += 3; /* need one 2-tuple */
+ micp->mi[micp->mi_i].node = mon->pid;
+ micp->sz += 3; /* need one 2-tuple */
} else if (is_external_pid(mon->pid)) { /* external by pid */
micp->mi[micp->mi_i].entity = mon->pid;
- micp->mi[micp->mi_i].node = NIL;
- micp->sz += NC_HEAP_SIZE(mon->pid);
+ micp->mi[micp->mi_i].node = NIL;
+ micp->sz += NC_HEAP_SIZE(mon->pid);
} else if (!is_nil(mon->name)) { /* internal by name */
micp->mi[micp->mi_i].entity = mon->name;
- micp->mi[micp->mi_i].node = erts_this_dist_entry->sysname;
- micp->sz += 3; /* need one 2-tuple */
+ micp->mi[micp->mi_i].node = erts_this_dist_entry->sysname;
+ micp->sz += 3; /* need one 2-tuple */
} else { /* internal by pid */
micp->mi[micp->mi_i].entity = mon->pid;
- micp->mi[micp->mi_i].node = NIL;
+ micp->mi[micp->mi_i].node = NIL;
/* no additional heap space needed */
}
+
+ /* have always pid at hand, to assist with figuring out if its a port or
+ * a process, when we monitored by name and process_info is requested.
+ * See: erl_bif_info.c:process_info_aux section for am_monitors */
+ micp->mi[micp->mi_i].pid = mon->pid;
+
micp->mi_i++;
micp->sz += 2 + 3; /* For a cons cell and a 2-tuple */
}
@@ -1190,37 +1201,49 @@ process_info_aux(Process *BIF_P,
case am_monitors: {
MonitorInfoCollection mic;
- int i;
+ int i;
INIT_MONITOR_INFOS(mic);
- erts_doforall_monitors(ERTS_P_MONITORS(rp),&collect_one_origin_monitor,&mic);
- hp = HAlloc(BIF_P, 3 + mic.sz);
+ erts_doforall_monitors(ERTS_P_MONITORS(rp),
+ &collect_one_origin_monitor, &mic);
+ hp = HAlloc(BIF_P, 3 + mic.sz);
res = NIL;
for (i = 0; i < mic.mi_i; i++) {
if (is_atom(mic.mi[i].entity)) {
/* Monitor by name.
- * Build {process, {Name, Node}} and cons it.
+ * Build {process|port, {Name, Node}} and cons it.
*/
Eterm t1, t2;
+ /* If pid is an atom, then it is a remote named monitor, which
+ has to be a process */
+ Eterm m_type = is_port(mic.mi[i].pid) ? am_port : am_process;
+ ASSERT(is_pid(mic.mi[i].pid)
+ || is_port(mic.mi[i].pid)
+ || is_atom(mic.mi[i].pid));
t1 = TUPLE2(hp, mic.mi[i].entity, mic.mi[i].node);
hp += 3;
- t2 = TUPLE2(hp, am_process, t1);
+ t2 = TUPLE2(hp, m_type, t1);
hp += 3;
res = CONS(hp, t2, res);
- hp += 2;
+ hp += 2;
}
else {
- /* Monitor by pid. Build {process, Pid} and cons it. */
+ /* Monitor by pid. Build {process|port, Pid} and cons it. */
Eterm t;
Eterm pid = STORE_NC(&hp, &MSO(BIF_P), mic.mi[i].entity);
- t = TUPLE2(hp, am_process, pid);
+
+ Eterm m_type = is_port(mic.mi[i].pid) ? am_port : am_process;
+ ASSERT(is_pid(mic.mi[i].pid)
+ || is_port(mic.mi[i].pid));
+
+ t = TUPLE2(hp, m_type, pid);
hp += 3;
res = CONS(hp, t, res);
- hp += 2;
+ hp += 2;
}
}
- DESTROY_MONITOR_INFOS(mic);
+ DESTROY_MONITOR_INFOS(mic);
break;
}
@@ -2880,7 +2903,8 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
*/
Eterm
-erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt, Eterm item)
+erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt,
+ Eterm item)
{
Eterm res = THE_NON_VALUE;
@@ -2928,8 +2952,8 @@ erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt, Eterm ite
Eterm item;
INIT_MONITOR_INFOS(mic);
-
- erts_doforall_monitors(ERTS_P_MONITORS(prt), &collect_one_origin_monitor, &mic);
+ erts_doforall_monitors(ERTS_P_MONITORS(prt),
+ &collect_one_origin_monitor, &mic);
if (szp)
*szp += mic.sz;
@@ -2938,14 +2962,16 @@ erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt, Eterm ite
res = NIL;
for (i = 0; i < mic.mi_i; i++) {
Eterm t;
- item = STORE_NC(hpp, ohp, mic.mi[i].entity);
- t = TUPLE2(*hpp, am_process, item);
+ Eterm m_type;
+
+ item = STORE_NC(hpp, ohp, mic.mi[i].entity);
+ m_type = is_port(item) ? am_port : am_process;
+ t = TUPLE2(*hpp, m_type, item);
*hpp += 3;
res = CONS(*hpp, t, res);
*hpp += 2;
}
- }
-
+ } // hpp
DESTROY_MONITOR_INFOS(mic);
if (szp) {
@@ -2953,6 +2979,32 @@ erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt, Eterm ite
goto done;
}
}
+ else if (item == am_monitored_by) {
+ MonitorInfoCollection mic;
+ int i;
+ Eterm item;
+
+ INIT_MONITOR_INFOS(mic);
+ erts_doforall_monitors(ERTS_P_MONITORS(prt),
+ &collect_one_target_monitor, &mic);
+ if (szp)
+ *szp += mic.sz;
+
+ if (hpp) {
+ res = NIL;
+ for (i = 0; i < mic.mi_i; ++i) {
+ item = STORE_NC(hpp, ohp, mic.mi[i].entity);
+ res = CONS(*hpp, item, res);
+ *hpp += 2;
+ }
+ } // hpp
+ DESTROY_MONITOR_INFOS(mic);
+
+ if (szp) {
+ res = am_true;
+ goto done;
+ }
+ }
else if (item == am_name) {
int count = sys_strlen(prt->name);
diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c
index fefa9d8391..90e78a9b0b 100644
--- a/erts/emulator/beam/erl_bif_port.c
+++ b/erts/emulator/beam/erl_bif_port.c
@@ -139,6 +139,12 @@ sig_lookup_port(Process *c_p, Eterm id_or_name)
return lookup_port(c_p, id_or_name, ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP);
}
+/* Non-inline copy of sig_lookup_port to be exported */
+Port *erts_sig_lookup_port(Process *c_p, Eterm id_or_name)
+{
+ return lookup_port(c_p, id_or_name, ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP);
+}
+
static ERTS_INLINE Port *
data_lookup_port(Process *c_p, Eterm id_or_name)
{
diff --git a/erts/emulator/beam/erl_port.h b/erts/emulator/beam/erl_port.h
index f0075ca2b9..f90844ccc8 100644
--- a/erts/emulator/beam/erl_port.h
+++ b/erts/emulator/beam/erl_port.h
@@ -361,6 +361,8 @@ Eterm erts_request_io_bytes(Process *c_p);
#define ERTS_PORT_REDS_CONNECT (CONTEXT_REDS/200)
#define ERTS_PORT_REDS_UNLINK (CONTEXT_REDS/200)
#define ERTS_PORT_REDS_LINK (CONTEXT_REDS/200)
+#define ERTS_PORT_REDS_MONITOR (CONTEXT_REDS/200)
+#define ERTS_PORT_REDS_DEMONITOR (CONTEXT_REDS/200)
#define ERTS_PORT_REDS_BADSIG (CONTEXT_REDS/200)
#define ERTS_PORT_REDS_CONTROL (CONTEXT_REDS/100)
#define ERTS_PORT_REDS_CALL (CONTEXT_REDS/50)
@@ -850,16 +852,20 @@ void erts_port_resume_procs(Port *);
struct binary;
-#define ERTS_P2P_SIG_TYPE_BAD 0
-#define ERTS_P2P_SIG_TYPE_OUTPUT 1
-#define ERTS_P2P_SIG_TYPE_OUTPUTV 2
-#define ERTS_P2P_SIG_TYPE_CONNECT 3
-#define ERTS_P2P_SIG_TYPE_EXIT 4
-#define ERTS_P2P_SIG_TYPE_CONTROL 5
-#define ERTS_P2P_SIG_TYPE_CALL 6
-#define ERTS_P2P_SIG_TYPE_INFO 7
-#define ERTS_P2P_SIG_TYPE_LINK 8
-#define ERTS_P2P_SIG_TYPE_UNLINK 9
+enum {
+ ERTS_P2P_SIG_TYPE_BAD = 0,
+ ERTS_P2P_SIG_TYPE_OUTPUT = 1,
+ ERTS_P2P_SIG_TYPE_OUTPUTV = 2,
+ ERTS_P2P_SIG_TYPE_CONNECT = 3,
+ ERTS_P2P_SIG_TYPE_EXIT = 4,
+ ERTS_P2P_SIG_TYPE_CONTROL = 5,
+ ERTS_P2P_SIG_TYPE_CALL = 6,
+ ERTS_P2P_SIG_TYPE_INFO = 7,
+ ERTS_P2P_SIG_TYPE_LINK = 8,
+ ERTS_P2P_SIG_TYPE_UNLINK = 9,
+ ERTS_P2P_SIG_TYPE_MONITOR = 10,
+ ERTS_P2P_SIG_TYPE_DEMONITOR = 11
+};
#define ERTS_P2P_SIG_TYPE_BITS 4
#define ERTS_P2P_SIG_TYPE_MASK \
@@ -921,6 +927,15 @@ struct ErtsProc2PortSigData_ {
struct {
Eterm from;
} unlink;
+ struct {
+ Eterm origin; /* who receives monitor event, pid */
+ Eterm name; /* either name for named monitor, or port id */
+ } monitor;
+ struct {
+ Eterm origin; /* who is at the other end of the monitor, pid */
+ Eterm name; /* port id */
+ Uint32 ref[ERTS_MAX_REF_NUMBERS]; /* box contents of a ref */
+ } demonitor;
} u;
} ;
@@ -1017,6 +1032,29 @@ ErtsPortOpResult erts_port_control(Process *, Port *, unsigned int, Eterm, Eterm
ErtsPortOpResult erts_port_call(Process *, Port *, unsigned int, Eterm, Eterm *);
ErtsPortOpResult erts_port_info(Process *, Port *, Eterm, Eterm *);
+/* Creates monitor between Origin and Target. Ref must be initialized to
+ * a reference (ref may be rewritten to be used to serve additionally as a
+ * signal id). Name is atom if user monitors port by name or NIL */
+ErtsPortOpResult erts_port_monitor(Process *origin, Port *target, Eterm name,
+ Eterm *ref);
+
+typedef enum {
+ /* Normal demonitor rules apply with locking and reductions bump */
+ ERTS_PORT_DEMONITOR_NORMAL = 1,
+ /* Relaxed demonitor rules when process is about to die, which means that
+ * pid lookup won't work, locks won't work, no reductions bump. */
+ ERTS_PORT_DEMONITOR_ORIGIN_ON_DEATHBED = 2,
+} ErtsDemonitorMode;
+
+/* Removes monitor between origin and target, identified by ref.
+ * origin_is_dying can be 0 (false, normal locking rules and reductions bump
+ * apply) or 1 (true, in case when we avoid origin locking) */
+ErtsPortOpResult erts_port_demonitor(Process *origin, ErtsDemonitorMode mode,
+ Port *target, Eterm ref,
+ Eterm *trap_ref);
+/* defined in erl_bif_port.c */
+Port *erts_sig_lookup_port(Process *c_p, Eterm id_or_name);
+
int erts_port_output_async(Port *, Eterm, Eterm);
/*
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index 5193be85b4..48f89d2bd7 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -12239,7 +12239,6 @@ static void doit_exit_monitor(ErtsMonitor *mon, void *vpcontext)
ExitMonitorContext *pcontext = vpcontext;
DistEntry *dep;
ErtsMonitor *rmon;
- Process *rp;
switch (mon->type) {
case MON_ORIGIN:
@@ -12268,9 +12267,10 @@ static void doit_exit_monitor(ErtsMonitor *mon, void *vpcontext)
erts_deref_dist_entry(dep);
}
} else {
- ASSERT(is_pid(mon->pid));
- if (is_internal_pid(mon->pid)) { /* local by pid or name */
- rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK);
+ ASSERT(is_pid(mon->pid) || is_port(mon->pid));
+ /* if is local by pid or name */
+ if (is_internal_pid(mon->pid)) {
+ Process *rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK);
if (!rp) {
goto done;
}
@@ -12280,7 +12280,17 @@ static void doit_exit_monitor(ErtsMonitor *mon, void *vpcontext)
goto done;
}
erts_destroy_monitor(rmon);
- } else { /* remote by pid */
+ } else if (is_internal_port(mon->pid)) {
+ /* Is a local port */
+ Port *prt = erts_port_lookup_raw(mon->pid);
+ if (!prt) {
+ goto done;
+ }
+ erts_port_demonitor(pcontext->p,
+ ERTS_PORT_DEMONITOR_ORIGIN_ON_DEATHBED,
+ prt, mon->ref, NULL);
+ return; /* let erts_port_demonitor do the deletion */
+ } else { /* remote by pid */
ASSERT(is_external_pid(mon->pid));
dep = external_pid_dist_entry(mon->pid);
ASSERT(dep != NULL);
@@ -12318,6 +12328,7 @@ static void doit_exit_monitor(ErtsMonitor *mon, void *vpcontext)
erts_port_release(prt);
} else if (is_internal_pid(mon->pid)) {/* local by name or pid */
Eterm watched;
+ Process *rp;
DeclareTmpHeapNoproc(lhp,3);
ErtsProcLocks rp_locks = (ERTS_PROC_LOCK_LINK
| ERTS_PROC_LOCKS_MSG_SEND);
diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c
index 01df5476db..cb8792dffa 100644
--- a/erts/emulator/beam/io.c
+++ b/erts/emulator/beam/io.c
@@ -1260,7 +1260,7 @@ typedef struct {
/*
* Try doing an immediate driver callback call from a process. If
* this fail, the operation should be scheduled in the normal case...
- *
+ * Returns: ok to do the call, or error (lock busy, does not exist, etc)
*/
static ERTS_INLINE ErtsTryImmDrvCallResult
try_imm_drv_call(ErtsTryImmDrvCallState *sp)
@@ -3074,6 +3074,250 @@ erts_port_link(Process *c_p, Port *prt, Eterm to, Eterm *refp)
}
static void
+port_monitor_failure(Eterm port_id, Eterm origin, Eterm ref_DOWN)
+{
+ Process *origin_p;
+ ErtsProcLocks p_locks = ERTS_PROC_LOCK_LINK;
+ ASSERT(is_internal_pid(origin));
+
+ origin_p = erts_pid2proc(NULL, 0, origin, p_locks);
+ if (! origin_p) { return; }
+
+ /* Send the DOWN message immediately. Ref is made on the fly because
+ * caller has never seen it yet. */
+ erts_queue_monitor_message(origin_p, &p_locks, ref_DOWN,
+ am_port, port_id, am_noproc);
+ erts_smp_proc_unlock(origin_p, p_locks);
+}
+
+/* Origin wants to monitor port Prt. State contains possible error, which has
+ * happened just before. Name is either NIL or an atom, if user monitors
+ * a port by name. Ref is premade reference that will be returned to user */
+static void
+port_monitor(Port *prt, erts_aint32_t state, Eterm origin,
+ Eterm name, Eterm ref)
+{
+ Eterm name_or_nil = is_atom(name) ? name : NIL;
+
+ ASSERT(is_pid(origin));
+ ASSERT(is_atom(name) || is_port(name) || name == NIL);
+ ASSERT(is_internal_ref(ref));
+
+ if (!(state & ERTS_PORT_SFLGS_INVALID_LOOKUP)) {
+ ErtsProcLocks p_locks = ERTS_PROC_LOCK_LINK;
+
+ Process *origin_p = erts_pid2proc(NULL, 0, origin, p_locks);
+ if (! origin_p) {
+ goto failure;
+ }
+ erts_add_monitor(&ERTS_P_MONITORS(origin_p), MON_ORIGIN, ref,
+ prt->common.id, name_or_nil);
+ erts_add_monitor(&ERTS_P_MONITORS(prt), MON_TARGET, ref,
+ origin, name_or_nil);
+
+ erts_smp_proc_unlock(origin_p, p_locks);
+ } else {
+failure:
+ port_monitor_failure(prt->common.id, origin, ref);
+ }
+}
+
+static int
+port_sig_monitor(Port *prt, erts_aint32_t state, int op,
+ ErtsProc2PortSigData *sigdp)
+{
+ Eterm hp[REF_THING_SIZE];
+ Eterm ref = make_internal_ref(&hp);
+ write_ref_thing(hp, sigdp->ref[0], sigdp->ref[1], sigdp->ref[2]);
+
+ if (op == ERTS_PROC2PORT_SIG_EXEC) {
+ /* erts_add_monitor call inside port_monitor will copy ref from hp */
+ port_monitor(prt, state,
+ sigdp->u.monitor.origin,
+ sigdp->u.monitor.name,
+ ref);
+ } else {
+ port_monitor_failure(sigdp->u.monitor.name,
+ sigdp->u.monitor.origin,
+ ref);
+ }
+ if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY) {
+ port_sched_op_reply(sigdp->caller, sigdp->ref, am_true, prt);
+ }
+ return ERTS_PORT_REDS_MONITOR;
+}
+
+/* Creates monitor between Origin and Target. Ref must be initialized to
+ * a reference (ref may be rewritten to be used to serve additionally as a
+ * signal id). Name is atom if user monitors port by name or NIL */
+ErtsPortOpResult
+erts_port_monitor(Process *origin, Port *port, Eterm name, Eterm *refp)
+{
+ ErtsProc2PortSigData *sigdp;
+ ErtsTryImmDrvCallState try_call_state
+ = ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
+ origin, port, ERTS_PORT_SFLGS_INVALID_LOOKUP,
+ 0,
+ 0, /* trap_ref is always set so !trap_ref always is false */
+ am_monitor);
+
+ ASSERT(origin);
+ ASSERT(port);
+ ASSERT(is_atom(name) || is_port(name));
+ ASSERT(refp);
+
+ switch (try_imm_drv_call(&try_call_state)) {
+ case ERTS_TRY_IMM_DRV_CALL_OK:
+ port_monitor(port, try_call_state.state, origin->common.id, name, *refp);
+ finalize_imm_drv_call(&try_call_state);
+ BUMP_REDS(origin, ERTS_PORT_REDS_MONITOR);
+ return ERTS_PORT_OP_DONE;
+ case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
+ return ERTS_PORT_OP_BADARG;
+ default:
+ break; /* Schedule call instead... */
+ }
+
+ sigdp = erts_port_task_alloc_p2p_sig_data();
+ sigdp->flags = ERTS_P2P_SIG_TYPE_MONITOR;
+ sigdp->u.monitor.origin = origin->common.id;
+ sigdp->u.monitor.name = name; /* either named monitor, or port id */
+
+ /* Ref contents will be initialized here */
+ return erts_schedule_proc2port_signal(origin, port, origin->common.id,
+ refp, sigdp, 0, NULL,
+ port_sig_monitor);
+}
+
+static void
+port_demonitor_failure(Eterm port_id, Eterm origin, Eterm ref)
+{
+ Process *origin_p;
+ ErtsProcLocks rp_locks = ERTS_PROC_LOCK_LINK;
+ ErtsMonitor *mon1;
+ ASSERT(is_internal_pid(origin));
+
+ origin_p = erts_pid2proc(NULL, 0, origin, rp_locks);
+ if (! origin_p) { return; }
+
+ /* do not send any DOWN messages, drop monitors on process */
+ mon1 = erts_remove_monitor(&ERTS_P_MONITORS(origin_p), ref);
+ if (mon1 != NULL) {
+ erts_destroy_monitor(mon1);
+ }
+
+ erts_smp_proc_unlock(origin_p, rp_locks);
+}
+
+/* Origin wants to demonitor port Prt. State contains possible error, which has
+ * happened just before. Ref is reference to monitor */
+static void
+port_demonitor(Port *port, erts_aint32_t state, Eterm origin, Eterm ref)
+{
+ ASSERT(port);
+ ASSERT(is_pid(origin));
+ ASSERT(is_internal_ref(ref));
+
+ if (!(state & ERTS_PORT_SFLGS_INVALID_LOOKUP)) {
+ ErtsProcLocks p_locks = ERTS_PROC_LOCK_LINK;
+ Process *origin_p = erts_pid2proc(NULL, 0, origin, p_locks);
+ if (origin_p) {
+ ErtsMonitor *mon1 = erts_remove_monitor(&ERTS_P_MONITORS(origin_p),
+ ref);
+ if (mon1 != NULL) {
+ erts_destroy_monitor(mon1);
+ }
+ }
+ if (1) {
+ ErtsMonitor *mon2 = erts_remove_monitor(&ERTS_P_MONITORS(port),
+ ref);
+ if (mon2 != NULL) {
+ erts_destroy_monitor(mon2);
+ }
+ }
+ if (origin_p) { /* when origin is dying, it won't be found */
+ erts_smp_proc_unlock(origin_p, p_locks);
+ }
+ } else {
+ port_demonitor_failure(port->common.id, origin, ref);
+ }
+}
+
+static int
+port_sig_demonitor(Port *prt, erts_aint32_t state, int op,
+ ErtsProc2PortSigData *sigdp)
+{
+ Eterm hp[REF_THING_SIZE];
+ Eterm ref = make_internal_ref(&hp);
+ write_ref_thing(hp, sigdp->u.demonitor.ref[0],
+ sigdp->u.demonitor.ref[1],
+ sigdp->u.demonitor.ref[2]);
+ if (op == ERTS_PROC2PORT_SIG_EXEC) {
+ port_demonitor(prt, state, sigdp->u.demonitor.origin, ref);
+ } else {
+ port_demonitor_failure(sigdp->u.demonitor.name,
+ sigdp->u.demonitor.origin,
+ ref);
+ }
+ if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY) {
+ port_sched_op_reply(sigdp->caller, sigdp->ref, am_true, prt);
+ }
+ return ERTS_PORT_REDS_DEMONITOR;
+}
+
+/* Removes monitor between origin and target, identified by ref.
+ * Mode defines normal or relaxed demonitor rules (process is at death) */
+ErtsPortOpResult erts_port_demonitor(Process *origin, ErtsDemonitorMode mode,
+ Port *target, Eterm ref,
+ Eterm *trap_ref)
+{
+ Process *c_p = mode == ERTS_PORT_DEMONITOR_NORMAL ? origin : NULL;
+ ErtsProc2PortSigData *sigdp;
+ ErtsTryImmDrvCallState try_call_state
+ = ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
+ c_p,
+ target, ERTS_PORT_SFLGS_INVALID_LOOKUP,
+ 0,
+ !trap_ref,
+ am_demonitor);
+
+ ASSERT(origin);
+ ASSERT(target);
+ ASSERT(is_internal_ref(ref));
+
+ switch (try_imm_drv_call(&try_call_state)) {
+ case ERTS_TRY_IMM_DRV_CALL_OK:
+ port_demonitor(target, try_call_state.state, origin->common.id, ref);
+ finalize_imm_drv_call(&try_call_state);
+ if (mode == ERTS_PORT_DEMONITOR_NORMAL) {
+ BUMP_REDS(origin, ERTS_PORT_REDS_DEMONITOR);
+ }
+ return ERTS_PORT_OP_DONE;
+ case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
+ return ERTS_PORT_OP_BADARG;
+ default:
+ break; /* Schedule call instead... */
+ }
+
+ sigdp = erts_port_task_alloc_p2p_sig_data();
+ sigdp->flags = ERTS_P2P_SIG_TYPE_DEMONITOR;
+ sigdp->u.demonitor.origin = origin->common.id;
+ sigdp->u.demonitor.name = target->common.id;
+ {
+ RefThing *reft = ref_thing_ptr(ref);
+ /* Start from 1 skip ref arity */
+ sys_memcpy(sigdp->u.demonitor.ref,
+ internal_thing_ref_numbers(reft),
+ sizeof(sigdp->u.demonitor.ref));
+ }
+
+ /* Ref contents will be initialized here */
+ return erts_schedule_proc2port_signal(c_p, target, origin->common.id,
+ trap_ref, sigdp, 0, NULL,
+ port_sig_demonitor);
+}
+
+static void
init_ack_send_reply(Port *port, Eterm resp)
{
@@ -3942,23 +4186,30 @@ erts_terminate_port(Port *pp)
terminate_port(pp);
}
+static void port_fire_one_monitor(ErtsMonitor *mon, void *ctx0);
static void sweep_one_monitor(ErtsMonitor *mon, void *vpsc)
{
- ErtsMonitor *rmon;
- Process *rp;
+ switch (mon->type) {
+ case MON_ORIGIN: {
+ ErtsMonitor *rmon;
+ Process *rp;
- ASSERT(mon->type == MON_ORIGIN);
- ASSERT(is_internal_pid(mon->pid));
- rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK);
- if (!rp) {
- goto done;
- }
- rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), mon->ref);
- erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
- if (rmon == NULL) {
- goto done;
+ ASSERT(is_internal_pid(mon->pid));
+ rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK);
+ if (!rp) {
+ goto done;
+ }
+ rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), mon->ref);
+ erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
+ if (rmon == NULL) {
+ goto done;
+ }
+ erts_destroy_monitor(rmon);
+ } break;
+ case MON_TARGET: {
+ port_fire_one_monitor(mon, vpsc); /* forward call */
+ } break;
}
- erts_destroy_monitor(rmon);
done:
erts_destroy_monitor(mon);
}
@@ -4039,6 +4290,43 @@ static void sweep_one_link(ErtsLink *lnk, void *vpsc)
erts_destroy_link(lnk);
}
+static void
+port_fire_one_monitor(ErtsMonitor *mon, void *ctx0)
+{
+ Process *origin;
+ ErtsProcLocks origin_locks;
+
+ if (mon->type != MON_TARGET || ! is_pid(mon->pid)) {
+ return;
+ }
+ /*
+ * Proceed here if someone monitors us, we (port) are the target and
+ * origin is some process
+ */
+ origin_locks = ERTS_PROC_LOCKS_MSG_SEND | ERTS_PROC_LOCK_LINK;
+
+ origin = erts_pid2proc(NULL, 0, mon->pid, origin_locks);
+ if (origin) {
+ DeclareTmpHeapNoproc(lhp,3);
+ SweepContext *ctx = (SweepContext *)ctx0;
+ ErtsMonitor *rmon;
+ Eterm watched = (is_atom(mon->name)
+ ? TUPLE2(lhp, mon->name, erts_this_dist_entry->sysname)
+ : ctx->port->common.id);
+
+ erts_queue_monitor_message(origin, &origin_locks, mon->ref, am_port,
+ watched, ctx->reason);
+ UnUseTmpHeapNoproc(3);
+
+ rmon = erts_remove_monitor(&ERTS_P_MONITORS(origin), mon->ref);
+ erts_smp_proc_unlock(origin, origin_locks);
+
+ if (rmon) {
+ erts_destroy_monitor(rmon);
+ }
+ }
+}
+
/* 'from' is sending 'this_port' an exit signal, (this_port must be internal).
* If reason is normal we don't do anything, *unless* from is our connected
* process in which case we close the port. Any other reason kills the port.
@@ -4050,39 +4338,40 @@ static void sweep_one_link(ErtsLink *lnk, void *vpsc)
*/
int
-erts_deliver_port_exit(Port *p, Eterm from, Eterm reason, int send_closed,
+erts_deliver_port_exit(Port *prt, Eterm from, Eterm reason, int send_closed,
int drop_normal)
{
ErtsLink *lnk;
- Eterm rreason;
+ Eterm modified_reason;
erts_aint32_t state, set_state_flags;
ERTS_SMP_CHK_NO_PROC_LOCKS;
- ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(p));
+ ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
- rreason = (reason == am_kill) ? am_killed : reason;
+ modified_reason = (reason == am_kill) ? am_killed : reason;
#ifdef USE_VM_PROBES
if (DTRACE_ENABLED(port_exit)) {
DTRACE_CHARBUF(from_str, DTRACE_TERM_BUF_SIZE);
DTRACE_CHARBUF(port_str, DTRACE_TERM_BUF_SIZE);
- DTRACE_CHARBUF(rreason_str, 64);
+ DTRACE_CHARBUF(reason_str, 64);
erts_snprintf(from_str, sizeof(DTRACE_CHARBUF_NAME(from_str)), "%T", from);
- dtrace_port_str(p, port_str);
- erts_snprintf(rreason_str, sizeof(DTRACE_CHARBUF_NAME(rreason_str)), "%T", rreason);
- DTRACE4(port_exit, from_str, port_str, p->name, rreason_str);
+ dtrace_port_str(prt, port_str);
+ erts_snprintf(reason_str, sizeof(DTRACE_CHARBUF_NAME(reason_str)), "%T",
+ modified_reason);
+ DTRACE4(port_exit, from_str, port_str, prt->name, reason_str);
}
#endif
- state = erts_atomic32_read_nob(&p->state);
+ state = erts_atomic32_read_nob(&prt->state);
if (state & (ERTS_PORT_SFLGS_DEAD
| ERTS_PORT_SFLG_EXITING
| ERTS_PORT_SFLG_CLOSING))
return 0;
- if (reason == am_normal && from != ERTS_PORT_GET_CONNECTED(p)
- && from != p->common.id && drop_normal) {
+ if (reason == am_normal && from != ERTS_PORT_GET_CONNECTED(prt)
+ && from != prt->common.id && drop_normal) {
return 0;
}
@@ -4090,53 +4379,54 @@ erts_deliver_port_exit(Port *p, Eterm from, Eterm reason, int send_closed,
if (send_closed)
set_state_flags |= ERTS_PORT_SFLG_SEND_CLOSED;
- erts_port_task_sched_enter_exiting_state(&p->sched);
+ erts_port_task_sched_enter_exiting_state(&prt->sched);
- state = erts_atomic32_read_bor_mb(&p->state, set_state_flags);
+ state = erts_atomic32_read_bor_mb(&prt->state, set_state_flags);
state |= set_state_flags;
- if (IS_TRACED_FL(p, F_TRACE_PORTS))
- trace_port(p, am_closed, reason);
+ if (IS_TRACED_FL(prt, F_TRACE_PORTS))
+ trace_port(prt, am_closed, reason);
- erts_trace_check_exiting(p->common.id);
+ erts_trace_check_exiting(prt->common.id);
- set_busy_port(ERTS_Port2ErlDrvPort(p), 0);
+ set_busy_port(ERTS_Port2ErlDrvPort(prt), 0);
- if (p->common.u.alive.reg != NULL)
- (void) erts_unregister_name(NULL, 0, p, p->common.u.alive.reg->name);
+ if (prt->common.u.alive.reg != NULL)
+ (void) erts_unregister_name(NULL, 0, prt, prt->common.u.alive.reg->name);
{
- SweepContext sc = {p, rreason};
- lnk = ERTS_P_LINKS(p);
- ERTS_P_LINKS(p) = NULL;
+ SweepContext sc = {prt, modified_reason};
+ lnk = ERTS_P_LINKS(prt);
+ ERTS_P_LINKS(prt) = NULL;
erts_sweep_links(lnk, &sweep_one_link, &sc);
}
- DRV_MONITOR_LOCK_PDL(p);
+ DRV_MONITOR_LOCK_PDL(prt);
{
- ErtsMonitor *moni = ERTS_P_MONITORS(p);
- ERTS_P_MONITORS(p) = NULL;
- erts_sweep_monitors(moni, &sweep_one_monitor, NULL);
+ SweepContext ctx = {prt, modified_reason};
+ ErtsMonitor *moni = ERTS_P_MONITORS(prt);
+ ERTS_P_MONITORS(prt) = NULL;
+ erts_sweep_monitors(moni, &sweep_one_monitor, &ctx);
}
- DRV_MONITOR_UNLOCK_PDL(p);
+ DRV_MONITOR_UNLOCK_PDL(prt);
- if ((state & ERTS_PORT_SFLG_DISTRIBUTION) && p->dist_entry) {
- erts_do_net_exits(p->dist_entry, rreason);
- erts_deref_dist_entry(p->dist_entry);
- p->dist_entry = NULL;
- erts_atomic32_read_band_relb(&p->state,
+ if ((state & ERTS_PORT_SFLG_DISTRIBUTION) && prt->dist_entry) {
+ erts_do_net_exits(prt->dist_entry, modified_reason);
+ erts_deref_dist_entry(prt->dist_entry);
+ prt->dist_entry = NULL;
+ erts_atomic32_read_band_relb(&prt->state,
~ERTS_PORT_SFLG_DISTRIBUTION);
}
- if ((reason != am_kill) && !is_port_ioq_empty(p)) {
+ if ((reason != am_kill) && !is_port_ioq_empty(prt)) {
/* must turn exiting flag off */
- erts_atomic32_read_bset_relb(&p->state,
+ erts_atomic32_read_bset_relb(&prt->state,
(ERTS_PORT_SFLG_EXITING
| ERTS_PORT_SFLG_CLOSING),
ERTS_PORT_SFLG_CLOSING);
- flush_port(p);
+ flush_port(prt);
}
else {
- terminate_port(p);
+ terminate_port(prt);
}
return 1;
diff --git a/erts/emulator/beam/register.c b/erts/emulator/beam/register.c
index 77f79fcea4..ac7096745e 100644
--- a/erts/emulator/beam/register.c
+++ b/erts/emulator/beam/register.c
@@ -323,7 +323,8 @@ erts_whereis_name(Process *c_p,
Process** proc,
ErtsProcLocks need_locks,
int flags,
- Port** port)
+ Port** port,
+ int lock_port)
{
RegProc* rp = NULL;
HashValue hval;
@@ -406,31 +407,33 @@ erts_whereis_name(Process *c_p,
*port = NULL;
else {
#ifdef ERTS_SMP
- if (pending_port == rp->pt)
- pending_port = NULL;
- else {
- if (pending_port) {
- /* Ahh! Registered port changed while reg lock
- was unlocked... */
- erts_port_release(pending_port);
- pending_port = NULL;
- }
+ if (lock_port) {
+ if (pending_port == rp->pt)
+ pending_port = NULL;
+ else {
+ if (pending_port) {
+ /* Ahh! Registered port changed while reg lock
+ was unlocked... */
+ erts_port_release(pending_port);
+ pending_port = NULL;
+ }
- if (erts_smp_port_trylock(rp->pt) == EBUSY) {
- Eterm id = rp->pt->common.id; /* id read only... */
- /* Unlock all locks, acquire port lock, and restart... */
- if (current_c_p_locks) {
- erts_smp_proc_unlock(c_p, current_c_p_locks);
- current_c_p_locks = 0;
- }
- reg_read_unlock();
- pending_port = erts_id2port(id);
- goto restart;
- }
- }
+ if (erts_smp_port_trylock(rp->pt) == EBUSY) {
+ Eterm id = rp->pt->common.id; /* id read only... */
+ /* Unlock all locks, acquire port lock, and restart... */
+ if (current_c_p_locks) {
+ erts_smp_proc_unlock(c_p, current_c_p_locks);
+ current_c_p_locks = 0;
+ }
+ reg_read_unlock();
+ pending_port = erts_id2port(id);
+ goto restart;
+ }
+ }
+ ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(rp->pt));
+ }
#endif
*port = rp->pt;
- ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(*port));
}
}
@@ -452,7 +455,7 @@ erts_whereis_process(Process *c_p,
int flags)
{
Process *proc;
- erts_whereis_name(c_p, c_p_locks, name, &proc, need_locks, flags, NULL);
+ erts_whereis_name(c_p, c_p_locks, name, &proc, need_locks, flags, NULL, 0);
return proc;
}
diff --git a/erts/emulator/beam/register.h b/erts/emulator/beam/register.h
index 88ab7b7bf1..d839f55d6b 100644
--- a/erts/emulator/beam/register.h
+++ b/erts/emulator/beam/register.h
@@ -49,7 +49,7 @@ int erts_register_name(Process *, Eterm, Eterm);
Eterm erts_whereis_name_to_id(Process *, Eterm);
void erts_whereis_name(Process *, ErtsProcLocks,
Eterm, Process**, ErtsProcLocks, int,
- Port**);
+ Port**, int);
Process *erts_whereis_process(Process *,
ErtsProcLocks,
Eterm,
diff --git a/erts/emulator/test/monitor_SUITE.erl b/erts/emulator/test/monitor_SUITE.erl
index 8955e62df5..90d2bd8c5d 100644
--- a/erts/emulator/test/monitor_SUITE.erl
+++ b/erts/emulator/test/monitor_SUITE.erl
@@ -21,6 +21,7 @@
-module(monitor_SUITE).
-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
-export([all/0, suite/0, groups/0,
case_1/1, case_1a/1, case_2/1, case_2a/1, mon_e_1/1, demon_e_1/1, demon_1/1,
@@ -706,7 +707,7 @@ named_down(Config) when is_list(Config) ->
spawn_opt(fun () ->
WFun = fun
(F, hej) -> F(F, hopp);
-(F, hopp) -> F(F, hej)
+ (F, hopp) -> F(F, hej)
end,
NoSchedulers = erlang:system_info(schedulers_online),
lists:foreach(fun (_) ->
@@ -726,13 +727,14 @@ named_down(Config) when is_list(Config) ->
NamedProc = spawn_link(fun () ->
receive after infinity -> ok end
end),
- true = register(Name, NamedProc),
+ ?assertEqual(true, register(Name, NamedProc)),
unlink(NamedProc),
exit(NamedProc, bang),
Mon = erlang:monitor(process, Name),
- receive {'DOWN',Mon, _, _, _} -> ok end,
- true = register(Name, self()),
- true = unregister(Name),
+ receive {'DOWN',Mon, _, _, bang} -> ok
+ after 3000 -> ?assert(false) end,
+ ?assertEqual(true, register(Name, self())),
+ ?assertEqual(true, unregister(Name)),
process_flag(priority,Prio),
ok.
diff --git a/erts/emulator/test/port_SUITE.erl b/erts/emulator/test/port_SUITE.erl
index 79abcbde5f..ee07699884 100644
--- a/erts/emulator/test/port_SUITE.erl
+++ b/erts/emulator/test/port_SUITE.erl
@@ -74,27 +74,68 @@
%%
--export([all/0, suite/0, groups/0,
- init_per_testcase/2, end_per_testcase/2,
- init_per_suite/1, end_per_suite/1,
- stream_small/1, stream_big/1,
- basic_ping/1, slow_writes/1, bad_packet/1, bad_port_messages/1,
- mul_basic/1, mul_slow_writes/1,
- dying_port/1, port_program_with_path/1,
- open_input_file_port/1, open_output_file_port/1,
- count_fds/1,
- iter_max_ports/1, eof/1, input_only/1, output_only/1,
- name1/1,
- t_binary/1, parallell/1, t_exit/1,
- env/1, huge_env/1, bad_env/1, cd/1, exit_status/1,
- bad_args/1,
- tps_16_bytes/1, tps_1K/1, line/1, stderr_to_stdout/1,
- otp_3906/1, otp_4389/1, win_massive/1, win_massive_client/1,
- mix_up_ports/1, otp_5112/1, otp_5119/1, otp_6224/1,
- exit_status_multi_scheduling_block/1, ports/1,
- spawn_driver/1, spawn_executable/1, close_deaf_port/1,
- port_setget_data/1,
- unregister_name/1, parallelism_option/1]).
+-export([all/0, suite/0, groups/0, init_per_testcase/2, end_per_testcase/2,
+ init_per_suite/1, end_per_suite/1]).
+-export([
+ bad_args/1,
+ bad_env/1,
+ bad_packet/1,
+ bad_port_messages/1,
+ basic_ping/1,
+ cd/1,
+ close_deaf_port/1,
+ count_fds/1,
+ dying_port/1,
+ env/1,
+ eof/1,
+ exit_status/1,
+ exit_status_multi_scheduling_block/1,
+ huge_env/1,
+ input_only/1,
+ iter_max_ports/1,
+ line/1,
+ mix_up_ports/1,
+ mon_port_invalid_type/1,
+ mon_port_bad_named/1,
+ mon_port_bad_remote_on_local/1,
+ mon_port_local/1,
+ mon_port_name_demonitor/1,
+ mon_port_named/1,
+ mon_port_origin_dies/1,
+ mon_port_pid_demonitor/1,
+ mon_port_remote_on_remote/1,
+ mon_port_driver_die/1,
+ mon_port_driver_die_demonitor/1,
+ mul_basic/1,
+ mul_slow_writes/1,
+ name1/1,
+ open_input_file_port/1,
+ open_output_file_port/1,
+ otp_3906/1,
+ otp_4389/1,
+ otp_5112/1,
+ otp_5119/1,
+ otp_6224/1,
+ output_only/1,
+ parallelism_option/1,
+ parallell/1,
+ port_program_with_path/1,
+ port_setget_data/1,
+ ports/1,
+ slow_writes/1,
+ spawn_driver/1,
+ spawn_executable/1,
+ stderr_to_stdout/1,
+ stream_big/1,
+ stream_small/1,
+ t_binary/1,
+ t_exit/1,
+ tps_16_bytes/1,
+ tps_1K/1,
+ unregister_name/1,
+ win_massive/1,
+ win_massive_client/1
+]).
-export([do_iter_max_ports/2]).
@@ -105,12 +146,13 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
+-include_lib("eunit/include/eunit.hrl").
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap, {seconds, 10}}].
-all() ->
+all() ->
[otp_6224, {group, stream}, basic_ping, slow_writes,
bad_packet, bad_port_messages, {group, options},
{group, multiple_packets}, parallell, dying_port,
@@ -123,14 +165,32 @@ all() ->
exit_status_multi_scheduling_block, ports, spawn_driver,
spawn_executable, close_deaf_port, unregister_name,
port_setget_data,
- parallelism_option].
-
-groups() ->
+ parallelism_option,
+ mon_port_invalid_type,
+ mon_port_local,
+ mon_port_remote_on_remote,
+ mon_port_bad_remote_on_local,
+ mon_port_origin_dies,
+ mon_port_named,
+ mon_port_bad_named,
+ mon_port_pid_demonitor,
+ mon_port_name_demonitor,
+ mon_port_driver_die,
+ mon_port_driver_die_demonitor
+ ].
+
+groups() ->
[{stream, [], [stream_small, stream_big]},
{options, [], [t_binary, eof, input_only, output_only]},
{multiple_packets, [], [mul_basic, mul_slow_writes]},
{tps, [], [tps_16_bytes, tps_1K]}].
+init_per_testcase(Case, Config) when Case =:= mon_port_driver_die;
+ Case =:= mon_port_driver_die_demonitor ->
+ case erlang:system_info(schedulers_online) of
+ 1 -> {skip, "Need 2 schedulers to run testcase"};
+ _ -> Config
+ end;
init_per_testcase(Case, Config) ->
[{testcase, Case} |Config].
@@ -160,7 +220,7 @@ do_win_massive() ->
ct:timetrap({minutes, 6}),
SuiteDir = filename:dirname(code:which(?MODULE)),
Ports = " +Q 8192",
- {ok, Node} =
+ {ok, Node} =
test_server:start_node(win_massive,
slave,
[{args, " -pa " ++ SuiteDir ++ Ports}]),
@@ -169,7 +229,7 @@ do_win_massive() ->
ok.
win_massive_client(N) ->
- {ok,P}=gen_tcp:listen(?WIN_MASSIVE_PORT,[{reuseaddr,true}]),
+ {ok,P}=gen_tcp:listen(?WIN_MASSIVE_PORT,[{reuseaddr,true}]),
L = win_massive_loop(P,N),
Len = length(L),
lists:foreach(fun(E) ->
@@ -278,7 +338,7 @@ bad_port_messages(Config) when is_list(Config) ->
bad_message(PortTest, {self(),{connect,no_pid}}),
ok.
-bad_message(PortTest, Message) ->
+bad_message(PortTest, Message) ->
P = open_port({spawn,PortTest}, []),
P ! Message,
receive
@@ -773,7 +833,7 @@ line(Config) when is_list(Config) ->
S1 = lists:flatten(io_lib:format("-l~w", [length(L1)])),
io:format("S1 = ~w, L1 = ~w~n", [S1,L1]),
port_expect(Config,[{L1,
- [{eol, Packet1}, {noeol, Packet2}, eof]}], 0,
+ [{eol, Packet1}, {noeol, Packet2}, eof]}], 0,
S1, [{line,Siz},eof]),
%% Test that lonely <CR> Don't get treated as newlines
port_expect(Config,[{lists:append([Packet1, [13], Packet2,
@@ -844,9 +904,9 @@ env(Config) when is_list(Config) ->
{"glurf","a glorfy string"}]),
%% A lot of non existing variables (mingled with existing)
- NotExistingList = [{lists:flatten(io_lib:format("V~p_not_existing",[X])),false}
+ NotExistingList = [{lists:flatten(io_lib:format("V~p_not_existing",[X])),false}
|| X <- lists:seq(1,150)],
- ExistingList = [{lists:flatten(io_lib:format("V~p_existing",[X])),"a_value"}
+ ExistingList = [{lists:flatten(io_lib:format("V~p_existing",[X])),"a_value"}
|| X <- lists:seq(1,150)],
env_slave(Temp, lists:sort(ExistingList ++ NotExistingList)),
ok.
@@ -1320,22 +1380,22 @@ spawn_driver(Config) when is_list(Config) ->
ok = load_driver(Path, "echo_drv"),
Port = erlang:open_port({spawn_driver, "echo_drv"}, []),
Port ! {self(), {command, "Hello port!"}},
- receive
- {Port, {data, "Hello port!"}} = Msg1 ->
+ receive
+ {Port, {data, "Hello port!"}} = Msg1 ->
io:format("~p~n", [Msg1]),
- ok;
+ ok;
Other ->
ct:fail({unexpected, Other})
end,
Port ! {self(), close},
receive {Port, closed} -> ok end,
- Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"},
+ Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"},
[]),
- receive
- {Port2, {data, "Hello port?"}} = Msg2 ->
+ receive
+ {Port2, {data, "Hello port?"}} = Msg2 ->
io:format("~p~n", [Msg2]),
- ok;
+ ok;
Other2 ->
ct:fail({unexpected2, Other2})
end,
@@ -1354,23 +1414,23 @@ parallelism_option(Config) when is_list(Config) ->
[{parallelism, true}]),
{parallelism, true} = erlang:port_info(Port, parallelism),
Port ! {self(), {command, "Hello port!"}},
- receive
- {Port, {data, "Hello port!"}} = Msg1 ->
+ receive
+ {Port, {data, "Hello port!"}} = Msg1 ->
io:format("~p~n", [Msg1]),
- ok;
+ ok;
Other ->
ct:fail({unexpected, Other})
end,
Port ! {self(), close},
receive {Port, closed} -> ok end,
- Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"},
+ Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"},
[{parallelism, false}]),
{parallelism, false} = erlang:port_info(Port2, parallelism),
- receive
- {Port2, {data, "Hello port?"}} = Msg2 ->
+ receive
+ {Port2, {data, "Hello port?"}} = Msg2 ->
io:format("~p~n", [Msg2]),
- ok;
+ ok;
Other2 ->
ct:fail({unexpected2, Other2})
end,
@@ -1389,20 +1449,20 @@ spawn_executable(Config) when is_list(Config) ->
["echo_args"] = run_echo_args(DataDir,[binary, "echo_args"]),
["echo_arguments"] = run_echo_args(DataDir,["echo_arguments"]),
["echo_arguments"] = run_echo_args(DataDir,[binary, "echo_arguments"]),
- [ExactFile1,"hello world","dlrow olleh"] =
+ [ExactFile1,"hello world","dlrow olleh"] =
run_echo_args(DataDir,[ExactFile1,"hello world","dlrow olleh"]),
[ExactFile1] = run_echo_args(DataDir,[default]),
[ExactFile1] = run_echo_args(DataDir,[binary, default]),
- [ExactFile1,"hello world","dlrow olleh"] =
+ [ExactFile1,"hello world","dlrow olleh"] =
run_echo_args(DataDir,[switch_order,ExactFile1,"hello world",
"dlrow olleh"]),
- [ExactFile1,"hello world","dlrow olleh"] =
+ [ExactFile1,"hello world","dlrow olleh"] =
run_echo_args(DataDir,[binary,switch_order,ExactFile1,"hello world",
"dlrow olleh"]),
[ExactFile1,"hello world","dlrow olleh"] =
run_echo_args(DataDir,[default,"hello world","dlrow olleh"]),
- [ExactFile1,"hello world","dlrow olleh"] =
+ [ExactFile1,"hello world","dlrow olleh"] =
run_echo_args_2("\""++ExactFile1++"\" "++"\"hello world\" \"dlrow olleh\""),
[ExactFile1,"hello world","dlrow olleh"] =
run_echo_args_2(unicode:characters_to_binary("\""++ExactFile1++"\" "++"\"hello world\" \"dlrow olleh\"")),
@@ -1418,7 +1478,7 @@ spawn_executable(Config) when is_list(Config) ->
[ExactFile2] = run_echo_args(SpaceDir,[]),
["echo_args"] = run_echo_args(SpaceDir,["echo_args"]),
["echo_arguments"] = run_echo_args(SpaceDir,["echo_arguments"]),
- [ExactFile2,"hello world","dlrow olleh"] =
+ [ExactFile2,"hello world","dlrow olleh"] =
run_echo_args(SpaceDir,[ExactFile2,"hello world","dlrow olleh"]),
[ExactFile2,"hello world","dlrow olleh"] =
run_echo_args(SpaceDir,[binary, ExactFile2,"hello world","dlrow olleh"]),
@@ -1429,16 +1489,16 @@ spawn_executable(Config) when is_list(Config) ->
run_echo_args(SpaceDir,[binary, ExactFile2,"hello \"world\"","\"dlrow\" olleh"]),
[ExactFile2] = run_echo_args(SpaceDir,[default]),
- [ExactFile2,"hello world","dlrow olleh"] =
+ [ExactFile2,"hello world","dlrow olleh"] =
run_echo_args(SpaceDir,[switch_order,ExactFile2,"hello world", "dlrow olleh"]),
- [ExactFile2,"hello world","dlrow olleh"] =
+ [ExactFile2,"hello world","dlrow olleh"] =
run_echo_args(SpaceDir,[default,"hello world","dlrow olleh"]),
- [ExactFile2,"hello world","dlrow olleh"] =
+ [ExactFile2,"hello world","dlrow olleh"] =
run_echo_args_2("\""++ExactFile2++"\" "++"\"hello world\" \"dlrow olleh\""),
[ExactFile2,"hello world","dlrow olleh"] =
run_echo_args_2(unicode:characters_to_binary("\""++ExactFile2++"\" "++"\"hello world\" \"dlrow olleh\"")),
- ExeExt =
+ ExeExt =
case string:to_lower(lists:last(string:tokens(ExactFile2,"."))) of
"exe" ->
".exe";
@@ -1452,17 +1512,17 @@ spawn_executable(Config) when is_list(Config) ->
[ExactFile3] = run_echo_args(SpaceDir,Executable2,[]),
["echo_args"] = run_echo_args(SpaceDir,Executable2,["echo_args"]),
["echo_arguments"] = run_echo_args(SpaceDir,Executable2,["echo_arguments"]),
- [ExactFile3,"hello world","dlrow olleh"] =
+ [ExactFile3,"hello world","dlrow olleh"] =
run_echo_args(SpaceDir,Executable2,[ExactFile3,"hello world","dlrow olleh"]),
[ExactFile3] = run_echo_args(SpaceDir,Executable2,[default]),
- [ExactFile3,"hello world","dlrow olleh"] =
+ [ExactFile3,"hello world","dlrow olleh"] =
run_echo_args(SpaceDir,Executable2,
[switch_order,ExactFile3,"hello world",
"dlrow olleh"]),
- [ExactFile3,"hello world","dlrow olleh"] =
+ [ExactFile3,"hello world","dlrow olleh"] =
run_echo_args(SpaceDir,Executable2,
[default,"hello world","dlrow olleh"]),
- [ExactFile3,"hello world","dlrow olleh"] =
+ [ExactFile3,"hello world","dlrow olleh"] =
run_echo_args_2("\""++ExactFile3++"\" "++"\"hello world\" \"dlrow olleh\""),
[ExactFile3,"hello world","dlrow olleh"] =
run_echo_args_2(unicode:characters_to_binary("\""++ExactFile3++"\" "++"\"hello world\" \"dlrow olleh\"")),
@@ -1510,11 +1570,11 @@ test_bat_file(Dir) ->
<<"\r\n">>],
file:write_file(Full,list_to_binary(D)),
EF = filename:basename(FN),
- [DN,"hello","world"] =
+ [DN,"hello","world"] =
run_echo_args(Dir,FN,
[default,"hello","world"]),
%% The arg0 argumant should be ignored when running batch files
- [DN,"hello","world"] =
+ [DN,"hello","world"] =
run_echo_args(Dir,FN,
["knaskurt","hello","world"]),
EF = filename:basename(DN),
@@ -1533,10 +1593,10 @@ test_sh_file(Dir) ->
<<"done\n">>],
file:write_file(Full,list_to_binary(D)),
chmodplusx(Full),
- [Full,"hello","world"] =
+ [Full,"hello","world"] =
run_echo_args(Dir,FN,
[default,"hello","world"]),
- [Full,"hello","world of spaces"] =
+ [Full,"hello","world of spaces"] =
run_echo_args(Dir,FN,
[default,"hello","world of spaces"]),
file:write_file(filename:join([Dir,"testfile1"]),<<"testdata1">>),
@@ -1544,7 +1604,7 @@ test_sh_file(Dir) ->
Pattern = filename:join([Dir,"testfile*"]),
L = filelib:wildcard(Pattern),
2 = length(L),
- [Full,"hello",Pattern] =
+ [Full,"hello",Pattern] =
run_echo_args(Dir,FN,
[default,"hello",Pattern]),
ok.
@@ -1620,10 +1680,10 @@ mix_up_ports(Config) when is_list(Config) ->
ok = load_driver(Path, "echo_drv"),
Port = erlang:open_port({spawn, "echo_drv"}, []),
Port ! {self(), {command, "Hello port!"}},
- receive
- {Port, {data, "Hello port!"}} = Msg1 ->
+ receive
+ {Port, {data, "Hello port!"}} = Msg1 ->
io:format("~p~n", [Msg1]),
- ok;
+ ok;
Other ->
ct:fail({unexpected, Other})
end,
@@ -1631,7 +1691,7 @@ mix_up_ports(Config) when is_list(Config) ->
receive {Port, closed} -> ok end,
loop(start, done,
fun(P) ->
- Q =
+ Q =
(catch erlang:open_port({spawn, "echo_drv"}, [])),
%% io:format("~p ", [Q]),
if is_port(Q) ->
@@ -1642,7 +1702,7 @@ mix_up_ports(Config) when is_list(Config) ->
end
end),
Port ! {self(), {command, "Hello again port!"}},
- receive
+ receive
Msg2 ->
ct:fail({unexpected, Msg2})
after 1000 ->
@@ -1802,7 +1862,7 @@ exit_status_msb_test(Config, SleepSecs) when is_list(Config) ->
%% We want to start port programs from as many schedulers as possible
%% and we want these port programs to terminate while multi-scheduling
%% is blocked.
- %%
+ %%
NoSchedsOnln = erlang:system_info(schedulers_online),
Parent = self(),
io:format("SleepSecs = ~p~n", [SleepSecs]),
@@ -2214,7 +2274,7 @@ ports_snapshots(0, _, _) ->
ok;
ports_snapshots(Iter, TrafficPid, OtherPorts) ->
- TrafficPid ! start,
+ TrafficPid ! start,
receive after 1 -> ok end,
Snapshot = erlang:ports(),
@@ -2243,7 +2303,7 @@ ports_traffic_stopped(MaxPorts, {PortList, PortCnt}) ->
end.
ports_traffic_started(MaxPorts, {PortList, PortCnt}, EventList) ->
- receive
+ receive
{Pid, stop} ->
%%io:format("Traffic stopped in ~p\n",[self()]),
Pid ! {self(), EventList, PortList},
@@ -2256,7 +2316,7 @@ ports_traffic_started(MaxPorts, {PortList, PortCnt}, EventList) ->
ports_traffic_do(MaxPorts, {PortList, PortCnt}, EventList) ->
N = uniform(MaxPorts),
case N > PortCnt of
- true -> % Open port
+ true -> % Open port
P = open_port({spawn, "exit_drv"}, []),
%%io:format("Created port ~p\n",[P]),
ports_traffic_started(MaxPorts, {[P|PortList], PortCnt+1},
@@ -2270,7 +2330,7 @@ ports_traffic_do(MaxPorts, {PortList, PortCnt}, EventList) ->
[{close,P}|EventList])
end.
-ports_verify(Ports, PortsAfter, EventList) ->
+ports_verify(Ports, PortsAfter, EventList) ->
%%io:format("Candidate=~p\nEvents=~p\n", [PortsAfter, EventList]),
case lists:sort(Ports) =:= lists:sort(PortsAfter) of
true ->
@@ -2280,10 +2340,10 @@ ports_verify(Ports, PortsAfter, EventList) ->
%% Note that we track the event list "backwards", undoing open/close:
case EventList of
[{open,P} | Tail] ->
- ports_verify(Ports, lists:delete(P,PortsAfter), Tail);
+ ports_verify(Ports, lists:delete(P,PortsAfter), Tail);
[{close,P} | Tail] ->
- ports_verify(Ports, [P | PortsAfter], Tail);
+ ports_verify(Ports, [P | PortsAfter], Tail);
[] ->
ct:fail("Inconsistent snapshot from erlang:ports()")
@@ -2391,3 +2451,227 @@ wait_until(Fun) ->
receive after 100 -> ok end,
wait_until(Fun)
end.
+
+%% Attempt to monitor pid as port, and port as pid
+mon_port_invalid_type(_Config) ->
+ Port = hd(erlang:ports()),
+ ?assertError(badarg, erlang:monitor(port, self())),
+ ?assertError(badarg, erlang:monitor(process, Port)),
+ ok.
+
+%% With local port
+mon_port_local(Config) ->
+ Port1 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte
+ Ref1 = erlang:monitor(port, Port1),
+ ?assertMatch({proc_monitors, true, port_monitored_by, true},
+ port_is_monitored(self(), Port1)),
+ Port1 ! {self(), {command, <<"1">>}}, % port test will close self immediately
+ receive ExitP1 -> ?assertMatch({'DOWN', Ref1, port, Port1, _}, ExitP1)
+ after 1000 -> ?assert(false) end,
+ ?assertMatch({proc_monitors, false, port_monitored_by, false},
+ port_is_monitored(self(), Port1)),
+
+ %% Trying to re-monitor a port which exists but is not healthy will
+ %% succeed but then will immediately send DOWN
+ Ref2 = erlang:monitor(port, Port1),
+ receive ExitP2 -> ?assertMatch({'DOWN', Ref2, port, Port1, _}, ExitP2)
+ after 1000 -> ?assert(false) end,
+ ok.
+
+%% With remote port on remote node (should fail)
+mon_port_remote_on_remote(_Config) ->
+ Port3 = binary_to_term(<<131, 102, % Ext term format: PORT_EXT
+ 100, 0, 13, "fgsfds@fgsfds", % Node :: ATOM_EXT
+ 1:32/big, % Id
+ 0>>), % Creation
+ ?assertError(badarg, erlang:monitor(port, Port3)),
+ ok.
+
+%% Remote port belongs to this node and does not exist
+%% Port4 produces #Port<0.167772160> which should not exist in a test run
+mon_port_bad_remote_on_local(_Config) ->
+ Port4 = binary_to_term(<<131, 102, % Ext term format: PORT_EXT
+ 100, 0, 13, "nonode@nohost", % Node
+ 167772160:32/big, % Id
+ 0>>), % Creation
+ ?assertError(badarg, erlang:monitor(port, Port4)),
+ ok.
+
+%% Monitor owner (origin) dies before port is closed
+mon_port_origin_dies(Config) ->
+ Port5 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte
+ Self5 = self(),
+ Proc5 = spawn(fun() ->
+ Self5 ! test5_started,
+ erlang:monitor(port, Port5),
+ receive stop -> ok end
+ end),
+ erlang:monitor(process, Proc5), % we want to sync with its death
+ receive test5_started -> ok
+ after 1000 -> ?assert(false) end,
+ ?assertMatch({proc_monitors, true, port_monitored_by, true},
+ port_is_monitored(Proc5, Port5)),
+ Proc5 ! stop,
+ % receive from monitor (removing race condition)
+ receive ExitP5 -> ?assertMatch({'DOWN', _, process, Proc5, _}, ExitP5)
+ after 1000 -> ?assert(false) end,
+ ?assertMatch({proc_monitors, false, port_monitored_by, false},
+ port_is_monitored(Proc5, Port5)),
+ Port5 ! {self(), {command, <<"1">>}}, % make port quit
+ ok.
+
+%% Monitor a named port
+mon_port_named(Config) ->
+ Name6 = test_port6,
+ Port6 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte
+ erlang:register(Name6, Port6),
+ erlang:monitor(port, Name6),
+ ?assertMatch({proc_monitors, true, port_monitored_by, true},
+ port_is_monitored(self(), Name6)),
+ Port6 ! {self(), {command, <<"1">>}}, % port test will close self immediately
+ receive ExitP6 -> ?assertMatch({'DOWN', _, port, {Name6, _}, _}, ExitP6)
+ after 1000 -> ?assert(false) end,
+ ?assertMatch({proc_monitors, false, port_monitored_by, false},
+ port_is_monitored(self(), Name6)),
+ ok.
+
+%% Named does not exist: Should succeed but immediately send 'DOWN'
+mon_port_bad_named(_Config) ->
+ Name7 = test_port7,
+ erlang:monitor(port, Name7),
+ receive {'DOWN', _, port, {Name7, _}, noproc} -> ok
+ after 1000 -> ?assert(false) end,
+ ok.
+
+%% Monitor a pid and demonitor by ref
+mon_port_pid_demonitor(Config) ->
+ Port8 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte
+ Ref8 = erlang:monitor(port, Port8),
+ ?assertMatch({proc_monitors, true, port_monitored_by, true},
+ port_is_monitored(self(), Port8)),
+ erlang:demonitor(Ref8),
+ ?assertMatch({proc_monitors, false, port_monitored_by, false},
+ port_is_monitored(self(), Port8)),
+ Port8 ! {self(), {command, <<"1">>}}, % port test will close self immediately
+ ok.
+
+%% Monitor by name and demonitor by ref
+mon_port_name_demonitor(Config) ->
+ Name9 = test_port9,
+ Port9 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte
+ erlang:register(Name9, Port9),
+ Ref9 = erlang:monitor(port, Name9),
+ ?assertMatch({proc_monitors, true, port_monitored_by, true},
+ port_is_monitored(self(), Name9)),
+ erlang:demonitor(Ref9),
+ ?assertMatch({proc_monitors, false, port_monitored_by, false},
+ port_is_monitored(self(), Name9)),
+ Port9 ! {self(), {command, <<"1">>}}, % port test will close self immediately
+ ok.
+
+%% 1. Spawn a port which will sleep 3 seconds
+%% 2. Port driver and dies horribly (via C driver_failure call). This should
+%% mark port as exiting or something.
+%% 3. While the command happens, a monitor is requested on the port
+mon_port_driver_die(Config) ->
+ erlang:process_flag(scheduler, 1),
+
+ Path = proplists:get_value(data_dir, Config),
+ ok = load_driver(Path, "sleep_failure_drv"),
+ Port = open_port({spawn, "sleep_failure_drv"}, []),
+
+ Self = self(),
+ erlang:spawn_opt(fun() ->
+ timer:sleep(250),
+ Ref = erlang:monitor(port, Port),
+ %% Now check that msg actually arrives
+ receive
+ {'DOWN', Ref, _Port2, _, _} = M -> Self ! M
+ after 3000 -> Self ! no_down_message
+ end
+ end,[{scheduler, 2}]),
+ Port ! {self(), {command, "Fail, please!"}},
+ receive
+ A when is_atom(A) -> ?assertEqual(A, 'A_should_be_printed');
+ {'DOWN', _R, port, Port, noproc} -> ok;
+ {'DOWN', _R, _P, _, _} = M -> ct:fail({got_wrong_down,M})
+ after 5000 -> ?assert(false)
+ end,
+ ok.
+
+
+%% 1. Spawn a port which will sleep 3 seconds
+%% 2. Monitor port
+%% 3. Port driver and dies horribly (via C driver_failure call). This should
+%% mark port as exiting or something.
+%% 4. While the command happens, a demonitor is requested on the port
+mon_port_driver_die_demonitor(Config) ->
+ erlang:process_flag(scheduler, 1),
+
+ Path = proplists:get_value(data_dir, Config),
+ ok = load_driver(Path, "sleep_failure_drv"),
+ Port = open_port({spawn, "sleep_failure_drv"}, []),
+
+ Self = self(),
+ erlang:spawn_opt(
+ fun() ->
+ Ref = erlang:monitor(port, Port),
+ Self ! Ref,
+ timer:sleep(250),
+ erlang:demonitor(Ref),
+ %% Now check that msg still arrives,
+ %% the demon should have arrived after
+ %% the port exited
+ receive
+ {'DOWN', Ref, _Port2, _, _} = M -> Self ! M
+ after 3000 -> Self ! no_down_message
+ end
+ end,[{scheduler, 2}]),
+ Ref = receive R -> R end,
+ Port ! {self(), {command, "Fail, please!"}},
+ receive
+ {'DOWN', Ref, port, Port, normal} -> ok;
+ {'DOWN', _R, _P, _, _} = M -> ct:fail({got_wrong_down,M})
+ after 5000 -> ?assert(false)
+ end,
+ ok.
+
+%% @doc Makes a controllable port for testing. Underlying mechanism of this
+%% port is not important, only important is our ability to close/kill it or
+%% have it monitored.
+create_port(Config, Args) ->
+ DataDir = ?config(data_dir, Config),
+ %% Borrow port test utility from port SUITE
+ Program = filename:join([DataDir, "port_test"]),
+ erlang:open_port({spawn_executable, Program}, [{args, Args}]).
+
+%% @doc Checks if process Pid exists, and if so, if its monitoring (or not)
+%% the Port (or if port doesn't exist, we assume answer is no).
+port_is_monitored(Pid, Port) when is_pid(Pid), is_port(Port) ->
+ %% Variant for when port is a port id (port())
+ A = case erlang:process_info(Pid, monitors) of
+ undefined -> false;
+ {monitors, ProcMTargets} -> lists:member({port, Port}, ProcMTargets)
+ end,
+ B = case erlang:port_info(Port, monitored_by) of
+ undefined -> false;
+ {monitored_by, PortMonitors} -> lists:member(Pid, PortMonitors)
+ end,
+ {proc_monitors, A, port_monitored_by, B};
+port_is_monitored(Pid, PortName) when is_pid(Pid), is_atom(PortName) ->
+ %% Variant for when port is an atom
+ A = case erlang:process_info(Pid, monitors) of
+ undefined -> false;
+ {monitors, ProcMTargets} ->
+ lists:member({port, {PortName, node()}}, ProcMTargets)
+ end,
+ B = case erlang:whereis(PortName) of
+ undefined -> false; % name is not registered or is dead
+ PortId ->
+ case erlang:port_info(PortId, monitored_by) of
+ undefined -> false; % is dead
+ {monitored_by, PortMonitors} ->
+ lists:member(Pid, PortMonitors)
+ end
+ end,
+ {proc_monitors, A, port_monitored_by, B}.
diff --git a/erts/emulator/test/port_SUITE_data/Makefile.src b/erts/emulator/test/port_SUITE_data/Makefile.src
index ff822ae720..fb7685c4b6 100644
--- a/erts/emulator/test/port_SUITE_data/Makefile.src
+++ b/erts/emulator/test/port_SUITE_data/Makefile.src
@@ -4,7 +4,7 @@ CFLAGS = @CFLAGS@ -I@erl_include@ @DEFS@
CROSSLDFLAGS = @CROSSLDFLAGS@
PROGS = port_test@exe@ echo_args@exe@ dead_port@exe@
-DRIVERS = echo_drv@dll@ exit_drv@dll@ failure_drv@dll@
+DRIVERS = echo_drv@dll@ exit_drv@dll@ failure_drv@dll@ sleep_failure_drv@dll@
all: $(PROGS) $(DRIVERS) port_test.@EMULATOR@
diff --git a/erts/emulator/test/port_SUITE_data/sleep_failure_drv.c b/erts/emulator/test/port_SUITE_data/sleep_failure_drv.c
new file mode 100644
index 0000000000..1f52646572
--- /dev/null
+++ b/erts/emulator/test/port_SUITE_data/sleep_failure_drv.c
@@ -0,0 +1,76 @@
+#include <stdio.h>
+#include "erl_driver.h"
+#ifdef __WIN32__
+# include <windows.h>
+#else
+# include <unistd.h>
+#endif
+
+typedef struct _erl_drv_data FailureDrvData;
+
+static FailureDrvData *failure_drv_start(ErlDrvPort, char *);
+static void failure_drv_stop(FailureDrvData *);
+static void failure_drv_output(ErlDrvData, char *, ErlDrvSizeT);
+static void failure_drv_finish(void);
+
+static ErlDrvEntry failure_drv_entry = {
+ NULL, /* init */
+ failure_drv_start,
+ failure_drv_stop,
+ failure_drv_output,
+ NULL, /* ready_input */
+ NULL, /* ready_output */
+ "sleep_failure_drv",
+ NULL, /* finish */
+ NULL, /* handle */
+ NULL, /* control */
+ NULL, /* timeout */
+ NULL, /* outputv */
+ NULL, /* ready_async */
+ NULL,
+ NULL,
+ NULL,
+ ERL_DRV_EXTENDED_MARKER,
+ ERL_DRV_EXTENDED_MAJOR_VERSION,
+ ERL_DRV_EXTENDED_MINOR_VERSION,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+};
+
+
+
+/* -------------------------------------------------------------------------
+** Entry functions
+**/
+
+DRIVER_INIT(failure_drv)
+{
+ return &failure_drv_entry;
+}
+
+static FailureDrvData *failure_drv_start(ErlDrvPort port, char *command) {
+ void *void_ptr;
+
+ return void_ptr = port;
+}
+
+static void failure_drv_stop(FailureDrvData *data_p) {
+}
+
+static void failure_drv_output(ErlDrvData drv_data, char *buf, ErlDrvSizeT len) {
+ FailureDrvData *data_p = (FailureDrvData *) drv_data;
+ void *void_ptr;
+ ErlDrvPort port = void_ptr = data_p;
+
+#ifdef __WIN32__
+ Sleep(3000);
+#else
+ sleep(3);
+#endif
+ driver_failure(port, 0);
+}
+
+static void failure_drv_finish() {
+}
diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl
index 94f3078173..edf79b8f75 100644
--- a/erts/preloaded/src/erlang.erl
+++ b/erts/preloaded/src/erlang.erl
@@ -1206,16 +1206,18 @@ module_loaded(_Module) ->
erlang:nif_error(undefined).
-type registered_name() :: atom().
-
-type registered_process_identifier() :: registered_name() | {registered_name(), node()}.
-
-type monitor_process_identifier() :: pid() | registered_process_identifier().
+-type monitor_port_identifier() :: port() | registered_name().
%% monitor/2
--spec monitor(process, monitor_process_identifier()) -> MonitorRef when
- MonitorRef :: reference();
- (time_offset, clock_service) -> MonitorRef when
- MonitorRef :: reference().
+-spec monitor
+ (process, monitor_process_identifier()) -> MonitorRef
+ when MonitorRef :: reference();
+ (port, monitor_port_identifier()) -> MonitorRef
+ when MonitorRef :: reference();
+ (time_offset, clock_service) -> MonitorRef
+ when MonitorRef :: reference().
monitor(_Type, _Item) ->
erlang:nif_error(undefined).
@@ -2160,7 +2162,7 @@ process_flag(_Flag, _Value) ->
{max_heap_size, MaxHeapSize :: max_heap_size()} |
{monitored_by, Pids :: [pid()]} |
{monitors,
- Monitors :: [{process, Pid :: pid() |
+ Monitors :: [{process | port, Pid :: pid() | port() |
{RegName :: atom(), Node :: node()}}]} |
{message_queue_data, MQD :: message_queue_data()} |
{priority, Level :: priority_level()} |
@@ -3087,6 +3089,9 @@ port_info(Port) ->
(Port, monitors) -> {monitors, Monitors} | 'undefined' when
Port :: port() | atom(),
Monitors :: [{process, pid()}];
+ (Port, monitored_by) -> {monitored_by, MonitoredBy} | 'undefined' when
+ Port :: port() | atom(),
+ MonitoredBy :: [pid()];
(Port, name) -> {name, Name} | 'undefined' when
Port :: port() | atom(),
Name :: string();
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml
index 5231ef24a4..264bcff251 100644
--- a/lib/common_test/doc/src/ct.xml
+++ b/lib/common_test/doc/src/ct.xml
@@ -935,7 +935,7 @@
</func>
<func>
- <name>reload_config(Required) -&gt; ValueOrElement</name>
+ <name>reload_config(Required) -&gt; ValueOrElement | {error, Reason}</name>
<fsummary>Reloads configuration file containing specified configuration
key.</fsummary>
<type>
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 455864efb6..53245c596a 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -137,7 +137,8 @@ close(Info, StartDir) ->
%% so we need to use a local copy of the log cache data
LogCacheBin =
case make_last_run_index() of
- {error,_} -> % log server not responding
+ {error, Reason} -> % log server not responding
+ io:format("Warning! ct_logs not responding: ~p~n", [Reason]),
undefined;
LCB ->
LCB
@@ -240,7 +241,7 @@ call(Msg) ->
Pid ->
MRef = erlang:monitor(process,Pid),
Ref = make_ref(),
- ?MODULE ! {Msg,{self(),Ref}},
+ Pid ! {Msg,{self(),Ref}},
receive
{Ref, Result} ->
erlang:demonitor(MRef, [flush]),
diff --git a/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl b/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl
index 1404df6410..19eb1211fa 100644
--- a/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl
+++ b/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl
@@ -73,24 +73,28 @@ handles_to_multi_conn_pids(_Config) ->
ConnPid3 = ct_gen_conn:get_conn_pid(Handle3),
{true,true} = {is_process_alive(Handle3),is_process_alive(ConnPid3)},
+ monitor_procs([Handle1,ConnPid1,Handle2,ConnPid2,Handle3,ConnPid3]),
+
ok = proto:close(Handle1),
- ct:sleep(100),
+ ok = wait_procs_down([Handle1,ConnPid1]),
{false,false} = {is_process_alive(Handle1),is_process_alive(ConnPid1)},
{true,true} = {is_process_alive(Handle2),is_process_alive(ConnPid2)},
ok = proto:kill_conn_proc(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([ConnPid2]),
{true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2)},
ConnPid2x = ct_gen_conn:get_conn_pid(Handle2),
true = is_process_alive(ConnPid2x),
+ monitor_procs([ConnPid2x]),
+
ok = proto:close(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2,ConnPid2x]),
{false,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2x)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- ct:sleep(100),
+ ok = wait_procs_down([Handle3,ConnPid3]),
{false,false} = {is_process_alive(Handle3),is_process_alive(ConnPid3)},
ok.
@@ -116,24 +120,28 @@ handles_to_single_conn_pids(_Config) ->
{undefined,Handle3,_,_}] = lists:sort(ct_util:get_connections(ConnPid)),
ct:pal("CONNS = ~n~p", [Conns]),
+ monitor_procs([Handle1,Handle2,Handle3,ConnPid]),
ok = proto:close(Handle1),
- ct:sleep(100),
+ ok = wait_procs_down([Handle1]),
{false,true} = {is_process_alive(Handle1),is_process_alive(ConnPid)},
ok = proto:kill_conn_proc(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([ConnPid]),
NewConnPid = ct_gen_conn:get_conn_pid(Handle2),
NewConnPid = ct_gen_conn:get_conn_pid(Handle3),
true = is_process_alive(Handle2),
true = is_process_alive(Handle3),
+ false = is_process_alive(ConnPid),
+
+ monitor_procs([NewConnPid]),
ok = proto:close(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2]),
{false,true} = {is_process_alive(Handle2),is_process_alive(NewConnPid)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- ct:sleep(100),
+ ok = wait_procs_down([Handle3,NewConnPid]),
{false,false} = {is_process_alive(Handle3),is_process_alive(NewConnPid)},
ok.
@@ -158,30 +166,37 @@ names_to_multi_conn_pids(_Config) ->
Handle1 = proto:open(mconn1),
+ monitor_procs([Handle1,ConnPid1,Handle2,ConnPid2,Handle3,ConnPid3]),
+
ok = proto:close(mconn1),
- ct:sleep(100),
+ ok = wait_procs_down([Handle1,ConnPid1]),
{false,false} = {is_process_alive(Handle1),is_process_alive(ConnPid1)},
ok = proto:kill_conn_proc(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([ConnPid2]),
Handle2 = proto:open(mconn2), % should've been reconnected already
{true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2)},
ConnPid2x = ct_gen_conn:get_conn_pid(Handle2),
true = is_process_alive(ConnPid2x),
+ monitor_procs([ConnPid2x]),
+
ok = proto:close(mconn2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2,ConnPid2x]),
{false,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2x)},
Handle2y = proto:open(mconn2),
ConnPid2y = ct_gen_conn:get_conn_pid(Handle2y),
{true,true} = {is_process_alive(Handle2y),is_process_alive(ConnPid2y)},
+
+ monitor_procs([Handle2y,ConnPid2y]),
+
ok = proto:close(mconn2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2y,ConnPid2y]),
{false,false} = {is_process_alive(Handle2y),is_process_alive(ConnPid2y)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- ct:sleep(100),
+ ok = wait_procs_down([Handle3,ConnPid3]),
{false,false} = {is_process_alive(Handle3),is_process_alive(ConnPid3)},
ok.
@@ -211,16 +226,20 @@ names_to_single_conn_pids(_Config) ->
{sconn3,Handle3,_,_}] = lists:sort(ct_util:get_connections(ConnPid)),
ct:pal("CONNS on ~p = ~n~p", [ConnPid,Conns]),
+ monitor_procs([Handle1,Handle2,Handle3,ConnPid]),
+
ok = proto:close(sconn1),
- ct:sleep(100),
+ ok = wait_procs_down([Handle1]),
{false,true} = {is_process_alive(Handle1),is_process_alive(ConnPid)},
ok = proto:kill_conn_proc(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([ConnPid]),
{true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid)},
Handle2 = proto:open(sconn2), % should've been reconnected already
NewConnPid = ct_gen_conn:get_conn_pid(Handle2),
true = is_process_alive(NewConnPid),
+
+ monitor_procs([NewConnPid]),
Conns1 = [{sconn2,Handle2,_,_},
{sconn3,Handle3,_,_}] =
@@ -228,14 +247,29 @@ names_to_single_conn_pids(_Config) ->
ct:pal("CONNS on ~p = ~n~p", [NewConnPid,Conns1]),
ok = proto:close(sconn2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2]),
{false,true} = {is_process_alive(Handle2),is_process_alive(NewConnPid)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- ct:sleep(100),
+ ok = wait_procs_down([Handle3,NewConnPid]),
{false,false} = {is_process_alive(Handle3),is_process_alive(NewConnPid)},
ok.
+%%%-----------------------------------------------------------------
+monitor_procs(Pids) ->
+ [erlang:monitor(process,Pid) || Pid <- Pids],
+ ok.
+
+wait_procs_down([]) ->
+ ok;
+wait_procs_down(Pids) ->
+ receive
+ {'DOWN',_,process,Pid,_} ->
+ wait_procs_down(lists:delete(Pid,Pids))
+ after 2000 ->
+ timeout
+ end.
+
diff --git a/lib/common_test/test/ct_netconfc_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE.erl
index 03fbc17bd2..2919f01605 100644
--- a/lib/common_test/test/ct_netconfc_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE.erl
@@ -44,16 +44,28 @@
%% there will be clashes with logging processes etc).
%%--------------------------------------------------------------------
init_per_suite(Config) ->
- case application:load(crypto) of
- {error,Reason} when Reason=/={already_loaded,crypto} ->
- {skip, Reason};
- _ ->
- case application:load(ssh) of
- {error,Reason} when Reason=/={already_loaded,ssh} ->
- {skip, Reason};
- _ ->
- ct_test_support:init_per_suite(Config)
- end
+ case check_crypto_and_ssh() of
+ ok ->
+ ct_test_support:init_per_suite(Config);
+ Skip ->
+ Skip
+ end.
+
+check_crypto_and_ssh() ->
+ (catch code:load_file(crypto)),
+ case code:is_loaded(crypto) of
+ {file,_} ->
+ case ssh:start() of
+ Ok when Ok==ok; Ok=={error,{already_started,ssh}} ->
+ ct:log("ssh started",[]),
+ ok;
+ Other ->
+ ct:log("could not start ssh: ~p",[Other]),
+ {skip, "SSH could not be started!"}
+ end;
+ Other ->
+ ct:log("could not load crypto: ~p",[Other]),
+ {skip, "crypto could not be loaded!"}
end.
end_per_suite(Config) ->
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
index f34969683c..2aa6c4d354 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
@@ -124,8 +124,9 @@ end_per_testcase(_Case, _Config) ->
ok.
init_per_suite(Config) ->
- case catch ssh:start() of
- Ok when Ok==ok; Ok=={error,{already_started,ssh}} ->
+ (catch code:load_file(crypto)),
+ case {ssh:start(),code:is_loaded(crypto)} of
+ {Ok,{file,_}} when Ok==ok; Ok=={error,{already_started,ssh}} ->
ct:log("ssh started",[]),
SshDir = filename:join(filename:dirname(code:which(?MODULE)),
"ssh_dir"),
@@ -133,7 +134,7 @@ init_per_suite(Config) ->
ct:log("netconf server started",[]),
[{netconf_server,Server},{ssh_dir,SshDir}|Config];
Other ->
- ct:log("could not start ssh: ~p",[Other]),
+ ct:log("could not start ssh or load crypto: ~p",[Other]),
{skip, "SSH could not be started!"}
end.
@@ -360,7 +361,7 @@ get(Config) ->
get_a_lot(Config) ->
SshDir = ?config(ssh_dir,Config),
{ok,Client} = open_success(SshDir),
- Descr = lists:append(lists:duplicate(1000,"Description of myserver! ")),
+ Descr = lists:append(lists:duplicate(100,"Description of myserver! ")),
Server = {server,[{xmlns,"myns"}],[{name,[],["myserver"]},
{description,[],[Descr]}]},
Data = lists:duplicate(100,Server),
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl
index 0a49cdabbb..a65275da43 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl
@@ -62,14 +62,15 @@ stop_node(Case) ->
init_per_suite(Config) ->
- case ssh:start() of
- Ok when Ok==ok; Ok=={error,{already_started,ssh}} ->
+ (catch code:load_file(crypto)),
+ case {ssh:start(),code:is_loaded(crypto)} of
+ {Ok,{file,_}} when Ok==ok; Ok=={error,{already_started,ssh}} ->
ct:log("SSH started locally",[]),
SshDir = filename:join(filename:dirname(code:which(?MODULE)),
"ssh_dir"),
[{ssh_dir,SshDir}|Config];
Other ->
- ct:log("could not start ssh locally: ~p",[Other]),
+ ct:log("could not start ssh or load crypto locally: ~p",[Other]),
{skip, "SSH could not be started locally!"}
end.
@@ -85,15 +86,15 @@ remote_crash(Config) ->
{ok,Node} = ct_slave:start(nc_remote_crash),
Pa = filename:dirname(code:which(?NS)),
true = rpc:call(Node,code,add_patha,[Pa]),
-
- case rpc:call(Node,ssh,start,[]) of
- Ok when Ok==ok; Ok=={error,{already_started,ssh}} ->
+ rpc:call(Node,code,load_file,[crypto]),
+ case {rpc:call(Node,ssh,start,[]),rpc:call(Node,code,is_loaded,[crypto])} of
+ {Ok,{file,_}} when Ok==ok; Ok=={error,{already_started,ssh}} ->
ct:log("SSH started remote",[]),
ns(Node,start,[?config(ssh_dir,Config)]),
ct:log("netconf server started remote",[]),
remote_crash(Node,Config);
Other ->
- ct:log("could not start ssh remote: ~p",[Other]),
+ ct:log("could not start ssh or load crypto remote: ~p",[Other]),
{skip, "SSH could not be started remote!"}
end.
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
index 9fb1fb6547..e62bc617fa 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
@@ -279,7 +279,7 @@ send({CM,Ch},Data) ->
%%% Split into many small parts and send to client
send_frag({CM,Ch},Data) ->
- Sz = rand:uniform(2000),
+ Sz = rand:uniform(1000),
case Data of
<<Chunk:Sz/binary,Rest/binary>> ->
ssh_connection:send(CM, Ch, Chunk),
diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl
index 477fcb8a26..e926abd885 100644
--- a/lib/common_test/test/ct_test_support.erl
+++ b/lib/common_test/test/ct_test_support.erl
@@ -484,7 +484,8 @@ get_events(_, Config) ->
{event_receiver,CTNode} ! {self(),get_events},
Events = receive {event_receiver,Evs} -> Evs end,
test_server:format(Level, "Stopping event receiver!~n", []),
- {event_receiver,CTNode} ! stop,
+ {event_receiver,CTNode} ! {self(),stop},
+ receive {event_receiver,stopped} -> ok end,
Events.
er() ->
@@ -499,8 +500,9 @@ er_loop(Evs) ->
{From,get_events} ->
From ! {event_receiver,lists:reverse(Evs)},
er_loop(Evs);
- stop ->
+ {From,stop} ->
unregister(event_receiver),
+ From ! {event_receiver,stopped},
ok
end.
diff --git a/lib/common_test/test/telnet_server.erl b/lib/common_test/test/telnet_server.erl
index 2c33cb268a..65300b0bdf 100644
--- a/lib/common_test/test/telnet_server.erl
+++ b/lib/common_test/test/telnet_server.erl
@@ -117,38 +117,64 @@ init_client(#state{client=Sock}=State) ->
dbg("Server sending: ~p~n",["login: "]),
R = case gen_tcp:send(Sock,"login: ") of
ok ->
- loop(State, 1);
+ loop(State);
Error ->
Error
end,
_ = gen_tcp:close(Sock),
R.
-loop(State, N) ->
+loop(State=#state{client=Sock}) ->
receive
- {tcp,_,Data} ->
+ {tcp,Sock,Data} ->
try handle_data(Data,State) of
{ok,State1} ->
- loop(State1, N);
+ loop(State1);
closed ->
+ _ = flush(State),
closed
catch
throw:Error ->
+ _ = flush(State),
Error
end;
- {tcp_closed, _} ->
+ {tcp_closed,Sock} ->
closed;
- {tcp_error,_,Error} ->
+ {tcp_error,Sock,Error} ->
{error,tcp,Error};
disconnect ->
- Sock = State#state.client,
dbg("Server closing connection on socket ~p~n", [Sock]),
+ timer:sleep(1000),
ok = gen_tcp:close(Sock),
- closed;
+ _ = flush(State);
stop ->
+ _ = flush(State),
stopped
end.
+flush(State=#state{client=Sock}) ->
+ receive
+ {tcp,Sock,Data} = M->
+ dbg("Message flushed after close or error: ~p~n", [M]),
+ try handle_data(Data,State) of
+ {ok,State1} ->
+ flush(State1);
+ closed ->
+ flush(State)
+ catch
+ throw:Error ->
+ Error
+ end;
+ {tcp_closed,Sock} = M ->
+ dbg("Message flushed after close or error: ~p~n", [M]),
+ ok;
+ {tcp_error,Sock,Error} = M ->
+ dbg("Message flushed after close or error: ~p~n", [M]),
+ {error,tcp,Error}
+ after 100 ->
+ ok
+ end.
+
handle_data(Cmd,#state{break=true}=State) ->
dbg("Server got data when in break mode: ~p~n",[Cmd]),
handle_break_cmd(Cmd,State);
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl
index 272ad10e90..976a2b8955 100644
--- a/lib/dialyzer/src/dialyzer_contracts.erl
+++ b/lib/dialyzer/src/dialyzer_contracts.erl
@@ -53,7 +53,9 @@
%% to expand records and/or remote types that they might contain.
%%-----------------------------------------------------------------------
--type tmp_contract_fun() :: fun((sets:set(mfa()), types()) -> contract_pair()).
+-type cache() :: ets:tid().
+-type tmp_contract_fun() ::
+ fun((sets:set(mfa()), types(), cache()) -> contract_pair()).
-record(tmp_contract, {contract_funs = [] :: [tmp_contract_fun()],
forms = [] :: [{_, _}]}).
@@ -153,19 +155,30 @@ process_contract_remote_types(CodeServer) ->
ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer),
RecordDict = dialyzer_codeserver:get_records(CodeServer),
ContractFun =
- fun({_M, _F, _A}, {File, #tmp_contract{contract_funs = CFuns, forms = Forms}, Xtra}) ->
- NewCs = [CFun(ExpTypes, RecordDict) || CFun <- CFuns],
- Args = general_domain(NewCs),
- {File, #contract{contracts = NewCs, args = Args, forms = Forms}, Xtra}
+ fun({{_M, _F, _A}=MFA, {File, TmpContract, Xtra}}, C0) ->
+ #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract,
+ {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) ->
+ CFun(ExpTypes, RecordDict, C1)
+ end, C0, CFuns),
+ Args = general_domain(NewCs),
+ Contract = #contract{contracts = NewCs, args = Args, forms = Forms},
+ {{MFA, {File, Contract, Xtra}}, C2}
end,
ModuleFun =
- fun(_ModuleName, ContractDict) ->
- dict:map(ContractFun, ContractDict)
+ fun({ModuleName, ContractDict}, C3) ->
+ {NewContractList, C4} =
+ lists:mapfoldl(ContractFun, C3, dict:to_list(ContractDict)),
+ {{ModuleName, dict:from_list(NewContractList)}, C4}
end,
- NewContractDict = dict:map(ModuleFun, TmpContractDict),
- NewCallbackDict = dict:map(ModuleFun, TmpCallbackDict),
+ Cache = erl_types:cache__new(),
+ {NewContractList, C5} =
+ lists:mapfoldl(ModuleFun, Cache, dict:to_list(TmpContractDict)),
+ {NewCallbackList, _C6} =
+ lists:mapfoldl(ModuleFun, C5, dict:to_list(TmpCallbackDict)),
+ NewContractDict = dict:from_list(NewContractList),
+ NewCallbackDict = dict:from_list(NewCallbackList),
dialyzer_codeserver:finalize_contracts(NewContractDict, NewCallbackDict,
- CodeServer).
+ CodeServer).
-type opaques_fun() :: fun((module()) -> [erl_types:erl_type()]).
@@ -431,19 +444,19 @@ contract_from_form(Forms, MFA, RecDict, FileLine) ->
contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], MFA, RecDict,
FileLine, TypeAcc, FormAcc) ->
TypeFun =
- fun(ExpTypes, AllRecords) ->
- NewType =
+ fun(ExpTypes, AllRecords, Cache) ->
+ {NewType, NewCache} =
try
- from_form_with_check(Form, ExpTypes, MFA, AllRecords)
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache)
catch
throw:{error, Msg} ->
{File, Line} = FileLine,
NewMsg = io_lib:format("~s:~p: ~s", [filename:basename(File),
- Line, Msg]),
+ Line, Msg]),
throw({error, NewMsg})
end,
NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType),
- {NewTypeNoVars, []}
+ {{NewTypeNoVars, []}, NewCache}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, []} | FormAcc],
@@ -452,13 +465,15 @@ contract_from_form([{type, _L1, bounded_fun,
[{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left],
MFA, RecDict, FileLine, TypeAcc, FormAcc) ->
TypeFun =
- fun(ExpTypes, AllRecords) ->
- {Constr1, VarDict} =
- process_constraints(Constr, MFA, RecDict, ExpTypes, AllRecords),
- NewType = from_form_with_check(Form, ExpTypes, MFA, AllRecords,
- VarDict),
+ fun(ExpTypes, AllRecords, Cache) ->
+ {Constr1, VarTable, Cache1} =
+ process_constraints(Constr, MFA, RecDict, ExpTypes, AllRecords,
+ Cache),
+ {NewType, NewCache} =
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords,
+ VarTable, Cache1),
NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType),
- {NewTypeNoVars, Constr1}
+ {{NewTypeNoVars, Constr1}, NewCache}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, Constr} | FormAcc],
@@ -466,74 +481,91 @@ contract_from_form([{type, _L1, bounded_fun,
contract_from_form([], _MFA, _RecDict, _FileLine, TypeAcc, FormAcc) ->
{lists:reverse(TypeAcc), lists:reverse(FormAcc)}.
-process_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords) ->
- Init0 = initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords),
+process_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) ->
+ {Init0, NewCache} = initialize_constraints(Constrs, MFA, RecDict, ExpTypes,
+ AllRecords, Cache),
Init = remove_cycles(Init0),
- constraints_fixpoint(Init, MFA, RecDict, ExpTypes, AllRecords).
+ constraints_fixpoint(Init, MFA, RecDict, ExpTypes, AllRecords, NewCache).
-initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords) ->
- initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, []).
+initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) ->
+ initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ Cache, []).
-initialize_constraints([], _MFA, _RecDict, _ExpTypes, _AllRecords, Acc) ->
- Acc;
-initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords, Acc) ->
+initialize_constraints([], _MFA, _RecDict, _ExpTypes, _AllRecords,
+ Cache, Acc) ->
+ {Acc, Cache};
+initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords,
+ Cache, Acc) ->
case Constr of
{type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]} ->
VarTable = erl_types:var_table__new(),
- T1 = final_form(Type1, ExpTypes, MFA, AllRecords, VarTable),
+ {T1, NewCache} =
+ final_form(Type1, ExpTypes, MFA, AllRecords, VarTable, Cache),
Entry = {T1, Type2},
- initialize_constraints(Rest, MFA, RecDict, ExpTypes, AllRecords, [Entry|Acc]);
+ initialize_constraints(Rest, MFA, RecDict, ExpTypes, AllRecords,
+ NewCache, [Entry|Acc]);
{type, _, constraint, [{atom,_,Name}, List]} ->
N = length(List),
throw({error,
io_lib:format("Unsupported type guard ~w/~w\n", [Name, N])})
end.
-constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, AllRecords) ->
+constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) ->
VarTable = erl_types:var_table__new(),
- VarDict =
- constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarTable),
- constraints_fixpoint(VarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords).
-
-constraints_fixpoint(OldVarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords) ->
- NewVarDict =
- constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, OldVarDict),
- case NewVarDict of
- OldVarDict ->
+ {VarTab, NewCache} =
+ constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ VarTable, Cache),
+ constraints_fixpoint(VarTab, MFA, Constrs, RecDict, ExpTypes,
+ AllRecords, NewCache).
+
+constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes,
+ AllRecords, Cache) ->
+ {NewVarTab, NewCache} =
+ constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ OldVarTab, Cache),
+ case NewVarTab of
+ OldVarTab ->
Fun =
fun(Key, Value, Acc) ->
[{subtype, erl_types:t_var(Key), Value}|Acc]
end,
- FinalConstrs = maps:fold(Fun, [], NewVarDict),
- {FinalConstrs, NewVarDict};
+ FinalConstrs = maps:fold(Fun, [], NewVarTab),
+ {FinalConstrs, NewVarTab, NewCache};
_Other ->
- constraints_fixpoint(NewVarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords)
+ constraints_fixpoint(NewVarTab, MFA, Constrs, RecDict, ExpTypes,
+ AllRecords, NewCache)
end.
-final_form(Form, ExpTypes, MFA, AllRecords, VarDict) ->
- from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarDict).
+final_form(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) ->
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache).
-from_form_with_check(Form, ExpTypes, MFA, AllRecords) ->
+from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache) ->
VarTable = erl_types:var_table__new(),
- from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable).
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache).
-from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarDict) ->
+from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) ->
Site = {spec, MFA},
- erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords, VarDict),
- erl_types:t_from_form(Form, ExpTypes, Site, AllRecords, VarDict).
-
-constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarDict) ->
- Subtypes =
- constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarDict, []),
- insert_constraints(Subtypes).
-
-constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _AllRecords, _VarDict, Acc) ->
- Acc;
-constraints_to_subs([C|Rest], MFA, RecDict, ExpTypes, AllRecords, VarDict, Acc) ->
- {T1, Form2} = C,
- T2 = final_form(Form2, ExpTypes, MFA, AllRecords, VarDict),
+ C1 = erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords,
+ VarTable, Cache),
+ erl_types:t_from_form(Form, ExpTypes, Site, AllRecords, VarTable, C1).
+
+constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, Cache) ->
+ {Subtypes, NewCache} =
+ constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, Cache, []),
+ {insert_constraints(Subtypes), NewCache}.
+
+constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _AllRecords,
+ _VarTab, Cache, Acc) ->
+ {Acc, Cache};
+constraints_to_subs([{T1, Form2}|Rest], MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, Cache, Acc) ->
+ {T2, NewCache} =
+ final_form(Form2, ExpTypes, MFA, AllRecords, VarTab, Cache),
NewAcc = [{subtype, T1, T2}|Acc],
- constraints_to_subs(Rest, MFA, RecDict, ExpTypes, AllRecords, VarDict, NewAcc).
+ constraints_to_subs(Rest, MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, NewCache, NewAcc).
%% Replaces variables with '_' when necessary to break up cycles among
%% the constraints.
@@ -647,6 +679,7 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract, _Xtra}}|Left],
{value, {Ret, Args}} ->
Sig = erl_types:t_fun(Args, Ret),
{M, _F, _A} = MFA,
+ %% io:format("MFA ~p~n", [MFA]),
Opaques = FindOpaques(M),
{File, Line} = FileLine,
WarningInfo = {File, Line, MFA},
@@ -795,7 +828,7 @@ is_remote_types_related(Contract, CSig, Sig, MFA, RecDict) ->
t_from_forms_without_remote([{FType, []}], MFA, RecDict) ->
Site = {spec, MFA},
- Type1 = erl_types:t_from_form_without_remote(FType, Site, RecDict),
+ {Type1, _} = erl_types:t_from_form_without_remote(FType, Site, RecDict),
{ok, erl_types:subst_all_vars_to_any(Type1)};
t_from_forms_without_remote([{_FType, _Constrs}], _MFA, _RecDict) ->
%% 'When' constraints
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 3349b12932..9399789464 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -522,7 +522,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
?debug("RetWithoutLocal: ~s\n", [erl_types:t_to_string(RetWithoutLocal)]),
?debug("BifRet: ~s\n", [erl_types:t_to_string(BifRange(NewArgTypes))]),
?debug("SigRange: ~s\n", [erl_types:t_to_string(SigRange)]),
- ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(NewArgTypes))]),
+ ?debug("ContrRet: ~s\n", [erl_types:t_to_string(ContrRet)]),
?debug("LocalRet: ~s\n", [erl_types:t_to_string(LocalRet)]),
State1 =
@@ -2954,11 +2954,15 @@ is_call_to_send(Tree) ->
Arity = cerl:call_arity(Tree),
cerl:is_c_atom(Mod)
andalso cerl:is_c_atom(Name)
- andalso (cerl:atom_val(Name) =:= '!')
+ andalso is_send(cerl:atom_val(Name))
andalso (cerl:atom_val(Mod) =:= erlang)
andalso (Arity =:= 2)
end.
+is_send('!') -> true;
+is_send(send) -> true;
+is_send(_) -> false.
+
is_lc_simple_list(Tree, TreeType, State) ->
Opaques = State#state.opaques,
Ann = cerl:get_ann(Tree),
@@ -3067,7 +3071,10 @@ state__add_warning(#state{warnings = Warnings, warning_mode = true} = State,
false ->
WarningInfo = {get_file(Ann), get_line(Ann), State#state.curr_fun},
Warn = {Tag, WarningInfo, Msg},
- ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)]),
+ case Tag of
+ ?WARN_CONTRACT_RANGE -> ok;
+ _ -> ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)])
+ end,
State#state{warnings = [Warn|Warnings]}
end
end.
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index d37701f03b..76a5cf3d0b 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -194,15 +194,18 @@ get_core_from_abstract_code(AbstrCode, Opts) ->
%%
%% ============================================================================
+-type type_table() :: erl_types:type_table().
+-type mod_records() :: dict:dict(module(), type_table()).
+
-spec get_record_and_type_info(abstract_code()) ->
- {'ok', dict:dict()} | {'error', string()}.
+ {'ok', type_table()} | {'error', string()}.
get_record_and_type_info(AbstractCode) ->
Module = get_module(AbstractCode),
get_record_and_type_info(AbstractCode, Module, dict:new()).
--spec get_record_and_type_info(abstract_code(), module(), dict:dict()) ->
- {'ok', dict:dict()} | {'error', string()}.
+-spec get_record_and_type_info(abstract_code(), module(), type_table()) ->
+ {'ok', type_table()} | {'error', string()}.
get_record_and_type_info(AbstractCode, Module, RecDict) ->
get_record_and_type_info(AbstractCode, Module, RecDict, "nofile").
@@ -299,92 +302,117 @@ get_record_fields([], _RecDict, Acc) ->
process_record_remote_types(CServer) ->
TempRecords = dialyzer_codeserver:get_temp_records(CServer),
ExpTypes = dialyzer_codeserver:get_exported_types(CServer),
- TempRecords1 = process_opaque_types0(TempRecords, ExpTypes),
+ Cache = erl_types:cache__new(),
+ {TempRecords1, Cache1} =
+ process_opaque_types0(TempRecords, ExpTypes, Cache),
+ %% A cache (not the field type cache) is used for speeding things up a bit.
+ VarTable = erl_types:var_table__new(),
ModuleFun =
- fun(Module, Record) ->
+ fun({Module, Record}, C0) ->
RecordFun =
- fun(Key, Value) ->
+ fun({Key, Value}, C2) ->
case Key of
{record, Name} ->
FieldFun =
- fun(Arity, Fields) ->
+ fun({Arity, Fields}, C4) ->
Site = {record, {Module, Name, Arity}},
- [{FieldName, Field,
- erl_types:t_from_form(Field,
- ExpTypes,
- Site,
- TempRecords1)}
- || {FieldName, Field, _} <- Fields]
+ {Fields1, C7} =
+ lists:mapfoldl(fun({FieldName, Field, _}, C5) ->
+ {FieldT, C6} =
+ erl_types:t_from_form
+ (Field, ExpTypes, Site,
+ TempRecords1, VarTable,
+ C5),
+ {{FieldName, Field, FieldT}, C6}
+ end, C4, Fields),
+ {{Arity, Fields1}, C7}
end,
{FileLine, Fields} = Value,
- {FileLine, orddict:map(FieldFun, Fields)};
- _Other -> Value
+ {FieldsList, C3} =
+ lists:mapfoldl(FieldFun, C2, orddict:to_list(Fields)),
+ {{Key, {FileLine, orddict:from_list(FieldsList)}}, C3};
+ _Other -> {{Key, Value}, C2}
end
end,
- dict:map(RecordFun, Record)
+ {RecordList, C1} =
+ lists:mapfoldl(RecordFun, C0, dict:to_list(Record)),
+ {{Module, dict:from_list(RecordList)}, C1}
end,
- NewRecords = dict:map(ModuleFun, TempRecords1),
- ok = check_record_fields(NewRecords, ExpTypes),
+ {NewRecordsList, C1} =
+ lists:mapfoldl(ModuleFun, Cache1, dict:to_list(TempRecords1)),
+ NewRecords = dict:from_list(NewRecordsList),
+ _C8 = check_record_fields(NewRecords, ExpTypes, C1),
dialyzer_codeserver:finalize_records(NewRecords, CServer).
%% erl_types:t_from_form() substitutes the declaration of opaque types
%% for the expanded type in some cases. To make sure the initial type,
%% any(), is not used, the expansion is done twice.
%% XXX: Recursive opaque types are not handled well.
-process_opaque_types0(TempRecords0, TempExpTypes) ->
- TempRecords1 = process_opaque_types(TempRecords0, TempExpTypes),
- process_opaque_types(TempRecords1, TempExpTypes).
+process_opaque_types0(TempRecords0, TempExpTypes, Cache) ->
+ {TempRecords1, NewCache} =
+ process_opaque_types(TempRecords0, TempExpTypes, Cache),
+ process_opaque_types(TempRecords1, TempExpTypes, NewCache).
-process_opaque_types(TempRecords, TempExpTypes) ->
+process_opaque_types(TempRecords, TempExpTypes, Cache) ->
+ VarTable = erl_types:var_table__new(),
ModuleFun =
- fun(Module, Record) ->
+ fun({Module, Record}, C0) ->
RecordFun =
- fun(Key, Value) ->
+ fun({Key, Value}, C2) ->
case Key of
{opaque, Name, NArgs} ->
{{_Module, _FileLine, Form, _ArgNames}=F, _Type} = Value,
Site = {type, {Module, Name, NArgs}},
- Type = erl_types:t_from_form(Form, TempExpTypes, Site,
- TempRecords),
- {F, Type};
- _Other -> Value
+ {Type, C3} =
+ erl_types:t_from_form(Form, TempExpTypes, Site,
+ TempRecords, VarTable, C2),
+ {{Key, {F, Type}}, C3};
+ _Other -> {{Key, Value}, C2}
end
end,
- dict:map(RecordFun, Record)
+ {RecordList, C1} =
+ lists:mapfoldl(RecordFun, C0, dict:to_list(Record)),
+ {{Module, dict:from_list(RecordList)}, C1}
+ %% dict:map(RecordFun, Record)
end,
- dict:map(ModuleFun, TempRecords).
+ {TempRecordList, NewCache} =
+ lists:mapfoldl(ModuleFun, Cache, dict:to_list(TempRecords)),
+ {dict:from_list(TempRecordList), NewCache}.
+ %% dict:map(ModuleFun, TempRecords).
-check_record_fields(Records, TempExpTypes) ->
+check_record_fields(Records, TempExpTypes, Cache) ->
+ VarTable = erl_types:var_table__new(),
CheckFun =
- fun({Module, Element}) ->
- CheckForm = fun(Form, Site) ->
- erl_types:t_check_record_fields(Form, TempExpTypes,
- Site, Records)
+ fun({Module, Element}, C0) ->
+ CheckForm = fun(Form, Site, C1) ->
+ erl_types:t_check_record_fields(Form, TempExpTypes,
+ Site, Records,
+ VarTable, C1)
end,
ElemFun =
- fun({Key, Value}) ->
+ fun({Key, Value}, C2) ->
case Key of
{record, Name} ->
FieldFun =
- fun({Arity, Fields}) ->
+ fun({Arity, Fields}, C3) ->
Site = {record, {Module, Name, Arity}},
- _ = [ok = CheckForm(Field, Site) ||
- {_, Field, _} <- Fields],
- ok
+ lists:foldl(fun({_, Field, _}, C4) ->
+ CheckForm(Field, Site, C4)
+ end, C3, Fields)
end,
{FileLine, Fields} = Value,
- Fun = fun() -> lists:foreach(FieldFun, Fields) end,
+ Fun = fun() -> lists:foldl(FieldFun, C2, Fields) end,
msg_with_position(Fun, FileLine);
{_OpaqueOrType, Name, NArgs} ->
Site = {type, {Module, Name, NArgs}},
{{_Module, FileLine, Form, _ArgNames}, _Type} = Value,
- Fun = fun() -> ok = CheckForm(Form, Site) end,
+ Fun = fun() -> CheckForm(Form, Site, C2) end,
msg_with_position(Fun, FileLine)
end
end,
- lists:foreach(ElemFun, dict:to_list(Element))
+ lists:foldl(ElemFun, C0, dict:to_list(Element))
end,
- lists:foreach(CheckFun, dict:to_list(Records)).
+ lists:foldl(CheckFun, Cache, dict:to_list(Records)).
msg_with_position(Fun, FileLine) ->
try Fun()
@@ -396,7 +424,7 @@ msg_with_position(Fun, FileLine) ->
throw({error, NewMsg})
end.
--spec merge_records(dict:dict(), dict:dict()) -> dict:dict().
+-spec merge_records(mod_records(), mod_records()) -> mod_records().
merge_records(NewRecords, OldRecords) ->
dict:merge(fun(_Key, NewVal, _OldVal) -> NewVal end, NewRecords, OldRecords).
@@ -410,7 +438,7 @@ merge_records(NewRecords, OldRecords) ->
-type spec_dict() :: dict:dict().
-type callback_dict() :: dict:dict().
--spec get_spec_info(module(), abstract_code(), dict:dict()) ->
+-spec get_spec_info(module(), abstract_code(), type_table()) ->
{'ok', spec_dict(), callback_dict()} | {'error', string()}.
get_spec_info(ModName, AbstractCode, RecordsDict) ->
@@ -676,7 +704,7 @@ format_errors([]) ->
format_sig(Type) ->
format_sig(Type, dict:new()).
--spec format_sig(erl_types:erl_type(), dict:dict()) -> string().
+-spec format_sig(erl_types:erl_type(), type_table()) -> string().
format_sig(Type, RecDict) ->
"fun(" ++ Sig = lists:flatten(erl_types:t_to_string(Type, RecDict)),
diff --git a/lib/dialyzer/test/map_SUITE_data/results/exact b/lib/dialyzer/test/map_SUITE_data/results/exact
index 374ada8869..ea00e61330 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/exact
+++ b/lib/dialyzer/test/map_SUITE_data/results/exact
@@ -1,3 +1,3 @@
exact.erl:15: Function t2/1 has no local return
-exact.erl:19: The variable _ can never match since previous clauses completely covered the type #{'a':=_, ...}
+exact.erl:19: The variable _ can never match since previous clauses completely covered the type #{'a':=_, _=>_}
diff --git a/lib/dialyzer/test/map_SUITE_data/results/guard_update b/lib/dialyzer/test/map_SUITE_data/results/guard_update
index e4bc892195..98df23907f 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/guard_update
+++ b/lib/dialyzer/test/map_SUITE_data/results/guard_update
@@ -1,5 +1,5 @@
guard_update.erl:5: Function t/0 has no local return
-guard_update.erl:6: The call guard_update:f(#{'a':=2}) will never return since it differs in the 1st argument from the success typing arguments: (#{'b':=_, ...})
+guard_update.erl:6: The call guard_update:f(#{'a':=2}) will never return since it differs in the 1st argument from the success typing arguments: (#{'b':=_, _=>_})
guard_update.erl:8: Clause guard cannot succeed. The variable M was matched against the type #{'a':=2}
guard_update.erl:8: Function f/1 has no local return
diff --git a/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2 b/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2
index 6bc0c010d7..f6fb98a863 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2
+++ b/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2
@@ -1,7 +1,7 @@
map_in_guard2.erl:10: The call map_in_guard2:assoc_guard_clause('not_a_map') will never return since it differs in the 1st argument from the success typing arguments: (map())
map_in_guard2.erl:12: The pattern 'true' can never match the type 'false'
-map_in_guard2.erl:14: The call map_in_guard2:exact_guard_clause(#{}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':=_, ...})
+map_in_guard2.erl:14: The call map_in_guard2:exact_guard_clause(#{}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':=_, _=>_})
map_in_guard2.erl:17: Clause guard cannot succeed. The variable M was matched against the type 'not_a_map'
map_in_guard2.erl:20: Function assoc_update/1 has no local return
map_in_guard2.erl:20: Guard test is_map(M::'not_a_map') can never succeed
diff --git a/lib/dialyzer/test/map_SUITE_data/results/typeflow b/lib/dialyzer/test/map_SUITE_data/results/typeflow
index e3378a24bb..acfb7f551e 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/typeflow
+++ b/lib/dialyzer/test/map_SUITE_data/results/typeflow
@@ -1,4 +1,4 @@
typeflow.erl:14: Function t2/1 has no local return
typeflow.erl:16: The call lists:sort(integer()) will never return since it differs in the 1st argument from the success typing arguments: ([any()])
-typeflow.erl:9: The variable _ can never match since previous clauses completely covered the type #{'a':=integer(), ...}
+typeflow.erl:9: The variable _ can never match since previous clauses completely covered the type #{'a':=integer(), _=>_}
diff --git a/lib/dialyzer/test/map_SUITE_data/results/typesig b/lib/dialyzer/test/map_SUITE_data/results/typesig
index 3049402860..fb2f851a7d 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/typesig
+++ b/lib/dialyzer/test/map_SUITE_data/results/typesig
@@ -1,5 +1,5 @@
typesig.erl:5: Function t1/0 has no local return
-typesig.erl:5: The call typesig:test(#{'a':=1}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, ...})
+typesig.erl:5: The call typesig:test(#{'a':=1}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, _=>_})
typesig.erl:6: Function t2/0 has no local return
-typesig.erl:6: The call typesig:test(#{'a':={'b'}}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, ...})
+typesig.erl:6: The call typesig:test(#{'a':={'b'}}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, _=>_})
diff --git a/lib/dialyzer/test/small_SUITE_data/results/maps1 b/lib/dialyzer/test/small_SUITE_data/results/maps1
index a178e96b20..f36f7f4926 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/maps1
+++ b/lib/dialyzer/test/small_SUITE_data/results/maps1
@@ -1,4 +1,4 @@
maps1.erl:43: Function t3/0 has no local return
-maps1.erl:44: The call maps1:foo(#{'greger'=>3, #{'arne'=>'anka'}=>45},1) will never return since it differs in the 1st and 2nd argument from the success typing arguments: (#{'beta':=_, ...},'b')
+maps1.erl:44: The call maps1:foo(#{'greger'=>3, #{'arne'=>'anka'}=>45},1) will never return since it differs in the 1st and 2nd argument from the success typing arguments: (#{'beta':=_, _=>_},'b')
maps1.erl:52: The variable Mod can never match since previous clauses completely covered the type #{}
diff --git a/lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl
new file mode 100644
index 0000000000..4d681b5cc7
--- /dev/null
+++ b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl
@@ -0,0 +1,11 @@
+-module(send).
+
+-export([s/0]).
+
+s() ->
+ self() ! n(), % no warning
+ erlang:send(self(), n()), % no warning
+ ok.
+
+n() ->
+ {1, 1}.
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 702f11593a..fdbbd412a1 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. 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.
@@ -38,17 +38,17 @@
-module(diameter_config).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2]}).
-
-export([start_service/2,
stop_service/1,
add_transport/2,
remove_transport/2,
have_transport/2,
- lookup/1]).
+ lookup/1,
+ subscribe/2]).
-%% child server start
--export([start_link/0]).
+%% server start
+-export([start_link/0,
+ start_link/1]).
%% gen_server callbacks
-export([init/1,
@@ -58,8 +58,8 @@
handle_info/2,
code_change/3]).
-%% diameter_sync requests.
--export([sync/1]).
+%% callbacks
+-export([sync/1]). %% diameter_sync requests
%% debug
-export([state/0,
@@ -69,7 +69,8 @@
-include("diameter_internal.hrl").
%% Server state.
--record(state, {id = diameter_lib:now()}).
+-record(state, {id = diameter_lib:now(),
+ role :: server | transport}).
%% Registered name of the server.
-define(SERVER, ?MODULE).
@@ -77,6 +78,9 @@
%% Table config is written to.
-define(TABLE, ?MODULE).
+%% Key on which a transport-specific child registers itself.
+-define(TRANSPORT_KEY(Ref), {?MODULE, transport, Ref}).
+
%% Workaround for dialyzer's lack of understanding of match specs.
-type match(T)
:: T | '_' | '$1' | '$2' | '$3' | '$4'.
@@ -225,6 +229,13 @@ pred(_) ->
?THROW(pred).
%% --------------------------------------------------------------------------
+%% # subscribe/2
+%% --------------------------------------------------------------------------
+
+subscribe(Ref, T) ->
+ diameter_reg:subscribe(?TRANSPORT_KEY(Ref), T).
+
+%% --------------------------------------------------------------------------
%% # have_transport/2
%%
%% Output: true | false
@@ -264,6 +275,9 @@ start_link() ->
Options = [{spawn_opt, diameter_lib:spawn_opts(server, [])}],
gen_server:start_link(ServerName, Module, Args, Options).
+start_link(T) ->
+ proc_lib:start_link(?MODULE, init, [T], infinity, []).
+
state() ->
call(state).
@@ -274,8 +288,27 @@ uptime() ->
%%% # init/1
%%% ----------------------------------------------------------
+%% ?SERVER start.
init([]) ->
- {ok, #state{}}.
+ {ok, #state{role = server}};
+
+%% Child start as a consequence of add_transport.
+init({SvcName, Type, Opts}) ->
+ Res = try
+ add(SvcName, Type, Opts)
+ catch
+ ?FAILURE(Reason) -> {error, Reason}
+ end,
+ proc_lib:init_ack({ok, self(), Res}),
+ loop(Res).
+
+%% loop/1
+
+loop({ok, _}) ->
+ gen_server:enter_loop(?MODULE, [], #state{role = transport});
+
+loop({error, _}) ->
+ ok. %% die
%%% ----------------------------------------------------------
%%% # handle_call/2
@@ -284,8 +317,8 @@ init([]) ->
handle_call(state, _, State) ->
{reply, State, State};
-handle_call(uptime, _, #state{id = Time} = State) ->
- {reply, diameter_lib:now_diff(Time), State};
+handle_call(uptime, _, #state{id = Time} = S) ->
+ {reply, diameter_lib:now_diff(Time), S};
handle_call(Req, From, State) ->
?UNEXPECTED([Req, From]),
@@ -304,30 +337,34 @@ handle_cast(Msg, State) ->
%%% # handle_info/2
%%% ----------------------------------------------------------
+%% remove_transport is telling published child to die.
+handle_info(stop, #state{role = transport} = S) ->
+ {stop, normal, S};
+
%% A service process has died. This is most likely a consequence of
%% stop_service, in which case the restart will find no config for the
%% service and do nothing. The entry keyed on the monitor ref is only
%% removed as a result of the 'DOWN' notification however.
-handle_info({'DOWN', MRef, process, _, Reason}, State) ->
+handle_info({'DOWN', MRef, process, _, Reason}, #state{role = server} = S) ->
[#monitor{service = SvcName} = T] = select([{#monitor{mref = MRef,
_ = '_'},
[],
['$_']}]),
queue_restart(Reason, SvcName),
delete_object(T),
- {noreply, State};
+ {noreply, S};
-handle_info({monitor, SvcName, Pid}, State) ->
- monitor(Pid, SvcName),
- {noreply, State};
+handle_info({monitor, SvcName, Pid}, #state{role = server} = S) ->
+ insert_monitor(Pid, SvcName),
+ {noreply, S};
-handle_info({restart, SvcName}, State) ->
+handle_info({restart, SvcName}, #state{role = server} = S) ->
restart(SvcName),
- {noreply, State};
+ {noreply, S};
-handle_info(restart, State) ->
+handle_info(restart, #state{role = server} = S) ->
restart(),
- {noreply, State};
+ {noreply, S};
handle_info(Info, State) ->
?UNEXPECTED([Info]),
@@ -404,19 +441,22 @@ sync({start_service, SvcName, Opts}) ->
sync({stop_service, SvcName}) ->
stop(SvcName);
+%% Start a child whose only purpose is to be alive for the lifetime of
+%% the transport configuration and publish itself in diameter_reg.
+%% This is to provide a way for processes to to be notified when the
+%% configuration is removed (diameter_reg:subscribe/2).
sync({add, SvcName, Type, Opts}) ->
- try
- add(SvcName, Type, Opts)
- catch
- ?FAILURE(Reason) -> {error, Reason}
- end;
+ {ok, _Pid, Res} = diameter_config_sup:start_child({SvcName, Type, Opts}),
+ Res;
sync({remove, SvcName, Pred}) ->
- remove(select([{#transport{service = '$1', _ = '_'},
+ Recs = select([{#transport{service = '$1', _ = '_'},
[{'=:=', '$1', {const, SvcName}}],
['$_']}]),
- SvcName,
- Pred).
+ F = fun(#transport{ref = R, type = T, options = O}) ->
+ Pred(R,T,O)
+ end,
+ remove(SvcName, lists:filter(F, Recs)).
%% start/3
@@ -438,8 +478,8 @@ startmon(SvcName, {ok, Pid}) ->
startmon(_, {error, _}) ->
ok.
-monitor(Pid, SvcName) ->
- MRef = erlang:monitor(process, Pid),
+insert_monitor(Pid, SvcName) ->
+ MRef = monitor(process, Pid),
insert(#monitor{mref = MRef, service = SvcName}).
%% queue_restart/2
@@ -503,6 +543,7 @@ add(SvcName, Type, Opts) ->
ok = transport_opts(Opts),
Ref = make_ref(),
+ true = diameter_reg:add_new(?TRANSPORT_KEY(Ref)),
T = {Ref, Type, Opts},
%% The call to the service returns error if the service isn't
%% started yet, which is harmless. The transport will be started
@@ -594,26 +635,30 @@ start_transport(SvcName, T) ->
No
end.
-%% remove/3
+%% remove/2
-remove(L, SvcName, Pred) ->
- rm(SvcName, lists:filter(fun(#transport{ref = R, type = T, options = O}) ->
- Pred(R,T,O)
- end,
- L)).
-
-rm(_, []) ->
+remove(_, []) ->
ok;
-rm(SvcName, L) ->
+
+remove(SvcName, L) ->
Refs = lists:map(fun(#transport{ref = R}) -> R end, L),
case stop_transport(SvcName, Refs) of
ok ->
+ lists:foreach(fun stop_child/1, Refs),
diameter_stats:flush(Refs),
lists:foreach(fun delete_object/1, L);
{error, _} = No ->
No
end.
+stop_child(Ref) ->
+ case diameter_reg:match(?TRANSPORT_KEY(Ref)) of
+ [{_, Pid}] -> %% tell the transport-specific child to die
+ Pid ! stop;
+ [] -> %% already removed/dead
+ ok
+ end.
+
stop_transport(SvcName, Refs) ->
case diameter_service:stop_transport(SvcName, Refs) of
ok ->
diff --git a/lib/diameter/src/base/diameter_config_sup.erl b/lib/diameter/src/base/diameter_config_sup.erl
new file mode 100644
index 0000000000..9524573378
--- /dev/null
+++ b/lib/diameter/src/base/diameter_config_sup.erl
@@ -0,0 +1,58 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2016. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% Supervisor for config processes.
+%%
+
+-module(diameter_config_sup).
+
+-behaviour(supervisor).
+
+%% interface
+-export([start_link/0, %% supervisor start
+ start_child/1]). %% config start
+
+-export([init/1]).
+
+-define(NAME, ?MODULE). %% supervisor name
+
+%% start_link/0
+
+start_link() ->
+ SupName = {local, ?NAME},
+ supervisor:start_link(SupName, ?MODULE, []).
+
+%% start_child/1
+
+start_child(T) ->
+ supervisor:start_child(?NAME, [T]).
+
+%% init/1
+
+init([]) ->
+ Mod = diameter_config,
+ Flags = {simple_one_for_one, 0, 1},
+ ChildSpec = {Mod,
+ {Mod, start_link, []},
+ temporary,
+ 1000,
+ worker,
+ [Mod]},
+ {ok, {Flags, [ChildSpec]}}.
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index 43b0ca24ab..b835e87967 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. 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,18 +20,17 @@
-module(diameter_lib).
-compile({no_auto_import, [now/0]}).
--compile({nowarn_deprecated_function, [{erlang, now, 0}]}).
-export([info_report/2,
error_report/2,
warning_report/2,
now/0,
+ timestamp/0,
timestamp/1,
now_diff/1,
micro_diff/1,
micro_diff/2,
time/1,
- seed/0,
eval/1,
eval_name/1,
get_stacktrace/0,
@@ -110,6 +109,16 @@ now() ->
erlang:monotonic_time().
%% ---------------------------------------------------------------------------
+%% # timestamp/0
+%% ---------------------------------------------------------------------------
+
+-spec timestamp()
+ -> erlang:timestamp().
+
+timestamp() ->
+ timestamp(now()).
+
+%% ---------------------------------------------------------------------------
%% # timestamp/1
%% ---------------------------------------------------------------------------
@@ -184,24 +193,6 @@ time(Micro) -> %% elapsed time
{H, M, S, Micro rem 1000000}.
%% ---------------------------------------------------------------------------
-%% # seed/0
-%% ---------------------------------------------------------------------------
-
--spec seed()
- -> {erlang:timestamp(), {integer(), integer(), integer()}}.
-
-%% Return an argument for random:seed/1.
-
-seed() ->
- T = now(),
- {timestamp(T), seed(T)}.
-
-%% seed/1
-
-seed(T) -> %% monotonic time
- {erlang:phash2(node()), T, erlang:unique_integer()}.
-
-%% ---------------------------------------------------------------------------
%% # eval/1
%%
%% Evaluate a function in various forms.
diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl
index 7f198080ba..9027130063 100644
--- a/lib/diameter/src/base/diameter_reg.erl
+++ b/lib/diameter/src/base/diameter_reg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. 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.
@@ -25,14 +25,12 @@
-module(diameter_reg).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2]}).
-
-export([add/1,
add_new/1,
- del/1,
- repl/2,
+ remove/1,
match/1,
- wait/1]).
+ wait/1,
+ subscribe/2]).
-export([start_link/0]).
@@ -46,29 +44,32 @@
%% test
-export([pids/0,
- terms/0]).
+ terms/0,
+ subs/0,
+ waits/0]).
%% debug
-export([state/0,
uptime/0]).
--include("diameter_internal.hrl").
-
-define(SERVER, ?MODULE).
-define(TABLE, ?MODULE).
-%% Table entry used to keep from starting more than one monitor on the
-%% same process. This isn't a problem but there's no point in starting
-%% multiple monitors if we can avoid it. Note that we can't have a 2-tuple
-%% keyed on Pid since a registered term can be anything. Want the entry
-%% keyed on Pid so that lookup is fast.
--define(MONITOR(Pid, MRef), {Pid, monitor, MRef}).
-
-%% Table entry containing the Term -> Pid mapping.
--define(MAPPING(Term, Pid), {Term, Pid}).
+-type key() :: term().
+-type from() :: {pid(), term()}.
+-type pattern() :: term().
-record(state, {id = diameter_lib:now(),
- q = []}). %% [{From, Pat}]
+ receivers = dict:new()
+ :: dict:dict(pattern(), [[pid() | term()]%% subscribe
+ | from()]), %% wait
+ monitors = sets:new() :: sets:set(pid())}).
+
+%% The ?TABLE bag contains the Key -> Pid mapping, as {Key, Pid}
+%% tuples. Each pid is stored in the monitors set to ensure only one
+%% monitor for each pid: more are harmless, but unnecessary. A pattern
+%% is added to receivers a result of calls to wait/1 or subscribe/2:
+%% changes to ?TABLE causes processes to be notified as required.
%% ===========================================================================
%% # add(T)
@@ -77,18 +78,18 @@
%% this or other assocations can be retrieved using match/1.
%%
%% An association is removed when the calling process dies or as a
-%% result of calling del/1. Adding the same term more than once is
-%% equivalent to adding it exactly once.
+%% result of calling remove/1. Adding the same term more than once is
+%% equivalent to adding it once.
%%
%% Note that since match/1 takes a pattern as argument, specifying a
%% term that contains match variables is probably not a good idea
%% ===========================================================================
--spec add(any())
+-spec add(key())
-> true.
add(T) ->
- call({add, fun ets:insert/2, T, self()}).
+ call({add, false, T}).
%% ===========================================================================
%% # add_new(T)
@@ -97,36 +98,23 @@ add(T) ->
%% association, false being returned if an association already exists.
%% ===========================================================================
--spec add_new(any())
+-spec add_new(key())
-> boolean().
add_new(T) ->
- call({add, fun insert_new/2, T, self()}).
+ call({add, true, T}).
%% ===========================================================================
-%% # repl(T, NewT)
-%%
-%% Like add/1 but only replace an existing association on T, false
-%% being returned if it doesn't exist.
-%% ===========================================================================
-
--spec repl(any(), any())
- -> boolean().
-
-repl(T, U) ->
- call({repl, T, U, self()}).
-
-%% ===========================================================================
-%% # del(Term)
+%% # remove(Term)
%%
%% Remove any existing association of Term with self().
%% ===========================================================================
--spec del(any())
+-spec remove(key())
-> true.
-del(T) ->
- call({del, T, self()}).
+remove(T) ->
+ call({remove, T}).
%% ===========================================================================
%% # match(Pat)
@@ -139,12 +127,17 @@ del(T) ->
%% associations removed.)
%% ===========================================================================
--spec match(any())
- -> [{term(), pid()}].
+-spec match(pattern())
+ -> [{key(), pid()}].
match(Pat) ->
- ets:match_object(?TABLE, ?MAPPING(Pat, '_')).
+ match(Pat, '_').
+
+%% match/2
+match(Pat, Pid) ->
+ ets:match_object(?TABLE, {Pat, Pid}).
+
%% ===========================================================================
%% # wait(Pat)
%%
@@ -152,10 +145,29 @@ match(Pat) ->
%% It's up to the caller to ensure that the wait won't be forever.
%% ===========================================================================
+-spec wait(pattern())
+ -> [{key(), pid()}].
+
wait(Pat) ->
+ _ = match(Pat), %% ensure match can succeed
call({wait, Pat}).
%% ===========================================================================
+%% # subscribe(Pat, T)
+%%
+%% Like match/1, but additionally receive messages of the form
+%% {T, add|remove, {term(), pid()} when associations are added
+%% or removed.
+%% ===========================================================================
+
+-spec subscribe(Pat :: any(), T :: term())
+ -> [{term(), pid()}].
+
+subscribe(Pat, T) ->
+ _ = match(Pat), %% ensure match can succeed
+ call({subscribe, Pat, T}).
+
+%% ===========================================================================
start_link() ->
ServerName = {local, ?SERVER},
@@ -169,19 +181,15 @@ uptime() ->
call(uptime).
%% pids/0
-%%
-%% Return: list of {Pid, [Term, ...]}
+
+-spec pids()
+ -> [{pid(), [key()]}].
pids() ->
to_list(fun swap/1).
to_list(Fun) ->
- ets:foldl(fun(T,A) -> acc(Fun, T, A) end, orddict:new(), ?TABLE).
-
-acc(Fun, ?MAPPING(Term, Pid), Dict) ->
- append(Fun({Term, Pid}), Dict);
-acc(_, _, Dict) ->
- Dict.
+ ets:foldl(fun(T,D) -> append(Fun(T), D) end, orddict:new(), ?TABLE).
append({K,V}, Dict) ->
orddict:append(K, V, Dict).
@@ -189,14 +197,47 @@ append({K,V}, Dict) ->
id(T) -> T.
%% terms/0
-%%
-%% Return: list of {Term, [Pid, ...]}
+
+-spec terms()
+ -> [{key(), [pid()]}].
terms() ->
to_list(fun id/1).
swap({X,Y}) -> {Y,X}.
+%% subs/0
+
+-spec subs()
+ -> [{pattern(), [{pid(), term()}]}].
+
+subs() ->
+ #state{receivers = RD} = state(),
+ dict:fold(fun sub/3, orddict:new(), RD).
+
+sub(Pat, Ps, Dict) ->
+ lists:foldl(fun([P|T], D) -> orddict:append(Pat, {P,T}, D);
+ (_, D) -> D
+ end,
+ Dict,
+ Ps).
+
+%% waits/0
+
+-spec waits()
+ -> [{pattern(), [{from(), term()}]}].
+
+waits() ->
+ #state{receivers = RD} = state(),
+ dict:fold(fun wait/3, orddict:new(), RD).
+
+wait(Pat, Ps, Dict) ->
+ lists:foldl(fun({_,_} = F, D) -> orddict:append(Pat, F, D);
+ (_, D) -> D
+ end,
+ Dict,
+ Ps).
+
%% ----------------------------------------------------------
%% # init/1
%% ----------------------------------------------------------
@@ -209,57 +250,58 @@ init(_) ->
%% # handle_call/3
%% ----------------------------------------------------------
-handle_call({add, Fun, Key, Pid}, _, S) ->
- B = Fun(?TABLE, {Key, Pid}),
- monitor(B andalso no_monitor(Pid), Pid),
- {reply, B, pending(B, S)};
-
-handle_call({del, Key, Pid}, _, S) ->
- {reply, ets:delete_object(?TABLE, ?MAPPING(Key, Pid)), S};
-
-handle_call({repl, T, U, Pid}, _, S) ->
- MatchSpec = [{?MAPPING('$1', Pid),
- [{'=:=', '$1', {const, T}}],
- ['$_']}],
- {reply, repl(ets:select(?TABLE, MatchSpec), U, Pid), S};
-
-handle_call({wait, Pat}, From, #state{q = Q} = S) ->
- case find(Pat) of
- {ok, L} ->
- {reply, L, S};
- false ->
- {noreply, S#state{q = [{From, Pat} | Q]}}
+handle_call({add, Uniq, Key}, {Pid, _}, S0) ->
+ Rec = {Key, Pid},
+ S1 = flush(Uniq, Rec, S0),
+ {Res, New} = insert(Uniq, Rec),
+ {Recvs, S} = add(New, Rec, S1),
+ notify(Recvs, Rec),
+ {reply, Res, S};
+
+handle_call({remove, Key}, {Pid, _}, S) ->
+ Rec = {Key, Pid},
+ Recvs = delete([Rec], S),
+ ets:delete_object(?TABLE, Rec),
+ notify(Recvs, remove),
+ {reply, true, S};
+
+handle_call({wait, Pat}, {Pid, _} = From, #state{receivers = RD} = S) ->
+ NS = add_monitor(Pid, S),
+ case match(Pat) of
+ [_|_] = L ->
+ {reply, L, NS};
+ [] ->
+ {noreply, NS#state{receivers = dict:append(Pat, From, RD)}}
end;
+handle_call({subscribe, Pat, T}, {Pid, _}, #state{receivers = RD} = S) ->
+ NS = add_monitor(Pid, S),
+ {reply, match(Pat), NS#state{receivers = dict:append(Pat, [Pid | T], RD)}};
+
handle_call(state, _, S) ->
{reply, S, S};
handle_call(uptime, _, #state{id = Time} = S) ->
{reply, diameter_lib:now_diff(Time), S};
-handle_call(Req, From, S) ->
- ?UNEXPECTED([Req, From]),
+handle_call(_Req, _From, S) ->
{reply, nok, S}.
%% ----------------------------------------------------------
%% # handle_cast/2
%% ----------------------------------------------------------
-handle_cast(Msg, S)->
- ?UNEXPECTED([Msg]),
+handle_cast(_Msg, S)->
{noreply, S}.
%% ----------------------------------------------------------
%% # handle_info/2
%% ----------------------------------------------------------
-handle_info({'DOWN', MRef, process, Pid, _}, S) ->
- ets:delete_object(?TABLE, ?MONITOR(Pid, MRef)),
- ets:match_delete(?TABLE, ?MAPPING('_', Pid)),
- {noreply, S};
+handle_info({'DOWN', _MRef, process, Pid, _}, S) ->
+ {noreply, down(Pid, S)};
-handle_info(Info, S) ->
- ?UNEXPECTED([Info]),
+handle_info(_Info, S) ->
{noreply, S}.
%% ----------------------------------------------------------
@@ -278,71 +320,166 @@ code_change(_OldVsn, State, _Extra) ->
%% ===========================================================================
-monitor(true, Pid) ->
- ets:insert(?TABLE, ?MONITOR(Pid, erlang:monitor(process, Pid)));
-monitor(false, _) ->
- ok.
+%% insert/2
+
+insert(false, Rec) ->
+ Spec = [{'$1', [{'==', '$1', {const, Rec}}], ['$_']}],
+ X = '$end_of_table' /= ets:select(?TABLE, Spec, 1), %% entry exists?
+ X orelse ets:insert(?TABLE, Rec),
+ {true, not X};
-%% Do we need a monitor for the specified Pid?
-no_monitor(Pid) ->
- [] == ets:match_object(?TABLE, ?MONITOR(Pid, '_')).
+insert(true, Rec) ->
+ B = ets:insert_new(?TABLE, Rec), %% entry inserted?
+ {B, B}.
-%% insert_new/2
+%% add/3
-insert_new(?TABLE, {Key, _} = T) ->
- flush(ets:lookup(?TABLE, Key)),
- ets:insert_new(?TABLE, T).
+%% Only add a single monitor for any given process, since there's no
+%% use to more.
+add(true, {_Key, Pid} = Rec, S) ->
+ NS = add_monitor(Pid, S),
+ {Recvs, RD} = add(Rec, NS),
+ {Recvs, S#state{receivers = RD}};
+
+add(false = No, _, S) ->
+ {No, S}.
+
+%% add/2
+
+%% Notify processes whose patterns match the inserted key.
+add({_Key, Pid} = Rec, #state{receivers = RD}) ->
+ dict:fold(fun(Pt, Ps, A) ->
+ add(lists:member(Rec, match(Pt, Pid)), Pt, Ps, Rec, A)
+ end,
+ {sets:new(), RD},
+ RD).
+
+%% add/5
+
+add(true, Pat, Recvs, {_,_} = Rec, {Set, Dict}) ->
+ {lists:foldl(fun sets:add_element/2, Set, Recvs),
+ remove(fun erlang:is_list/1, Pat, Recvs, Dict)};
+
+add(false, _, _, _, Acc) ->
+ Acc.
+
+%% add_monitor/2
+
+add_monitor(Pid, #state{monitors = MS} = S) ->
+ add_monitor(sets:is_element(Pid, MS), Pid, S).
+
+%% add_monitor/3
+
+add_monitor(false, Pid, #state{monitors = MS} = S) ->
+ monitor(process, Pid),
+ S#state{monitors = sets:add_element(Pid, MS)};
+
+add_monitor(true, _, S) ->
+ S.
+
+%% delete/2
+
+delete(Recs, #state{receivers = RD}) ->
+ lists:foldl(fun(R,S) -> delete(R, RD, S) end, sets:new(), Recs).
+
+%% delete/3
+
+delete({_Key, Pid} = Rec, RD, Set) ->
+ dict:fold(fun(Pt, Ps, S) ->
+ delete(lists:member(Rec, match(Pt, Pid)), Rec, Ps, S)
+ end,
+ Set,
+ RD).
+
+%% delete/4
+
+%% Entry matches a pattern ...
+delete(true, Rec, Recvs, Set) ->
+ lists:foldl(fun(R,S) -> sets:add_element({R, Rec}, S) end,
+ Set,
+ Recvs);
+
+%% ... or not.
+delete(false, _, _, Set) ->
+ Set.
+
+%% notify/2
+
+notify(false = No, _) ->
+ No;
+
+notify(Recvs, remove = Op) ->
+ sets:fold(fun({P,R}, N) -> send(P, R, Op), N+1 end, 0, Recvs);
+
+notify(Recvs, {_,_} = Rec) ->
+ sets:fold(fun(P,N) -> send(P, Rec, add), N+1 end, 0, Recvs).
+
+%% send/3
+
+%% No processes waiting on remove, by construction: they've either
+%% received notification at add or aren't waiting.
+send([Pid | T], Rec, Op) ->
+ Pid ! {T, Op, Rec};
+
+send({_,_} = From, Rec, add) ->
+ gen_server:reply(From, [Rec]).
+
+%% down/2
+
+down(Pid, #state{monitors = MS} = S) ->
+ NS = flush(Pid, S),
+ Recvs = delete(match('_', Pid), NS),
+ ets:match_delete(?TABLE, {'_', Pid}),
+ notify(Recvs, remove),
+ NS#state{monitors = sets:del_element(Pid, MS)}.
+
+%% flush/3
%% Remove any processes that are dead but for which we may not have
-%% received 'DOWN' yet. This is to ensure that add_new can be used
-%% to register a unique name each time a process restarts.
-flush(List) ->
- lists:foreach(fun({_,P} = T) ->
- del(erlang:is_process_alive(P), T)
- end,
- List).
-
-del(Alive, T) ->
- Alive orelse ets:delete_object(?TABLE, T).
-
-%% repl/3
-
-repl([?MAPPING(_, Pid) = M], Key, Pid) ->
- ets:delete_object(?TABLE, M),
- true = ets:insert(?TABLE, ?MAPPING(Key, Pid));
-repl([], _, _) ->
- false.
-
-%% pending/1
-
-pending(true, #state{q = [_|_] = Q} = S) ->
- S#state{q = q(lists:reverse(Q), [])}; %% retain reply order
-pending(_, S) ->
+%% received 'DOWN' yet, to ensure that add_new can be used to register
+%% a unique name each time a registering process restarts.
+flush(true, {Key, Pid}, S) ->
+ Spec = [{{'$1', '$2'},
+ [{'andalso', {'==', '$1', {const, Key}},
+ {'/=', '$2', Pid}}],
+ ['$2']}],
+ lists:foldl(fun down/2, S, [P || P <- ets:select(?TABLE, Spec),
+ not is_process_alive(P)]);
+
+flush(false, _, S) ->
S.
-q([], Q) ->
- Q;
-q([{From, Pat} = T | Rest], Q) ->
- case find(Pat) of
- {ok, L} ->
- gen_server:reply(From, L),
- q(Rest, Q);
- false ->
- q(Rest, [T|Q])
- end.
-
-%% find/1
-
-find(Pat) ->
- try match(Pat) of
- [] ->
- false;
- L ->
- {ok, L}
- catch
- _:_ ->
- {ok, []}
- end.
+%% flush/2
+
+%% Process has died and should no longer receive messages/replies.
+flush(Pid, #state{receivers = RD} = S)
+ when is_pid(Pid) ->
+ S#state{receivers = dict:fold(fun(Pt,Ps,D) -> flush(Pid, Pt, Ps, D) end,
+ RD,
+ RD)}.
+
+%% flush/4
+
+flush(Pid, Pat, Recvs, Dict) ->
+ remove(fun(T) -> Pid /= head(T) end, Pat, Recvs, Dict).
+
+%% head/1
+
+head([P|_]) ->
+ P;
+
+head({P,_}) ->
+ P.
+
+%% remove/4
+
+remove(Pred, Key, Values, Dict) ->
+ case lists:filter(Pred, Values) of
+ [] ->
+ dict:erase(Key, Dict);
+ Rest ->
+ dict:store(Key, Rest, Dict)
+ end.
%% call/1
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index cfb5cb5b82..ccf68f4d93 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -136,7 +136,7 @@
state = ?WD_INITIAL :: match(wd_state()),
started = diameter_lib:now(),%% at process start
peer = false :: match(boolean() | pid())}).
- %% true at accepted, pid() at okay/reopen
+ %% true at accepted/remove, pid() at okay/reopen
%% Record representing a Peer State Machine processes implemented by
%% diameter_peer_fsm.
@@ -250,7 +250,7 @@ subscribe(SvcName) ->
diameter_reg:add({?MODULE, subscriber, SvcName}).
unsubscribe(SvcName) ->
- diameter_reg:del({?MODULE, subscriber, SvcName}).
+ diameter_reg:remove({?MODULE, subscriber, SvcName}).
subscriptions(Pat) ->
pmap(diameter_reg:match({?MODULE, subscriber, Pat})).
@@ -676,25 +676,34 @@ mod_state(Alias, ModS) ->
%% remove_transport
shutdown(Refs, #state{watchdogT = WatchdogT})
when is_list(Refs) ->
- ets:foldl(fun(P,ok) -> st(P, Refs), ok end, ok, WatchdogT);
+ ets:insert(WatchdogT, ets:foldl(fun(R,A) -> st(R, Refs, A) end,
+ [],
+ WatchdogT));
%% application/service shutdown
shutdown(Reason, #state{watchdogT = WatchdogT})
when Reason == application;
Reason == service ->
- diameter_lib:wait(ets:foldl(fun(P,A) -> st(P, Reason, A) end,
+ diameter_lib:wait(ets:foldl(fun(P,A) -> ss(P, Reason, A) end,
[],
WatchdogT)).
-%% st/2
+%% st/3
-st(#watchdog{ref = Ref, pid = Pid}, Refs) ->
- lists:member(Ref, Refs)
- andalso (Pid ! {shutdown, self(), transport}). %% 'DOWN' cleans up
+%% Mark replacement as started so that a subsequent accept doesn't
+%% result in a new process that isn't terminated.
+st(#watchdog{ref = Ref, pid = Pid, peer = P} = Rec, Refs, Acc) ->
+ case lists:member(Ref, Refs) of
+ true ->
+ Pid ! {shutdown, self(), transport}, %% 'DOWN' cleans up
+ [Rec#watchdog{peer = true} || P == false] ++ Acc;
+ false ->
+ Acc
+ end.
-%% st/3
+%% ss/3
-st(#watchdog{pid = Pid}, Reason, Acc) ->
+ss(#watchdog{pid = Pid}, Reason, Acc) ->
MRef = monitor(process, Pid),
Pid ! {shutdown, self(), Reason},
[MRef | Acc].
@@ -974,11 +983,22 @@ ms(_, Svc) ->
%% ---------------------------------------------------------------------------
accepted(Pid, _TPid, #state{watchdogT = WatchdogT} = S) ->
- #watchdog{ref = Ref, type = accept = T, peer = false, options = Opts}
+ #watchdog{type = accept = T, peer = P}
= Wd
= fetch(WatchdogT, Pid),
- ets:insert(WatchdogT, Wd#watchdog{peer = true}),%% mark replacement started
- start(Ref, T, Opts, S). %% start new watchdog
+ if not P ->
+ #watchdog{ref = Ref, options = Opts} = Wd,
+ %% Mark replacement started, and start new watchdog.
+ ets:insert(WatchdogT, Wd#watchdog{peer = true}),
+ start(Ref, T, Opts, S);
+ P ->
+ %% Transport removal in progress: true has been set in
+ %% shutdown/2, and the transport will die as a
+ %% consequence.
+ ok
+ end.
+
+%% fetch/2
fetch(Tid, Key) ->
[T] = ets:lookup(Tid, Key),
@@ -1317,8 +1337,7 @@ start_tc(Tc, T, _) ->
tc_timeout({Ref, _Type, _Opts} = T, #state{service_name = SvcName} = S) ->
tc(diameter_config:have_transport(SvcName, Ref), T, S).
-tc(true, {Ref, Type, Opts}, #state{service_name = SvcName}
- = S) ->
+tc(true, {Ref, Type, Opts}, #state{service_name = SvcName} = S) ->
send_event(SvcName, {reconnect, Ref, Opts}),
start(Ref, Type, Opts, S);
tc(false = No, _, _) -> %% removed
diff --git a/lib/diameter/src/base/diameter_session.erl b/lib/diameter/src/base/diameter_session.erl
index 53973649fd..d854bc36a5 100644
--- a/lib/diameter/src/base/diameter_session.erl
+++ b/lib/diameter/src/base/diameter_session.erl
@@ -158,10 +158,9 @@ session_id(Host) ->
%% ---------------------------------------------------------------------------
init() ->
- {Now, Seed} = diameter_lib:seed(),
- random:seed(Seed),
+ Now = diameter_lib:timestamp(),
Time = time32(Now),
- Seq = (?INT32 band (Time bsl 20)) bor (random:uniform(1 bsl 20) - 1),
+ Seq = (?INT32 band (Time bsl 20)) bor (rand:uniform(1 bsl 20) - 1),
ets:insert(diameter_sequence, [{origin_state_id, Time},
{session_base, Time bsl 32},
{sequence, Seq}]),
diff --git a/lib/diameter/src/base/diameter_sup.erl b/lib/diameter/src/base/diameter_sup.erl
index e89ede9843..482289cb9a 100644
--- a/lib/diameter/src/base/diameter_sup.erl
+++ b/lib/diameter/src/base/diameter_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. 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.
@@ -34,6 +34,7 @@
-export([init/1]).
-define(CHILDREN, [diameter_misc_sup,
+ diameter_config_sup,
diameter_watchdog_sup,
diameter_peer_fsm_sup,
diameter_transport_sup,
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index 3fd87b223e..2ba60a65fb 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -125,8 +125,6 @@ i({Ack, T, Pid, {RecvData,
= Svc}}) ->
monitor(process, Pid),
wait(Ack, Pid),
- {_, Seed} = diameter_lib:seed(),
- random:seed(Seed),
putr(restart, {T, Opts, Svc, SvcOpts}), %% save seeing it in trace
putr(dwr, dwr(Caps)), %%
{_,_} = Mask = proplists:get_value(sequence, SvcOpts),
@@ -565,7 +563,7 @@ tw(TwInit, Ms) ->
tw(T)
when is_integer(T), T >= 6000 ->
- T - 2000 + (random:uniform(4001) - 1); %% RFC3539 jitter of +/- 2 sec.
+ T - 2000 + (rand:uniform(4001) - 1); %% RFC3539 jitter of +/- 2 sec.
tw({M,F,A}) ->
apply(M,F,A).
diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk
index 3b223ea391..4e4ce60ddf 100644
--- a/lib/diameter/src/modules.mk
+++ b/lib/diameter/src/modules.mk
@@ -1,7 +1,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2015. All Rights Reserved.
+# Copyright Ericsson AB 2010-2016. 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.
@@ -37,6 +37,7 @@ RT_MODULES = \
base/diameter_callback \
base/diameter_capx \
base/diameter_config \
+ base/diameter_config_sup \
base/diameter_codec \
base/diameter_dict \
base/diameter_lib \
diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl
index 8a80ce630a..4a005b853d 100644
--- a/lib/diameter/src/transport/diameter_sctp.erl
+++ b/lib/diameter/src/transport/diameter_sctp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. 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.
@@ -61,10 +61,6 @@
%% Remote addresses to accept connections from.
-define(DEFAULT_ACCEPT, []). %% any
-%% How long a listener with no associations lives before offing
-%% itself.
--define(LISTENER_TIMEOUT, 30000).
-
%% How long to wait for a transport process to attach after
%% association establishment.
-define(ACCEPT_TIMEOUT, 5000).
@@ -104,7 +100,6 @@
socket :: gen_sctp:sctp_socket(),
count = 0 :: uint(), %% attached transport processes
pending = {0, queue:new()},
- tref :: reference() | undefined,
accept :: [match()]}).
%% Field pending implements two queues: the first of transport-to-be
%% processes to which an association has been assigned but for which
@@ -216,14 +211,15 @@ init(T) ->
%% A process owning a listening socket.
i({listen, Ref, {Opts, Addrs}}) ->
+ [_] = diameter_config:subscribe(Ref, transport), %% assert existence
{[Matches], Rest} = proplists:split(Opts, [accept]),
{LAs, Sock} = AS = open(Addrs, Rest, ?DEFAULT_PORT),
ok = gen_sctp:listen(Sock, true),
true = diameter_reg:add_new({?MODULE, listener, {Ref, AS}}),
proc_lib:init_ack({ok, self(), LAs}),
- start_timer(#listener{ref = Ref,
- socket = Sock,
- accept = [[M] || {accept, M} <- Matches]});
+ #listener{ref = Ref,
+ socket = Sock,
+ accept = [[M] || {accept, M} <- Matches]};
%% A connecting transport.
i({connect, Pid, Opts, Addrs, Ref}) ->
@@ -431,13 +427,6 @@ putr(Key, Val) ->
getr(Key) ->
get({?MODULE, Key}).
-%% start_timer/1
-
-start_timer(#listener{count = 0} = S) ->
- S#listener{tref = erlang:start_timer(?LISTENER_TIMEOUT, self(), close)};
-start_timer(S) ->
- S.
-
%% l/2
%%
%% Transition listener state.
@@ -455,12 +444,10 @@ l({sctp, Sock, _RA, _RP, Data} = T, #listener{socket = Sock,
l({'DOWN', _MRef, process, TPid, _}, #listener{pending = {_,Q}} = S) ->
down(queue:member(TPid, Q), TPid, S);
-%% Timeout after the last accepting process has died.
-l({timeout, TRef, close = T}, #listener{tref = TRef,
- count = 0}) ->
- x(T);
-l({timeout, _, close}, #listener{} = S) ->
- S.
+%% Transport has been removed.
+l({transport, remove, _} = T, #listener{socket = Sock}) ->
+ gen_sctp:close(Sock),
+ x(T).
%% down/3
%%
@@ -472,15 +459,15 @@ down(true, TPid, #listener{pending = {N,Q},
= S) ->
NQ = queue:filter(fun(P) -> P /= TPid end, Q),
if N < 0 -> %% awaiting an association ...
- start_timer(S#listener{count = K-1,
- pending = {N+1, NQ}});
+ S#listener{count = K-1,
+ pending = {N+1, NQ}};
true -> %% ... or one has been assigned
S#listener{pending = {N-1, NQ}}
end;
%% ... or one that's already attached.
down(false, _TPid, #listener{count = K} = S) ->
- start_timer(S#listener{count = K-1}).
+ S#listener{count = K-1}.
%% t/2
%%
diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl
index 6a5e5fe89d..546c2cfa5e 100644
--- a/lib/diameter/src/transport/diameter_tcp.erl
+++ b/lib/diameter/src/transport/diameter_tcp.erl
@@ -57,7 +57,6 @@
-define(ERROR(T), erlang:error({T, ?MODULE, ?LINE})).
-define(DEFAULT_PORT, 3868). %% RFC 3588, ch 2.1
--define(LISTENER_TIMEOUT, 30000).
-define(DEFAULT_FRAGMENT_TIMEOUT, 1000).
-define(IS_UINT32(N), (is_integer(N) andalso 0 =< N andalso 0 == N bsr 32)).
@@ -73,8 +72,10 @@
%% Listener process state.
-record(listener, {socket :: inet:socket(),
- count = 1 :: non_neg_integer(),
- tref :: reference() | undefined}).
+ count = 1 :: non_neg_integer()}). %% accepting processes
+%% The count of accepting processes was previously used to terminate
+%% the listening process, but diameter_reg:subscribe/2 is now used for
+%% this. Leave the the count for trace purposes.
%% Monitor process state.
-record(monitor,
@@ -240,6 +241,7 @@ i(#monitor{parent = Pid, transport = TPid} = S) ->
%% gen_tcp seems to so. Links should be left to supervisors.
i({listen, LRef, APid, {Mod, Opts, Addrs}}) ->
+ [_] = diameter_config:subscribe(LRef, transport), %% assert existence
{[LA, LP], Rest} = proplists:split(Opts, [ip, port]),
LAddrOpt = get_addr(LA, Addrs),
LPort = get_port(LP),
@@ -248,7 +250,7 @@ i({listen, LRef, APid, {Mod, Opts, Addrs}}) ->
true = diameter_reg:add_new({?MODULE, listener, {LRef, {LAddr, LSock}}}),
proc_lib:init_ack({ok, self(), {LAddr, LSock}}),
monitor(process, APid),
- start_timer(#listener{socket = LSock}).
+ #listener{socket = LSock}.
laddr([], Mod, Sock) ->
{ok, {Addr, _Port}} = sockname(Mod, Sock),
@@ -484,13 +486,6 @@ putr(Key, Val) ->
getr(Key) ->
get({?MODULE, Key}).
-%% start_timer/1
-
-start_timer(#listener{count = 0} = S) ->
- S#listener{tref = erlang:start_timer(?LISTENER_TIMEOUT, self(), close)};
-start_timer(S) ->
- S.
-
%% m/2
%%
%% Transition monitor state.
@@ -512,21 +507,19 @@ m({'DOWN', _, process, Pid, _}, #monitor{parent = Pid,
%%
%% Transition listener state.
-%% Another accept transport is attaching.
+%% An accepting transport is attaching.
l({accept, TPid}, #listener{count = N} = S) ->
monitor(process, TPid),
S#listener{count = N+1};
%% Accepting process has died.
l({'DOWN', _, process, _, _}, #listener{count = N} = S) ->
- start_timer(S#listener{count = N-1});
+ S#listener{count = N-1};
-%% Timeout after the last accepting process has died.
-l({timeout, TRef, close = T}, #listener{tref = TRef,
- count = 0}) ->
- x(T);
-l({timeout, _, close}, #listener{} = S) ->
- S.
+%% Transport has been removed.
+l({transport, remove, _} = T, #listener{socket = Sock}) ->
+ gen_tcp:close(Sock),
+ x(T).
%% t/2
%%
diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl
index 78308856ac..37c41a1761 100644
--- a/lib/diameter/test/diameter_codec_test.erl
+++ b/lib/diameter/test/diameter_codec_test.erl
@@ -230,13 +230,12 @@ v(Max, Ord, E)
when Ord =< Max ->
diameter_enum:to_list(E);
v(Max, Ord, E) ->
- random:seed(diameter_util:seed()),
v(Max, Ord, E, []).
v(0, _, _, Acc) ->
Acc;
v(N, Ord, E, Acc) ->
- v(N-1, Ord, E, [E(random:uniform(Ord)) | Acc]).
+ v(N-1, Ord, E, [E(rand:uniform(Ord)) | Acc]).
%% arity/3
@@ -518,15 +517,7 @@ random(M) ->
random(0,M).
random(Mn,Mx) ->
- seed(get({?MODULE, seed})),
- Mn + random:uniform(Mx - Mn + 1) - 1.
-
-seed(undefined) ->
- put({?MODULE, seed}, true),
- random:seed(diameter_util:seed());
-
-seed(true) ->
- ok.
+ Mn + rand:uniform(Mx - Mn + 1) - 1.
%% run/1
%%
diff --git a/lib/diameter/test/diameter_gen_sctp_SUITE.erl b/lib/diameter/test/diameter_gen_sctp_SUITE.erl
index cbd7fc8ec5..79db39ca45 100644
--- a/lib/diameter/test/diameter_gen_sctp_SUITE.erl
+++ b/lib/diameter/test/diameter_gen_sctp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. 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.
@@ -365,8 +365,8 @@ open(Opts) ->
assoc(Sock) ->
receive
- ?SCTP(Sock, {[], #sctp_assoc_change{state = S,
- assoc_id = Id}}) ->
+ ?SCTP(Sock, {_, #sctp_assoc_change{state = S,
+ assoc_id = Id}}) ->
comm_up = S, %% assert
Id
end.
diff --git a/lib/diameter/test/diameter_reg_SUITE.erl b/lib/diameter/test/diameter_reg_SUITE.erl
index 3d9ad8bfa8..e2a1ca00c3 100644
--- a/lib/diameter/test/diameter_reg_SUITE.erl
+++ b/lib/diameter/test/diameter_reg_SUITE.erl
@@ -33,8 +33,7 @@
%% testcases
-export([add/1,
add_new/1,
- del/1,
- repl/1,
+ remove/1,
terms/1,
pids/1]).
@@ -56,8 +55,7 @@ groups() ->
tc() ->
[add,
add_new,
- del,
- repl,
+ remove,
terms,
pids].
@@ -82,20 +80,11 @@ add_new(_) ->
true = ?reg:add_new(Ref),
false = ?reg:add_new(Ref).
-del(_) ->
+remove(_) ->
Ref = make_ref(),
true = ?reg:add_new(Ref),
true = ?reg:add_new({Ref}),
- true = ?reg:del({Ref}),
- [{Ref, Pid}] = ?reg:match(Ref),
- Pid = self().
-
-repl(_) ->
- Ref = make_ref(),
- true = ?reg:add_new({Ref}),
- true = ?reg:repl({Ref}, Ref),
- false = ?reg:add_new(Ref),
- false = ?reg:repl({Ref}, Ref),
+ true = ?reg:remove({Ref}),
[{Ref, Pid}] = ?reg:match(Ref),
Pid = self().
diff --git a/lib/diameter/test/diameter_relay_SUITE.erl b/lib/diameter/test/diameter_relay_SUITE.erl
index f766f54a80..b5e520e642 100644
--- a/lib/diameter/test/diameter_relay_SUITE.erl
+++ b/lib/diameter/test/diameter_relay_SUITE.erl
@@ -171,8 +171,9 @@ connect(Config) ->
Conns)).
disconnect(Config) ->
- lists:foreach(fun({{CN,CR},{SN,SR}}) -> ?util:disconnect(CN,CR,SN,SR) end,
- ?util:read_priv(Config, "cfg")).
+ [] = [{T,C} || C <- ?util:read_priv(Config, "cfg"),
+ T <- [break(C)],
+ T /= ok].
stop_services(_Config) ->
[] = [{H,T} || H <- ?SERVICES,
@@ -184,6 +185,13 @@ stop(_Config) ->
%% ----------------------------------------
+break({{CN,CR},{SN,SR}}) ->
+ try
+ ?util:disconnect(CN,CR,SN,SR)
+ after
+ diameter:remove_transport(SN, SR)
+ end.
+
server(Name, Dict) ->
ok = diameter:start_service(Name, ?SERVICE(Name, Dict)),
{Name, ?util:listen(Name, tcp)}.
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index 976abf9138..6f3a4801ee 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -428,7 +428,11 @@ remove_transports(Config) ->
server_service = SN}
= group(Config),
[LRef | Cs] = ?util:read_priv(Config, "transport"),
- [?util:disconnect(CN, C, SN, LRef) || C <- Cs].
+ try
+ [] = [T || C <- Cs, T <- [?util:disconnect(CN, C, SN, LRef)], T /= ok]
+ after
+ ok = diameter:remove_transport(SN, LRef)
+ end.
stop_services(Config) ->
#group{client_service = CN,
diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl
index 53d2d6660e..c94f46b7a5 100644
--- a/lib/diameter/test/diameter_transport_SUITE.erl
+++ b/lib/diameter/test/diameter_transport_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. 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.
@@ -140,7 +140,9 @@ sctp_accept(Config) ->
-define(PEER_COUNT, 8).
accept(Prot) ->
- T = {Prot, make_ref()},
+ Ref = make_ref(),
+ true = diameter_reg:add_new({diameter_config, transport, Ref}), %% fake it
+ T = {Prot, Ref},
[] = ?util:run(?util:scramble(acc(2*?PEER_COUNT, T, []))).
acc(0, _, Acc) ->
@@ -336,13 +338,12 @@ make_msg() ->
%% crypto:rand_bytes/1 isn't available on all platforms (since openssl
%% isn't) so roll our own.
rand_bytes(N) ->
- random:seed(diameter_util:seed()),
rand_bytes(N, <<>>).
rand_bytes(0, Bin) ->
Bin;
rand_bytes(N, Bin) ->
- Oct = random:uniform(256) - 1,
+ Oct = rand:uniform(256) - 1,
rand_bytes(N-1, <<Oct, Bin/binary>>).
%% ===========================================================================
diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl
index 52b747e99c..37fcbbc267 100644
--- a/lib/diameter/test/diameter_util.erl
+++ b/lib/diameter/test/diameter_util.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. 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.
@@ -31,7 +31,6 @@
fold/3,
foldl/3,
scramble/1,
- seed/0,
unique_string/0,
have_sctp/0]).
@@ -178,23 +177,15 @@ scramble(L) ->
[[fun s/1, L]]).
s(L) ->
- random:seed(seed()),
s([], L).
s(Acc, []) ->
Acc;
s(Acc, L) ->
- {H, [T|Rest]} = lists:split(random:uniform(length(L)) - 1, L),
+ {H, [T|Rest]} = lists:split(rand:uniform(length(L)) - 1, L),
s([T|Acc], H ++ Rest).
%% ---------------------------------------------------------------------------
-%% seed/0
-
-seed() ->
- {_,T} = diameter_lib:seed(),
- T.
-
-%% ---------------------------------------------------------------------------
%% unique_string/0
unique_string() ->
@@ -345,11 +336,12 @@ transport(SvcName, Ref) ->
disconnect(Client, Ref, Server, LRef) ->
true = diameter:subscribe(Server),
ok = diameter:remove_transport(Client, Ref),
- ok = receive
- {diameter_event, Server, {down, LRef, _, _}} -> ok
- after 10000 ->
- {Client, Ref, Server, LRef, process_info(self(), messages)}
- end.
+ receive
+ {diameter_event, Server, {down, LRef, _, _}} ->
+ ok
+ after 10000 ->
+ {Client, Ref, Server, LRef, process_info(self(), messages)}
+ end.
%% ---------------------------------------------------------------------------
diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl
index e86d090b13..ef57b7b084 100644
--- a/lib/edoc/src/edoc_layout.erl
+++ b/lib/edoc/src/edoc_layout.erl
@@ -901,17 +901,11 @@ t_map(Es) ->
t_map_field(#xmlElement{content = [K,V]}=E) ->
KElem = t_utype_elem(K),
VElem = t_utype_elem(V),
- AT = get_attrval(assoc_type, E),
- IsAny = fun(["any","()"]) -> true; (_) -> false end,
- case AT =:= "assoc" andalso IsAny(KElem) andalso IsAny(VElem) of
- true -> "...";
- false ->
- AS = case AT of
- "assoc" -> " => ";
- "exact" -> " := "
- end,
- KElem ++ [AS] ++ VElem
- end.
+ AS = case get_attrval(assoc_type, E) of
+ "assoc" -> " => ";
+ "exact" -> " := "
+ end,
+ KElem ++ [AS] ++ VElem.
t_record(E, Es) ->
Name = ["#"] ++ t_type(get_elem(atom, Es)),
diff --git a/lib/edoc/src/edoc_parser.yrl b/lib/edoc/src/edoc_parser.yrl
index 983e2f8496..68a3439f10 100644
--- a/lib/edoc/src/edoc_parser.yrl
+++ b/lib/edoc/src/edoc_parser.yrl
@@ -82,9 +82,6 @@ utype_map_field -> utype '=>' utype : #t_map_field{assoc_type = assoc,
utype_map_field -> utype ':=' utype : #t_map_field{assoc_type = exact,
k_type = '$1',
v_type = '$3'}.
-utype_map_field -> '...' : #t_map_field{assoc_type = assoc,
- k_type = any(),
- v_type = any()}.
utype_tuple -> '{' utypes '}' : lists:reverse('$2').
@@ -354,9 +351,6 @@ all_vars([#t_var{} | As]) ->
all_vars(As) ->
As =:= [].
-any() ->
- #t_type{name = #t_name{name = any}, args = []}.
-
%% ---------------------------------------------------------------------
%% @doc EDoc type specification parsing. Parses the content of
diff --git a/lib/erl_docgen/src/docgen_otp_specs.erl b/lib/erl_docgen/src/docgen_otp_specs.erl
index 5bc3be7a8d..6c41147e27 100644
--- a/lib/erl_docgen/src/docgen_otp_specs.erl
+++ b/lib/erl_docgen/src/docgen_otp_specs.erl
@@ -446,17 +446,11 @@ t_map(Es) ->
t_map_field(#xmlElement{content = [K,V]}=E) ->
KElem = t_utype_elem(K),
VElem = t_utype_elem(V),
- AT = get_attrval(assoc_type, E),
- IsAny = fun(["any","()"]) -> true; (_) -> false end,
- case AT =:= "assoc" andalso IsAny(KElem) andalso IsAny(VElem) of
- true -> "...";
- false ->
- AS = case AT of
- "assoc" -> " => ";
- "exact" -> " := "
- end,
- KElem ++ [AS] ++ VElem
- end.
+ AS = case get_attrval(assoc_type, E) of
+ "assoc" -> " => ";
+ "exact" -> " := "
+ end,
+ KElem ++ [AS] ++ VElem.
t_record(E, Es) ->
Name = ["#"] ++ t_type(get_elem(atom, Es)),
diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index c383541020..7826dada9d 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -80,11 +80,9 @@
t_float/0,
t_var_names/1,
t_form_to_string/1,
- t_from_form/4,
- t_from_form/5,
+ t_from_form/6,
t_from_form_without_remote/3,
- t_check_record_fields/4,
- t_check_record_fields/5,
+ t_check_record_fields/6,
t_from_range/2,
t_from_range_unsafe/2,
t_from_term/1,
@@ -221,6 +219,7 @@
is_erl_type/1,
atom_to_string/1,
var_table__new/0,
+ cache__new/0,
map_pairwise_merge/3
]).
@@ -237,7 +236,7 @@
-export([t_is_identifier/1]).
-endif.
--export_type([erl_type/0, opaques/0, type_table/0, var_table/0]).
+-export_type([erl_type/0, opaques/0, type_table/0, var_table/0, cache/0]).
%%-define(DEBUG, true).
@@ -328,7 +327,7 @@
%% Auxiliary types and convenient macros
%%
--type parse_form() :: erl_parse:abstract_expr().
+-type parse_form() :: erl_parse:abstract_type().
-type rng_elem() :: 'pos_inf' | 'neg_inf' | integer().
-record(int_set, {set :: [integer()]}).
@@ -375,9 +374,11 @@
-type opaques() :: [erl_type()] | 'universe'.
-type record_key() :: {'record', atom()}.
--type type_key() :: {'type' | 'opaque', atom(), arity()}.
+-type type_key() :: {'type' | 'opaque', mfa()}.
-type record_value() :: [{atom(), erl_parse:abstract_expr(), erl_type()}].
--type type_value() :: {module(), erl_type(), atom()}.
+-type type_value() :: {{module(), {file:name(), erl_anno:line()},
+ erl_parse:abstract_type(), ArgNames :: [atom()]},
+ erl_type()}.
-type type_table() :: dict:dict(record_key() | type_key(),
record_value() | type_value()).
@@ -757,8 +758,8 @@ t_opaque_from_records(RecDict) ->
{{Module, _FileLine, _Form, ArgNames}, _Type}) ->
%% Args = args_to_types(ArgNames),
%% List = lists:zip(ArgNames, Args),
- %% TmpVarDict = dict:from_list(List),
- %% Rep = t_from_form(Type, RecDict, TmpVarDict),
+ %% TmpVarTab = maps:to_list(List),
+ %% Rep = t_from_form(Type, RecDict, TmpVarTab),
Rep = t_any(), % not used for anything right now
Args = [t_any() || _ <- ArgNames],
t_opaque(Module, Name, Args, Rep)
@@ -1769,7 +1770,8 @@ mapdict_insert(E={_,_,_}, T) -> [E|T].
t_map_mandatoriness(), erl_type())
-> t_map_pair() | false),
erl_type(), erl_type()) -> t_map_dict().
-map_pairwise_merge(F, ?map(APairs, ADefK, ADefV), ?map(BPairs, BDefK, BDefV)) ->
+map_pairwise_merge(F, ?map(APairs, ADefK, ADefV),
+ ?map(BPairs, BDefK, BDefV)) ->
map_pairwise_merge(F, APairs, ADefK, ADefV, BPairs, BDefK, BDefV).
map_pairwise_merge(_, [], _, _, [], _, _) -> [];
@@ -4302,7 +4304,6 @@ t_to_string(?map(Pairs0,DefK,DefV), RecDict) ->
{Pairs, ExtraEl} =
case {DefK, DefV} of
{?none, ?none} -> {Pairs0, []};
- {?any, ?any} -> {Pairs0, ["..."]};
_ -> {Pairs0 ++ [{DefK,?opt,DefV}], []}
end,
Tos = fun(T) -> case T of
@@ -4414,33 +4415,30 @@ mod_name(Mod, Name) ->
-type type_names() :: [type_key() | record_key()].
--type mta() :: {module(), atom(), arity()}.
--type mra() :: {module(), atom(), arity()}.
--type site() :: {'type', mta()} | {'spec', mfa()} | {'record', mra()}.
+-type mta() :: {module(), atom(), arity()}.
+-type mra() :: {module(), atom(), arity()}.
+-type site() :: {'type', mta()} | {'spec', mfa()} | {'record', mra()}.
+-type cache_key() :: {module(), atom(), expand_depth(),
+ [erl_type()], type_names()}.
+-opaque cache() :: #{cache_key() => {erl_type(), expand_limit()}}.
--spec t_from_form(parse_form(), sets:set(mfa()),
- site(), mod_records()) -> erl_type().
+-spec t_from_form(parse_form(), sets:set(mfa()), site(), mod_records(),
+ var_table(), cache()) -> {erl_type(), cache()}.
-t_from_form(Form, ExpTypes, Site, RecDict) ->
- t_from_form(Form, ExpTypes, Site, RecDict, maps:new()).
-
--spec t_from_form(parse_form(), sets:set(mfa()),
- site(), mod_records(), var_table()) -> erl_type().
-
-t_from_form(Form, ExpTypes, Site, RecDict, VarDict) ->
- {T, _} = t_from_form1(Form, ExpTypes, Site, RecDict, VarDict),
- T.
+t_from_form(Form, ExpTypes, Site, RecDict, VarTab, Cache) ->
+ t_from_form1(Form, ExpTypes, Site, RecDict, VarTab, Cache).
%% Replace external types with with none().
-spec t_from_form_without_remote(parse_form(), site(), type_table()) ->
- erl_type().
+ {erl_type(), cache()}.
t_from_form_without_remote(Form, Site, TypeTable) ->
Module = site_module(Site),
RecDict = dict:from_list([{Module, TypeTable}]),
ExpTypes = replace_by_none,
- {T, _} = t_from_form1(Form, ExpTypes, Site, RecDict, maps:new()),
- T.
+ VarTab = var_table__new(),
+ Cache = cache__new(),
+ t_from_form1(Form, ExpTypes, Site, RecDict, VarTab, Cache).
%% REC_TYPE_LIMIT is used for limiting the depth of recursive types.
%% EXPAND_LIMIT is used for limiting the size of types by
@@ -4453,34 +4451,53 @@ t_from_form_without_remote(Form, Site, TypeTable) ->
-type expand_depth() :: integer().
+-record(from_form, {site :: site(),
+ xtypes :: sets:set(mfa()) | 'replace_by_none',
+ mrecs :: mod_records(),
+ vtab :: var_table(),
+ tnames :: type_names()}).
+
-spec t_from_form1(parse_form(), sets:set(mfa()) | 'replace_by_none',
- site(), mod_records(), var_table()) ->
- {erl_type(), expand_limit()}.
+ site(), mod_records(), var_table(), cache()) ->
+ {erl_type(), cache()}.
-t_from_form1(Form, ET, Site, MR, V) ->
+t_from_form1(Form, ET, Site, MR, V, C) ->
TypeNames = initial_typenames(Site),
- t_from_form1(Form, TypeNames, ET, Site, MR, V, ?EXPAND_DEPTH).
+ State = #from_form{site = Site,
+ xtypes = ET,
+ mrecs = MR,
+ vtab = V,
+ tnames = TypeNames},
+ L = ?EXPAND_LIMIT,
+ {T1, L1, C1} = from_form(Form, State, ?EXPAND_DEPTH, L, C),
+ if
+ L1 =< 0 ->
+ from_form_loop(Form, State, 1, L, C1);
+ true ->
+ {T1, C1}
+ end.
initial_typenames({type, _MTA}=Site) -> [Site];
initial_typenames({spec, _MFA}) -> [];
initial_typenames({record, _MRA}) -> [].
-t_from_form1(Form, TypeNames, ET, Site, MR, V, D) ->
- L = ?EXPAND_LIMIT,
- {T, L1} = t_from_form(Form, TypeNames, ET, Site, MR, V, D, L),
+from_form_loop(Form, State, D, Limit, C) ->
+ {T1, L1, C1} = from_form(Form, State, D, Limit, C),
+ Delta = Limit - L1,
if
- L1 =< 0, D > 1 ->
- D1 = D div 2,
- t_from_form1(Form, TypeNames, ET, Site, MR, V, D1);
+ %% Save some time by assuming next depth will exceed the limit.
+ Delta * 8 > Limit ->
+ {T1, C1};
true ->
- {T, L1}
+ D1 = D + 1,
+ from_form_loop(Form, State, D1, Limit, C1)
end.
--spec t_from_form(parse_form(), type_names(),
- sets:set(mfa()) | 'replace_by_none',
- site(), mod_records(), var_table(),
- expand_depth(), expand_limit())
- -> {erl_type(), expand_limit()}.
+-spec from_form(parse_form(),
+ #from_form{},
+ expand_depth(),
+ expand_limit(),
+ cache()) -> {erl_type(), expand_limit(), cache()}.
%% If there is something wrong with parse_form()
%% throw({error, io_lib:chars()} is called;
@@ -4490,330 +4507,336 @@ t_from_form1(Form, TypeNames, ET, Site, MR, V, D) ->
%%
%% It is assumed that site_module(S) can be found in MR.
-t_from_form(_, _TypeNames, _ET, _S, _MR, _V, D, L) when D =< 0 ; L =< 0 ->
- {t_any(), L};
-t_from_form({var, _L, '_'}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_any(), L};
-t_from_form({var, _L, Name}, _TypeNames, _ET, _S, _MR, V, _D, L) ->
+from_form(_, _S, D, L, C) when D =< 0 ; L =< 0 ->
+ {t_any(), L, C};
+from_form({var, _L, '_'}, _S, _D, L, C) ->
+ {t_any(), L, C};
+from_form({var, _L, Name}, S, _D, L, C) ->
+ V = S#from_form.vtab,
case maps:find(Name, V) of
- error -> {t_var(Name), L};
- {ok, Val} -> {Val, L}
+ error -> {t_var(Name), L, C};
+ {ok, Val} -> {Val, L, C}
end;
-t_from_form({ann_type, _L, [_Var, Type]}, TypeNames, ET, S, MR, V, D, L) ->
- t_from_form(Type, TypeNames, ET, S, MR, V, D, L);
-t_from_form({paren_type, _L, [Type]}, TypeNames, ET, S, MR, V, D, L) ->
- t_from_form(Type, TypeNames, ET, S, MR, V, D, L);
-t_from_form({remote_type, _L, [{atom, _, Module}, {atom, _, Type}, Args]},
- TypeNames, ET, S, MR, V, D, L) ->
- remote_from_form(Module, Type, Args, TypeNames, ET, S, MR, V, D, L);
-t_from_form({atom, _L, Atom}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_atom(Atom), L};
-t_from_form({integer, _L, Int}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_integer(Int), L};
-t_from_form({op, _L, _Op, _Arg} = Op, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
+from_form({ann_type, _L, [_Var, Type]}, S, D, L, C) ->
+ from_form(Type, S, D, L, C);
+from_form({paren_type, _L, [Type]}, S, D, L, C) ->
+ from_form(Type, S, D, L, C);
+from_form({remote_type, _L, [{atom, _, Module}, {atom, _, Type}, Args]},
+ S, D, L, C) ->
+ remote_from_form(Module, Type, Args, S, D, L, C);
+from_form({atom, _L, Atom}, _S, _D, L, C) ->
+ {t_atom(Atom), L, C};
+from_form({integer, _L, Int}, _S, _D, L, C) ->
+ {t_integer(Int), L, C};
+from_form({op, _L, _Op, _Arg} = Op, _S, _D, L, C) ->
case erl_eval:partial_eval(Op) of
{integer, _, Val} ->
- {t_integer(Val), L};
+ {t_integer(Val), L, C};
_ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Op])})
end;
-t_from_form({op, _L, _Op, _Arg1, _Arg2} = Op, _TypeNames,
- _ET, _S, _MR, _V, _D, L) ->
+from_form({op, _L, _Op, _Arg1, _Arg2} = Op, _S, _D, L, C) ->
case erl_eval:partial_eval(Op) of
{integer, _, Val} ->
- {t_integer(Val), L};
+ {t_integer(Val), L, C};
_ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Op])})
end;
-t_from_form({type, _L, any, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_any(), L};
-t_from_form({type, _L, arity, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_arity(), L};
-t_from_form({type, _L, atom, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_atom(), L};
-t_from_form({type, _L, binary, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_binary(), L};
-t_from_form({type, _L, binary, [Base, Unit]} = Type,
- _TypeNames, _ET, _S, _MR, _V, _D, L) ->
+from_form({type, _L, any, []}, _S, _D, L, C) ->
+ {t_any(), L, C};
+from_form({type, _L, arity, []}, _S, _D, L, C) ->
+ {t_arity(), L, C};
+from_form({type, _L, atom, []}, _S, _D, L, C) ->
+ {t_atom(), L, C};
+from_form({type, _L, binary, []}, _S, _D, L, C) ->
+ {t_binary(), L, C};
+from_form({type, _L, binary, [Base, Unit]} = Type, _S, _D, L, C) ->
case {erl_eval:partial_eval(Base), erl_eval:partial_eval(Unit)} of
{{integer, _, B}, {integer, _, U}} when B >= 0, U >= 0 ->
- {t_bitstr(U, B), L};
+ {t_bitstr(U, B), L, C};
_ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Type])})
end;
-t_from_form({type, _L, bitstring, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_bitstr(), L};
-t_from_form({type, _L, bool, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_boolean(), L}; % XXX: Temporarily
-t_from_form({type, _L, boolean, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_boolean(), L};
-t_from_form({type, _L, byte, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_byte(), L};
-t_from_form({type, _L, char, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_char(), L};
-t_from_form({type, _L, float, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_float(), L};
-t_from_form({type, _L, function, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_fun(), L};
-t_from_form({type, _L, 'fun', []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_fun(), L};
-t_from_form({type, _L, 'fun', [{type, _, any}, Range]}, TypeNames,
- ET, S, MR, V, D, L) ->
- {T, L1} = t_from_form(Range, TypeNames, ET, S, MR, V, D - 1, L - 1),
- {t_fun(T), L1};
-t_from_form({type, _L, 'fun', [{type, _, product, Domain}, Range]},
- TypeNames, ET, S, MR, V, D, L) ->
- {Dom1, L1} = list_from_form(Domain, TypeNames, ET, S, MR, V, D, L),
- {Ran1, L2} = t_from_form(Range, TypeNames, ET, S, MR, V, D, L1),
- {t_fun(Dom1, Ran1), L2};
-t_from_form({type, _L, identifier, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_identifier(), L};
-t_from_form({type, _L, integer, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_integer(), L};
-t_from_form({type, _L, iodata, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_iodata(), L};
-t_from_form({type, _L, iolist, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_iolist(), L};
-t_from_form({type, _L, list, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_list(), L};
-t_from_form({type, _L, list, [Type]}, TypeNames, ET, S, MR, V, D, L) ->
- {T, L1} = t_from_form(Type, TypeNames, ET, S, MR, V, D - 1, L - 1),
- {t_list(T), L1};
-t_from_form({type, _L, map, any}, TypeNames, ET, S, MR, V, D, L) ->
- builtin_type(map, t_map(), TypeNames, ET, S, MR, V, D, L);
-t_from_form({type, _L, map, List}, TypeNames, ET, S, MR, V, D, L) ->
- {Pairs1, L5} =
- fun PairsFromForm(_, L1) when L1 =< 0 -> {[{?any,?opt,?any}], L1};
- PairsFromForm([], L1) -> {[], L1};
- PairsFromForm([{type, _, Oper, [KF, VF]}|T], L1) ->
- {Key, L2} = t_from_form(KF, TypeNames, ET, S, MR, V, D - 1, L1),
- {Val, L3} = t_from_form(VF, TypeNames, ET, S, MR, V, D - 1, L2),
- {Pairs0, L4} = PairsFromForm(T, L3 - 1),
+from_form({type, _L, bitstring, []}, _S, _D, L, C) ->
+ {t_bitstr(), L, C};
+from_form({type, _L, bool, []}, _S, _D, L, C) ->
+ {t_boolean(), L, C}; % XXX: Temporarily
+from_form({type, _L, boolean, []}, _S, _D, L, C) ->
+ {t_boolean(), L, C};
+from_form({type, _L, byte, []}, _S, _D, L, C) ->
+ {t_byte(), L, C};
+from_form({type, _L, char, []}, _S, _D, L, C) ->
+ {t_char(), L, C};
+from_form({type, _L, float, []}, _S, _D, L, C) ->
+ {t_float(), L, C};
+from_form({type, _L, function, []}, _S, _D, L, C) ->
+ {t_fun(), L, C};
+from_form({type, _L, 'fun', []}, _S, _D, L, C) ->
+ {t_fun(), L, C};
+from_form({type, _L, 'fun', [{type, _, any}, Range]}, S, D, L, C) ->
+ {T, L1, C1} = from_form(Range, S, D - 1, L - 1, C),
+ {t_fun(T), L1, C1};
+from_form({type, _L, 'fun', [{type, _, product, Domain}, Range]},
+ S, D, L, C) ->
+ {Dom1, L1, C1} = list_from_form(Domain, S, D, L, C),
+ {Ran1, L2, C2} = from_form(Range, S, D, L1, C1),
+ {t_fun(Dom1, Ran1), L2, C2};
+from_form({type, _L, identifier, []}, _S, _D, L, C) ->
+ {t_identifier(), L, C};
+from_form({type, _L, integer, []}, _S, _D, L, C) ->
+ {t_integer(), L, C};
+from_form({type, _L, iodata, []}, _S, _D, L, C) ->
+ {t_iodata(), L, C};
+from_form({type, _L, iolist, []}, _S, _D, L, C) ->
+ {t_iolist(), L, C};
+from_form({type, _L, list, []}, _S, _D, L, C) ->
+ {t_list(), L, C};
+from_form({type, _L, list, [Type]}, S, D, L, C) ->
+ {T, L1, C1} = from_form(Type, S, D - 1, L - 1, C),
+ {t_list(T), L1, C1};
+from_form({type, _L, map, any}, S, D, L, C) ->
+ builtin_type(map, t_map(), S, D, L, C);
+from_form({type, _L, map, List}, S, D0, L, C) ->
+ {Pairs1, L5, C5} =
+ fun PairsFromForm(_, L1, C1) when L1 =< 0 -> {[{?any,?opt,?any}], L1, C1};
+ PairsFromForm([], L1, C1) -> {[], L1, C1};
+ PairsFromForm([{type, _, Oper, [KF, VF]}|T], L1, C1) ->
+ D = D0 - 1,
+ {Key, L2, C2} = from_form(KF, S, D, L1, C1),
+ {Val, L3, C3} = from_form(VF, S, D, L2, C2),
+ {Pairs0, L4, C4} = PairsFromForm(T, L3 - 1, C3),
case Oper of
- map_field_assoc -> {[{Key,?opt, Val}|Pairs0], L4};
- map_field_exact -> {[{Key,?mand,Val}|Pairs0], L4}
+ map_field_assoc -> {[{Key,?opt, Val}|Pairs0], L4, C4};
+ map_field_exact -> {[{Key,?mand,Val}|Pairs0], L4, C4}
end
- end(List, L),
+ end(List, L, C),
try
{Pairs, DefK, DefV} = map_from_form(Pairs1, [], [], [], ?none, ?none),
- {t_map(Pairs, DefK, DefV), L5}
- catch none -> {t_none(), L5}
+ {t_map(Pairs, DefK, DefV), L5, C5}
+ catch none -> {t_none(), L5, C5}
end;
-t_from_form({type, _L, mfa, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_mfa(), L};
-t_from_form({type, _L, module, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_module(), L};
-t_from_form({type, _L, nil, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_nil(), L};
-t_from_form({type, _L, neg_integer, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_neg_integer(), L};
-t_from_form({type, _L, non_neg_integer, []}, _TypeNames, _ET, _S, _MR,
- _V, _D, L) ->
- {t_non_neg_integer(), L};
-t_from_form({type, _L, no_return, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_unit(), L};
-t_from_form({type, _L, node, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_node(), L};
-t_from_form({type, _L, none, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_none(), L};
-t_from_form({type, _L, nonempty_list, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_nonempty_list(), L};
-t_from_form({type, _L, nonempty_list, [Type]}, TypeNames, ET, S, MR, V, D, L) ->
- {T, L1} = t_from_form(Type, TypeNames, ET, S, MR, V, D, L - 1),
- {t_nonempty_list(T), L1};
-t_from_form({type, _L, nonempty_improper_list, [Cont, Term]}, TypeNames,
- ET, S, MR, V, D, L) ->
- {T1, L1} = t_from_form(Cont, TypeNames, ET, S, MR, V, D, L - 1),
- {T2, L2} = t_from_form(Term, TypeNames, ET, S, MR, V, D, L1),
- {t_cons(T1, T2), L2};
-t_from_form({type, _L, nonempty_maybe_improper_list, []}, _TypeNames,
- _ET, _S, _MR, _V, _D, L) ->
- {t_cons(?any, ?any), L};
-t_from_form({type, _L, nonempty_maybe_improper_list, [Cont, Term]},
- TypeNames, ET, S, MR, V, D, L) ->
- {T1, L1} = t_from_form(Cont, TypeNames, ET, S, MR, V, D, L - 1),
- {T2, L2} = t_from_form(Term, TypeNames, ET, S, MR, V, D, L1),
- {t_cons(T1, T2), L2};
-t_from_form({type, _L, nonempty_string, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_nonempty_string(), L};
-t_from_form({type, _L, number, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_number(), L};
-t_from_form({type, _L, pid, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_pid(), L};
-t_from_form({type, _L, port, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_port(), L};
-t_from_form({type, _L, pos_integer, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_pos_integer(), L};
-t_from_form({type, _L, maybe_improper_list, []}, _TypeNames,
- _ET, _S, _MR, _V, _D, L) ->
- {t_maybe_improper_list(), L};
-t_from_form({type, _L, maybe_improper_list, [Content, Termination]},
- TypeNames, ET, S, MR, V, D, L) ->
- {T1, L1} = t_from_form(Content, TypeNames, ET, S, MR, V, D, L - 1),
- {T2, L2} = t_from_form(Termination, TypeNames, ET, S, MR, V, D, L1),
- {t_maybe_improper_list(T1, T2), L2};
-t_from_form({type, _L, product, Elements}, TypeNames, ET, S, MR, V, D, L) ->
- {Lst, L1} = list_from_form(Elements, TypeNames, ET, S, MR, V, D - 1, L),
- {t_product(Lst), L1};
-t_from_form({type, _L, range, [From, To]} = Type,
- _TypeNames, _ET, _S, _MR, _V, _D, L) ->
+from_form({type, _L, mfa, []}, _S, _D, L, C) ->
+ {t_mfa(), L, C};
+from_form({type, _L, module, []}, _S, _D, L, C) ->
+ {t_module(), L, C};
+from_form({type, _L, nil, []}, _S, _D, L, C) ->
+ {t_nil(), L, C};
+from_form({type, _L, neg_integer, []}, _S, _D, L, C) ->
+ {t_neg_integer(), L, C};
+from_form({type, _L, non_neg_integer, []}, _S, _D, L, C) ->
+ {t_non_neg_integer(), L, C};
+from_form({type, _L, no_return, []}, _S, _D, L, C) ->
+ {t_unit(), L, C};
+from_form({type, _L, node, []}, _S, _D, L, C) ->
+ {t_node(), L, C};
+from_form({type, _L, none, []}, _S, _D, L, C) ->
+ {t_none(), L, C};
+from_form({type, _L, nonempty_list, []}, _S, _D, L, C) ->
+ {t_nonempty_list(), L, C};
+from_form({type, _L, nonempty_list, [Type]}, S, D, L, C) ->
+ {T, L1, C1} = from_form(Type, S, D, L - 1, C),
+ {t_nonempty_list(T), L1, C1};
+from_form({type, _L, nonempty_improper_list, [Cont, Term]}, S, D, L, C) ->
+ {T1, L1, C1} = from_form(Cont, S, D, L - 1, C),
+ {T2, L2, C2} = from_form(Term, S, D, L1, C1),
+ {t_cons(T1, T2), L2, C2};
+from_form({type, _L, nonempty_maybe_improper_list, []}, _S, _D, L, C) ->
+ {t_cons(?any, ?any), L, C};
+from_form({type, _L, nonempty_maybe_improper_list, [Cont, Term]},
+ S, D, L, C) ->
+ {T1, L1, C1} = from_form(Cont, S, D, L - 1, C),
+ {T2, L2, C2} = from_form(Term, S, D, L1, C1),
+ {t_cons(T1, T2), L2, C2};
+from_form({type, _L, nonempty_string, []}, _S, _D, L, C) ->
+ {t_nonempty_string(), L, C};
+from_form({type, _L, number, []}, _S, _D, L, C) ->
+ {t_number(), L, C};
+from_form({type, _L, pid, []}, _S, _D, L, C) ->
+ {t_pid(), L, C};
+from_form({type, _L, port, []}, _S, _D, L, C) ->
+ {t_port(), L, C};
+from_form({type, _L, pos_integer, []}, _S, _D, L, C) ->
+ {t_pos_integer(), L, C};
+from_form({type, _L, maybe_improper_list, []}, _S, _D, L, C) ->
+ {t_maybe_improper_list(), L, C};
+from_form({type, _L, maybe_improper_list, [Content, Termination]},
+ S, D, L, C) ->
+ {T1, L1, C1} = from_form(Content, S, D, L - 1, C),
+ {T2, L2, C2} = from_form(Termination, S, D, L1, C1),
+ {t_maybe_improper_list(T1, T2), L2, C2};
+from_form({type, _L, product, Elements}, S, D, L, C) ->
+ {Lst, L1, C1} = list_from_form(Elements, S, D - 1, L, C),
+ {t_product(Lst), L1, C1};
+from_form({type, _L, range, [From, To]} = Type, _S, _D, L, C) ->
case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of
{{integer, _, FromVal}, {integer, _, ToVal}} ->
- {t_from_range(FromVal, ToVal), L};
+ {t_from_range(FromVal, ToVal), L, C};
_ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Type])})
end;
-t_from_form({type, _L, record, [Name|Fields]}, TypeNames, ET, S, MR, V, D, L) ->
- record_from_form(Name, Fields, TypeNames, ET, S, MR, V, D, L);
-t_from_form({type, _L, reference, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_reference(), L};
-t_from_form({type, _L, string, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_string(), L};
-t_from_form({type, _L, term, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_any(), L};
-t_from_form({type, _L, timeout, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_timeout(), L};
-t_from_form({type, _L, tuple, any}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_tuple(), L};
-t_from_form({type, _L, tuple, Args}, TypeNames, ET, S, MR, V, D, L) ->
- {Lst, L1} = list_from_form(Args, TypeNames, ET, S, MR, V, D - 1, L),
- {t_tuple(Lst), L1};
-t_from_form({type, _L, union, Args}, TypeNames, ET, S, MR, V, D, L) ->
- {Lst, L1} = list_from_form(Args, TypeNames, ET, S, MR, V, D, L),
- {t_sup(Lst), L1};
-t_from_form({user_type, _L, Name, Args}, TypeNames, ET, S, MR, V, D, L) ->
- type_from_form(Name, Args, TypeNames, ET, S, MR, V, D, L);
-t_from_form({type, _L, Name, Args}, TypeNames, ET, S, MR, V, D, L) ->
+from_form({type, _L, record, [Name|Fields]}, S, D, L, C) ->
+ record_from_form(Name, Fields, S, D, L, C);
+from_form({type, _L, reference, []}, _S, _D, L, C) ->
+ {t_reference(), L, C};
+from_form({type, _L, string, []}, _S, _D, L, C) ->
+ {t_string(), L, C};
+from_form({type, _L, term, []}, _S, _D, L, C) ->
+ {t_any(), L, C};
+from_form({type, _L, timeout, []}, _S, _D, L, C) ->
+ {t_timeout(), L, C};
+from_form({type, _L, tuple, any}, _S, _D, L, C) ->
+ {t_tuple(), L, C};
+from_form({type, _L, tuple, Args}, S, D, L, C) ->
+ {Lst, L1, C1} = list_from_form(Args, S, D - 1, L, C),
+ {t_tuple(Lst), L1, C1};
+from_form({type, _L, union, Args}, S, D, L, C) ->
+ {Lst, L1, C1} = list_from_form(Args, S, D, L, C),
+ {t_sup(Lst), L1, C1};
+from_form({user_type, _L, Name, Args}, S, D, L, C) ->
+ type_from_form(Name, Args, S, D, L, C);
+from_form({type, _L, Name, Args}, S, D, L, C) ->
%% Compatibility: modules compiled before Erlang/OTP 18.0.
- type_from_form(Name, Args, TypeNames, ET, S, MR, V, D, L);
-t_from_form({opaque, _L, Name, {Mod, Args, Rep}}, _TypeNames,
- _ET, _S, _MR, _V, _D, L) ->
+ type_from_form(Name, Args, S, D, L, C);
+from_form({opaque, _L, Name, {Mod, Args, Rep}}, _S, _D, L, C) ->
%% XXX. To be removed.
- {t_opaque(Mod, Name, Args, Rep), L}.
+ {t_opaque(Mod, Name, Args, Rep), L, C}.
-builtin_type(Name, Type, TypeNames, ET, Site, MR, V, D, L) ->
+builtin_type(Name, Type, S, D, L, C) ->
+ #from_form{site = Site, mrecs = MR} = S,
M = site_module(Site),
case dict:find(M, MR) of
{ok, R} ->
case lookup_type(Name, 0, R) of
{_, {{_M, _FL, _F, _A}, _T}} ->
- type_from_form(Name, [], TypeNames, ET, Site, MR, V, D, L);
+ type_from_form(Name, [], S, D, L, C);
error ->
- {Type, L}
+ {Type, L, C}
end;
error ->
- {Type, L}
+ {Type, L, C}
end.
-type_from_form(Name, Args, TypeNames, ET, Site0, MR, V, D, L) ->
+type_from_form(Name, Args, S, D, L, C) ->
+ #from_form{site = Site, mrecs = MR, tnames = TypeNames} = S,
ArgsLen = length(Args),
- Module = site_module(Site0),
- {ok, R} = dict:find(Module, MR),
+ Module = site_module(Site),
TypeName = {type, {Module, Name, ArgsLen}},
+ case can_unfold_more(TypeName, TypeNames) of
+ true ->
+ {ok, R} = dict:find(Module, MR),
+ type_from_form1(Name, Args, ArgsLen, R, TypeName, TypeNames,
+ S, D, L, C);
+ false ->
+ {t_any(), L, C}
+ end.
+
+type_from_form1(Name, Args, ArgsLen, R, TypeName, TypeNames, S, D, L, C) ->
case lookup_type(Name, ArgsLen, R) of
- {type, {{Module, _FileName, Form, ArgNames}, _Type}} ->
- case can_unfold_more(TypeName, TypeNames) of
- true ->
- NewTypeNames = [TypeName|TypeNames],
- {ArgTypes, L1} =
- list_from_form(Args, TypeNames, ET, Site0, MR, V, D, L),
- List = lists:zip(ArgNames, ArgTypes),
- TmpV = maps:from_list(List),
- Site = TypeName,
- t_from_form(Form, NewTypeNames, ET, Site, MR, TmpV, D, L1);
- false ->
- {t_any(), L}
- end;
- {opaque, {{Module, _FileName, Form, ArgNames}, Type}} ->
- case can_unfold_more(TypeName, TypeNames) of
- true ->
- NewTypeNames = [TypeName|TypeNames],
- {ArgTypes, L1} =
- list_from_form(Args, NewTypeNames, ET, Site0, MR, V, D, L),
+ {Tag, {{Module, _FileName, Form, ArgNames}, Type}} ->
+ NewTypeNames = [TypeName|TypeNames],
+ S1 = S#from_form{tnames = NewTypeNames},
+ {ArgTypes, L1, C1} = list_from_form(Args, S1, D, L, C),
+ CKey = cache_key(Module, Name, ArgTypes, TypeNames, D),
+ case cache_find(CKey, C) of
+ {CachedType, DeltaL} ->
+ {CachedType, L1 - DeltaL, C};
+ error ->
List = lists:zip(ArgNames, ArgTypes),
TmpV = maps:from_list(List),
- Site = TypeName,
- {Rep, L2} =
- t_from_form(Form, NewTypeNames, ET, Site, MR, TmpV, D, L1),
- Rep1 = choose_opaque_type(Rep, Type),
- Rep2 = case cannot_have_opaque(Rep1, TypeName, TypeNames) of
- true -> Rep1;
- false ->
- ArgTypes2 = subst_all_vars_to_any_list(ArgTypes),
- t_opaque(Module, Name, ArgTypes2, Rep1)
- end,
- {Rep2, L2};
- false -> {t_any(), L}
+ S2 = S1#from_form{site = TypeName, vtab = TmpV},
+ Fun = fun(DD, LL) -> from_form(Form, S2, DD, LL, C1) end,
+ {NewType, L3, C3} =
+ case Tag of
+ type ->
+ recur_limit(Fun, D, L1, TypeName, TypeNames);
+ opaque ->
+ {Rep, L2, C2} = recur_limit(Fun, D, L1, TypeName, TypeNames),
+ Rep1 = choose_opaque_type(Rep, Type),
+ Rep2 = case cannot_have_opaque(Rep1, TypeName, TypeNames) of
+ true -> Rep1;
+ false ->
+ ArgTypes2 = subst_all_vars_to_any_list(ArgTypes),
+ t_opaque(Module, Name, ArgTypes2, Rep1)
+ end,
+ {Rep2, L2, C2}
+ end,
+ C4 = cache_put(CKey, NewType, L1 - L3, C3),
+ {NewType, L3, C4}
end;
error ->
- Msg = io_lib:format("Unable to find type ~w/~w\n", [Name, ArgsLen]),
+ Msg = io_lib:format("Unable to find type ~w/~w\n",
+ [Name, ArgsLen]),
throw({error, Msg})
end.
-remote_from_form(RemMod, Name, Args, TypeNames, ET, S, MR, V, D, L) ->
+remote_from_form(RemMod, Name, Args, S, D, L, C) ->
+ #from_form{xtypes = ET, mrecs = MR, tnames = TypeNames} = S,
if
ET =:= replace_by_none ->
- {t_none(), L};
+ {t_none(), L, C};
true ->
ArgsLen = length(Args),
MFA = {RemMod, Name, ArgsLen},
case dict:find(RemMod, MR) of
error ->
self() ! {self(), ext_types, MFA},
- {t_any(), L};
+ {t_any(), L, C};
{ok, RemDict} ->
- RemType = {type, MFA},
case sets:is_element(MFA, ET) of
true ->
- case lookup_type(Name, ArgsLen, RemDict) of
- {type, {{_Mod, _FileLine, Form, ArgNames}, _Type}} ->
- case can_unfold_more(RemType, TypeNames) of
- true ->
- NewTypeNames = [RemType|TypeNames],
- {ArgTypes, L1} = list_from_form(Args, TypeNames,
- ET, S, MR, V, D, L),
- List = lists:zip(ArgNames, ArgTypes),
- TmpVarDict = maps:from_list(List),
- Site = RemType,
- t_from_form(Form, NewTypeNames, ET,
- Site, MR, TmpVarDict, D, L1);
- false ->
- {t_any(), L}
- end;
- {opaque, {{Mod, _FileLine, Form, ArgNames}, Type}} ->
- case can_unfold_more(RemType, TypeNames) of
- true ->
- NewTypeNames = [RemType|TypeNames],
- {ArgTypes, L1} = list_from_form(Args, NewTypeNames,
- ET, S, MR, V, D, L),
- List = lists:zip(ArgNames, ArgTypes),
- TmpVarDict = maps:from_list(List),
- Site = RemType,
- {NewRep, L2} =
- t_from_form(Form, NewTypeNames, ET, Site, MR,
- TmpVarDict, D, L1),
- NewRep1 = choose_opaque_type(NewRep, Type),
- NewRep2 =
- case
- cannot_have_opaque(NewRep1, RemType, TypeNames)
- of
- true -> NewRep1;
- false ->
- ArgTypes2 = subst_all_vars_to_any_list(ArgTypes),
- t_opaque(Mod, Name, ArgTypes2, NewRep1)
- end,
- {NewRep2, L2};
- false ->
- {t_any(), L}
- end;
- error ->
- Msg = io_lib:format("Unable to find remote type ~w:~w()\n",
- [RemMod, Name]),
- throw({error, Msg})
+ RemType = {type, MFA},
+ case can_unfold_more(RemType, TypeNames) of
+ true ->
+ remote_from_form1(RemMod, Name, Args, ArgsLen, RemDict,
+ RemType, TypeNames, S, D, L, C);
+ false ->
+ {t_any(), L, C}
end;
false ->
self() ! {self(), ext_types, {RemMod, Name, ArgsLen}},
- {t_any(), L}
+ {t_any(), L, C}
end
end
end.
+remote_from_form1(RemMod, Name, Args, ArgsLen, RemDict, RemType, TypeNames,
+ S, D, L, C) ->
+ case lookup_type(Name, ArgsLen, RemDict) of
+ {Tag, {{Mod, _FileLine, Form, ArgNames}, Type}} ->
+ NewTypeNames = [RemType|TypeNames],
+ S1 = S#from_form{tnames = NewTypeNames},
+ {ArgTypes, L1, C1} = list_from_form(Args, S1, D, L, C),
+ CKey = cache_key(RemMod, Name, ArgTypes, TypeNames, D),
+ %% case error of
+ case cache_find(CKey, C) of
+ {CachedType, DeltaL} ->
+ {CachedType, L - DeltaL, C};
+ error ->
+ List = lists:zip(ArgNames, ArgTypes),
+ TmpVarTab = maps:from_list(List),
+ S2 = S1#from_form{site = RemType, vtab = TmpVarTab},
+ Fun = fun(DD, LL) -> from_form(Form, S2, DD, LL, C1) end,
+ {NewType, L3, C3} =
+ case Tag of
+ type ->
+ recur_limit(Fun, D, L1, RemType, TypeNames);
+ opaque ->
+ {NewRep, L2, C2} = recur_limit(Fun, D, L1, RemType, TypeNames),
+ NewRep1 = choose_opaque_type(NewRep, Type),
+ NewRep2 =
+ case cannot_have_opaque(NewRep1, RemType, TypeNames) of
+ true -> NewRep1;
+ false ->
+ ArgTypes2 = subst_all_vars_to_any_list(ArgTypes),
+ t_opaque(Mod, Name, ArgTypes2, NewRep1)
+ end,
+ {NewRep2, L2, C2}
+ end,
+ C4 = cache_put(CKey, NewType, L1 - L3, C3),
+ {NewType, L3, C4}
+ end;
+ error ->
+ Msg = io_lib:format("Unable to find remote type ~w:~w()\n",
+ [RemMod, Name]),
+ throw({error, Msg})
+ end.
+
subst_all_vars_to_any_list(Types) ->
[subst_all_vars_to_any(Type) || Type <- Types].
@@ -4836,63 +4859,67 @@ choose_opaque_type(Type, DeclType) ->
false -> DeclType
end.
-record_from_form({atom, _, Name}, ModFields, TypeNames, ET, S, MR, V, D, L) ->
- case can_unfold_more({record, Name}, TypeNames) of
+record_from_form({atom, _, Name}, ModFields, S, D0, L0, C) ->
+ #from_form{site = Site, mrecs = MR, tnames = TypeNames} = S,
+ RecordType = {record, Name},
+ case can_unfold_more(RecordType, TypeNames) of
true ->
- M = site_module(S),
+ M = site_module(Site),
{ok, R} = dict:find(M, MR),
case lookup_record(Name, R) of
{ok, DeclFields} ->
- NewTypeNames = [{record, Name}|TypeNames],
- S1 = {record, {M, Name, length(DeclFields)}},
- {GetModRec, L1} = get_mod_record(ModFields, DeclFields,
- NewTypeNames, ET, S1, MR, V, D, L),
- case GetModRec of
- {error, FieldName} ->
- throw({error, io_lib:format("Illegal declaration of #~w{~w}\n",
- [Name, FieldName])});
- {ok, NewFields} ->
- {NewFields1, L2} =
- fields_from_form(NewFields, NewTypeNames, ET, S1, MR,
- maps:new(), D, L1),
- Rec = t_tuple(
- [t_atom(Name)|[Type
- || {_FieldName, Type} <- NewFields1]]),
- {Rec, L2}
- end;
+ NewTypeNames = [RecordType|TypeNames],
+ Site1 = {record, {M, Name, length(DeclFields)}},
+ S1 = S#from_form{site = Site1, tnames = NewTypeNames},
+ Fun = fun(D, L) ->
+ {GetModRec, L1, C1} =
+ get_mod_record(ModFields, DeclFields, S1, D, L, C),
+ case GetModRec of
+ {error, FieldName} ->
+ throw({error,
+ io_lib:format("Illegal declaration of #~w{~w}\n",
+ [Name, FieldName])});
+ {ok, NewFields} ->
+ S2 = S1#from_form{vtab = var_table__new()},
+ {NewFields1, L2, C2} =
+ fields_from_form(NewFields, S2, D, L1, C1),
+ Rec = t_tuple(
+ [t_atom(Name)|[Type
+ || {_FieldName, Type} <- NewFields1]]),
+ {Rec, L2, C2}
+ end
+ end,
+ recur_limit(Fun, D0, L0, RecordType, TypeNames);
error ->
throw({error, io_lib:format("Unknown record #~w{}\n", [Name])})
end;
false ->
- {t_any(), L}
+ {t_any(), L0, C}
end.
-get_mod_record([], DeclFields, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {{ok, DeclFields}, L};
-get_mod_record(ModFields, DeclFields, TypeNames, ET, S, MR, V, D, L) ->
+get_mod_record([], DeclFields, _S, _D, L, C) ->
+ {{ok, DeclFields}, L, C};
+get_mod_record(ModFields, DeclFields, S, D, L, C) ->
DeclFieldsDict = lists:keysort(1, DeclFields),
- {ModFieldsDict, L1} =
- build_field_dict(ModFields, TypeNames, ET, S, MR, V, D, L),
+ {ModFieldsDict, L1, C1} = build_field_dict(ModFields, S, D, L, C),
case get_mod_record_types(DeclFieldsDict, ModFieldsDict, []) of
- {error, _FieldName} = Error -> {Error, L1};
+ {error, _FieldName} = Error -> {Error, L1, C1};
{ok, FinalKeyDict} ->
Fields = [lists:keyfind(FieldName, 1, FinalKeyDict)
|| {FieldName, _, _} <- DeclFields],
- {{ok, Fields}, L1}
+ {{ok, Fields}, L1, C1}
end.
-build_field_dict(FieldTypes, TypeNames, ET, S, MR, V, D, L) ->
- build_field_dict(FieldTypes, TypeNames, ET, S, MR, V, D, L, []).
+build_field_dict(FieldTypes, S, D, L, C) ->
+ build_field_dict(FieldTypes, S, D, L, C, []).
build_field_dict([{type, _, field_type, [{atom, _, Name}, Type]}|Left],
- TypeNames, ET, S, MR, V, D, L, Acc) ->
- {T, L1} = t_from_form(Type, TypeNames, ET, S, MR, V, D, L - 1),
+ S, D, L, C, Acc) ->
+ {T, L1, C1} = from_form(Type, S, D, L - 1, C),
NewAcc = [{Name, Type, T}|Acc],
- {Dict, L2} =
- build_field_dict(Left, TypeNames, ET, S, MR, V, D, L1, NewAcc),
- {Dict, L2};
-build_field_dict([], _TypeNames, _ET, _S, _MR, _V, _D, L, Acc) ->
- {lists:keysort(1, Acc), L}.
+ build_field_dict(Left, S, D, L1, C1, NewAcc);
+build_field_dict([], _S, _D, L, C, Acc) ->
+ {lists:keysort(1, Acc), L, C}.
get_mod_record_types([{FieldName, _Abstr, _DeclType}|Left1],
[{FieldName, TypeForm, ModType}|Left2],
@@ -4910,20 +4937,19 @@ get_mod_record_types(_, [{FieldName2, _FormType, _ModType}|_], _Acc) ->
%% It is important to create a limited version of the record type
%% since nested record types can otherwise easily result in huge
%% terms.
-fields_from_form([], _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {[], L};
-fields_from_form([{Name, Abstr, _Type}|Tail], TypeNames, ET, S, MR,
- V, D, L) ->
- {T, L1} = t_from_form(Abstr, TypeNames, ET, S, MR, V, D, L),
- {F, L2} = fields_from_form(Tail, TypeNames, ET, S, MR, V, D, L1),
- {[{Name, T}|F], L2}.
-
-list_from_form([], _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {[], L};
-list_from_form([H|Tail], TypeNames, ET, S, MR, V, D, L) ->
- {H1, L1} = t_from_form(H, TypeNames, ET, S, MR, V, D, L - 1),
- {T1, L2} = list_from_form(Tail, TypeNames, ET, S, MR, V, D, L1),
- {[H1|T1], L2}.
+fields_from_form([], _S, _D, L, C) ->
+ {[], L, C};
+fields_from_form([{Name, Abstr, _Type}|Tail], S, D, L, C) ->
+ {T, L1, C1} = from_form(Abstr, S, D, L, C),
+ {F, L2, C2} = fields_from_form(Tail, S, D, L1, C1),
+ {[{Name, T}|F], L2, C2}.
+
+list_from_form([], _S, _D, L, C) ->
+ {[], L, C};
+list_from_form([H|Tail], S, D, L, C) ->
+ {H1, L1, C1} = from_form(H, S, D, L - 1, C),
+ {T1, L2, C2} = list_from_form(Tail, S, D, L1, C1),
+ {[H1|T1], L2, C2}.
%% Sorts, combines non-singleton pairs, and applies precendence and
%% mandatoriness rules.
@@ -4969,80 +4995,140 @@ promote_to_mand(MKs, [E={K,_,V}|T]) ->
false -> E
end|promote_to_mand(MKs, T)].
--spec t_check_record_fields(parse_form(), sets:set(mfa()), site(),
- mod_records()) -> ok.
+-define(RECUR_EXPAND_LIMIT, 10).
+-define(RECUR_EXPAND_DEPTH, 2).
-t_check_record_fields(Form, ExpTypes, Site, RecDict) ->
- t_check_record_fields(Form, ExpTypes, Site, RecDict, maps:new()).
+%% If more of the limited resources is spent on the non-recursive
+%% forms, more warnings are found. And the analysis is also a bit
+%% faster.
+%%
+%% Setting REC_TYPE_LIMIT to 1 would work also work well.
+
+recur_limit(Fun, D, L, _, _) when L =< ?RECUR_EXPAND_DEPTH,
+ D =< ?RECUR_EXPAND_LIMIT ->
+ Fun(D, L);
+recur_limit(Fun, D, L, TypeName, TypeNames) ->
+ case is_recursive(TypeName, TypeNames) of
+ true ->
+ {T, L1, C1} = Fun(?RECUR_EXPAND_DEPTH, ?RECUR_EXPAND_LIMIT),
+ {T, L - L1, C1};
+ false ->
+ Fun(D, L)
+ end.
-spec t_check_record_fields(parse_form(), sets:set(mfa()), site(),
- mod_records(), var_table()) -> ok.
+ mod_records(), var_table(), cache()) -> cache().
+
+t_check_record_fields(Form, ExpTypes, Site, RecDict, VarTable, Cache) ->
+ State = #from_form{site = Site,
+ xtypes = ExpTypes,
+ mrecs = RecDict,
+ vtab = VarTable,
+ tnames = []},
+ check_record_fields(Form, State, Cache).
+
+-spec check_record_fields(parse_form(), #from_form{}, cache()) -> cache().
%% If there is something wrong with parse_form()
%% throw({error, io_lib:chars()} is called.
-t_check_record_fields({var, _L, _}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({ann_type, _L, [_Var, Type]}, ET, S, MR, V) ->
- t_check_record_fields(Type, ET, S, MR, V);
-t_check_record_fields({paren_type, _L, [Type]}, ET, S, MR, V) ->
- t_check_record_fields(Type, ET, S, MR, V);
-t_check_record_fields({remote_type, _L, [{atom, _, _}, {atom, _, _}, Args]},
- ET, S, MR, V) ->
- list_check_record_fields(Args, ET, S, MR, V);
-t_check_record_fields({atom, _L, _}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({integer, _L, _}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({op, _L, _Op, _Arg}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({op, _L, _Op, _Arg1, _Arg2}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({type, _L, tuple, any}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({type, _L, map, any}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({type, _L, binary, [_Base, _Unit]}, _ET, _S, _MR, _V) ->
- ok;
-t_check_record_fields({type, _L, 'fun', [{type, _, any}, Range]},
- ET, S, MR, V) ->
- t_check_record_fields(Range, ET, S, MR, V);
-t_check_record_fields({type, _L, range, [_From, _To]}, _ET, _S, _MR, _V) ->
- ok;
-t_check_record_fields({type, _L, record, [Name|Fields]}, ET, S, MR, V) ->
- check_record(Name, Fields, ET, S, MR, V);
-t_check_record_fields({type, _L, _, Args}, ET, S, MR, V) ->
- list_check_record_fields(Args, ET, S, MR, V);
-t_check_record_fields({user_type, _L, _Name, Args}, ET, S, MR, V) ->
- list_check_record_fields(Args, ET, S, MR, V).
-
-check_record({atom, _, Name}, ModFields, ET, Site, MR, V) ->
+check_record_fields({var, _L, _}, _S, C) -> C;
+check_record_fields({ann_type, _L, [_Var, Type]}, S, C) ->
+ check_record_fields(Type, S, C);
+check_record_fields({paren_type, _L, [Type]}, S, C) ->
+ check_record_fields(Type, S, C);
+check_record_fields({remote_type, _L, [{atom, _, _}, {atom, _, _}, Args]},
+ S, C) ->
+ list_check_record_fields(Args, S, C);
+check_record_fields({atom, _L, _}, _S, C) -> C;
+check_record_fields({integer, _L, _}, _S, C) -> C;
+check_record_fields({op, _L, _Op, _Arg}, _S, C) -> C;
+check_record_fields({op, _L, _Op, _Arg1, _Arg2}, _S, C) -> C;
+check_record_fields({type, _L, tuple, any}, _S, C) -> C;
+check_record_fields({type, _L, map, any}, _S, C) -> C;
+check_record_fields({type, _L, binary, [_Base, _Unit]}, _S, C) -> C;
+check_record_fields({type, _L, 'fun', [{type, _, any}, Range]}, S, C) ->
+ check_record_fields(Range, S, C);
+check_record_fields({type, _L, range, [_From, _To]}, _S, C) -> C;
+check_record_fields({type, _L, record, [Name|Fields]}, S, C) ->
+ check_record(Name, Fields, S, C);
+check_record_fields({type, _L, _, Args}, S, C) ->
+ list_check_record_fields(Args, S, C);
+check_record_fields({user_type, _L, _Name, Args}, S, C) ->
+ list_check_record_fields(Args, S, C).
+
+check_record({atom, _, Name}, ModFields, S, C) ->
+ #from_form{site = Site, mrecs = MR} = S,
M = site_module(Site),
{ok, R} = dict:find(M, MR),
{ok, DeclFields} = lookup_record(Name, R),
- case check_fields(Name, ModFields, DeclFields, ET, Site, MR, V) of
+ case check_fields(Name, ModFields, DeclFields, S, C) of
{error, FieldName} ->
throw({error, io_lib:format("Illegal declaration of #~w{~w}\n",
[Name, FieldName])});
- ok -> ok
+ C1 -> C1
end.
check_fields(RecName, [{type, _, field_type, [{atom, _, Name}, Abstr]}|Left],
- DeclFields, ET, Site0, MR, V) ->
+ DeclFields, S, C) ->
+ #from_form{site = Site0, xtypes = ET, mrecs = MR, vtab = V} = S,
M = site_module(Site0),
Site = {record, {M, RecName, length(DeclFields)}},
- Type = t_from_form(Abstr, ET, Site, MR, V),
+ {Type, C1} = t_from_form(Abstr, ET, Site, MR, V, C),
{Name, _, DeclType} = lists:keyfind(Name, 1, DeclFields),
TypeNoVars = subst_all_vars_to_any(Type),
case t_is_subtype(TypeNoVars, DeclType) of
false -> {error, Name};
- true -> check_fields(RecName, Left, DeclFields, ET, Site0, MR, V)
+ true -> check_fields(RecName, Left, DeclFields, S, C1)
end;
-check_fields(_RecName, [], _Decl, _ET, _Site, _MR, _V) ->
- ok.
+check_fields(_RecName, [], _Decl, _S, C) ->
+ C.
-list_check_record_fields([], _ET, _S, _MR, _V) ->
- ok;
-list_check_record_fields([H|Tail], ET, S, MR, V) ->
- ok = t_check_record_fields(H, ET, S, MR, V),
- list_check_record_fields(Tail, ET, S, MR, V).
+list_check_record_fields([], _S, C) ->
+ C;
+list_check_record_fields([H|Tail], S, C) ->
+ C1 = check_record_fields(H, S, C),
+ list_check_record_fields(Tail, S, C1).
site_module({_, {Module, _, _}}) ->
Module.
+-spec cache__new() -> cache().
+
+cache__new() ->
+ maps:new().
+
+-spec cache_key(module(), atom(), [erl_type()],
+ type_names(), expand_depth()) -> cache_key().
+
+%% If TypeNames is left out from the key, the cache is smaller, and
+%% the form-to-type translation is faster. But it would be a shame if,
+%% for example, any() is used, where a more complex type should be
+%% used. There is also a slight risk of creating unnecessarily big
+%% types.
+
+cache_key(Module, Name, ArgTypes, TypeNames, D) ->
+ {Module, Name, D, ArgTypes, TypeNames}.
+
+-spec cache_find(cache_key(), cache()) ->
+ {erl_type(), expand_limit()} | 'error'.
+
+cache_find(Key, Cache) ->
+ case maps:find(Key, Cache) of
+ {ok, Value} ->
+ Value;
+ error ->
+ error
+ end.
+
+-spec cache_put(cache_key(), erl_type(), expand_limit(), cache()) -> cache().
+
+cache_put(_Key, _Type, DeltaL, Cache) when DeltaL < 0 ->
+ %% The type is truncated; do not reuse it.
+ Cache;
+cache_put(Key, Type, DeltaL, Cache) ->
+ maps:put(Key, {Type, DeltaL}, Cache).
+
-spec t_var_names([erl_type()]) -> [atom()].
t_var_names([{var, _, Name}|L]) when L =/= '_' ->
@@ -5141,10 +5227,15 @@ t_form_to_string({type, _L, Name, []} = T) ->
M = mod,
D0 = dict:new(),
MR = dict:from_list([{M, D0}]),
- S = {type, {M,Name,0}},
- V = #{},
- {T1, _} =
- t_from_form(T, [], sets:new(), S, MR, V, _Deep=1000, _ALot=100000),
+ Site = {type, {M,Name,0}},
+ V = var_table__new(),
+ C = cache__new(),
+ State = #from_form{site = Site,
+ xtypes = sets:new(),
+ mrecs = MR,
+ vtab = V,
+ tnames = []},
+ {T1, _, _} = from_form(T, State, _Deep=1000, _ALot=1000000, C),
t_to_string(T1)
catch throw:{error, _} -> atom_to_string(Name) ++ "()"
end;
@@ -5220,6 +5311,7 @@ lookup_record(Tag, Arity, RecDict) when is_atom(Tag) ->
error -> error
end.
+-spec lookup_type(_, _, _) -> {'type' | 'opaque', type_value()} | 'error'.
lookup_type(Name, Arity, RecDict) ->
case dict:find({type, Name, Arity}, RecDict) of
error ->
diff --git a/lib/inets/test/ftp_SUITE.erl b/lib/inets/test/ftp_SUITE.erl
index a8d39e3fe7..e2dec0c42a 100644
--- a/lib/inets/test/ftp_SUITE.erl
+++ b/lib/inets/test/ftp_SUITE.erl
@@ -114,6 +114,7 @@ ftp_tests()->
%%% ftpservers list of servers to check if they are available
%%% The element is:
%%% {Name, % string(). The os command name
+%%% Path, % string(). The os PATH syntax, e.g "/bin:/usr/bin"
%%% StartCommand, % fun()->{ok,start_result()} | {error,string()}.
%%% % The command to start the daemon with.
%%% ChkUp, % fun(start_result()) -> string(). Os command to check
@@ -129,12 +130,13 @@ ftp_tests()->
-define(default_ftp_servers,
[{"vsftpd",
- fun(__CONF__) ->
+ "/sbin:/usr/sbin:/usr/local/sbin",
+ fun(__CONF__, AbsName) ->
DataDir = proplists:get_value(data_dir,__CONF__),
ConfFile = filename:join(DataDir, "vsftpd.conf"),
PrivDir = proplists:get_value(priv_dir,__CONF__),
AnonRoot = PrivDir,
- Cmd = ["vsftpd "++filename:join(DataDir,"vsftpd.conf"),
+ Cmd = [AbsName ++" "++filename:join(DataDir,"vsftpd.conf"),
" -oftpd_banner=erlang_otp_testing",
" -oanon_root=\"",AnonRoot,"\"",
" -orsa_cert_file=\"",filename:join(DataDir,"server-cert.pem"),"\"",
@@ -856,28 +858,51 @@ chk_no_dir(PathList, Config) ->
%%--------------------------------------------------------------------
find_executable(Config) ->
- FTPservers = case proplists:get_value(ftpservers,Config) of
- undefined -> ?default_ftp_servers;
- L -> L
- end,
- case lists:dropwhile(fun not_available/1, FTPservers) of
- [] -> false;
- [FTPD_data|_] -> {ok, FTPD_data}
- end.
+ search_executable(proplists:get_value(ftpservers, Config, ?default_ftp_servers)).
+
+
+search_executable([{Name,Paths,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}|Srvrs]) ->
+ case os_find(Name,Paths) of
+ false ->
+ ct:log("~p not found",[Name]),
+ search_executable(Srvrs);
+ AbsName ->
+ ct:comment("Found ~p",[AbsName]),
+ {ok, {AbsName,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}}
+ end;
+search_executable([]) ->
+ false.
-not_available({Name,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}) ->
- os:find_executable(Name) == false.
+os_find(Name, Paths) ->
+ case os:find_executable(Name, Paths) of
+ false -> os:find_executable(Name);
+ AbsName -> AbsName
+ end.
-start_ftpd(Config) ->
- {Name,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} = proplists:get_value(ftpd_data, Config),
- case StartCmd(Config) of
+%%%----------------------------------------------------------------
+start_ftpd(Config0) ->
+ {AbsName,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} =
+ proplists:get_value(ftpd_data, Config0),
+ case StartCmd(Config0, AbsName) of
{ok,StartResult} ->
- [{ftpd_host,Host},
- {ftpd_port,Port},
- {ftpd_start_result,StartResult} | ConfigRewrite(Config)];
+ Config = [{ftpd_host,Host},
+ {ftpd_port,Port},
+ {ftpd_start_result,StartResult} | ConfigRewrite(Config0)],
+ try
+ ftp__close(ftp__open(Config,[verbose]))
+ of
+ Config1 when is_list(Config1) ->
+ ct:log("Usuable ftp server ~p started on ~p:~p",[AbsName,Host,Port]),
+ Config
+ catch
+ Class:Exception ->
+ ct:log("Ftp server ~p started on ~p:~p but is unusable:~n~p:~p",
+ [AbsName,Host,Port,Class,Exception]),
+ {skip, [AbsName," started but unusuable"]}
+ end;
{error,Msg} ->
- {skip, [Name," not started: ",Msg]}
+ {skip, [AbsName," not started: ",Msg]}
end.
stop_ftpd(Config) ->
diff --git a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
index ba0746e736..37389ce5ae 100644
--- a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
@@ -317,7 +317,7 @@
sex = male,
phone = 98108,
room_no = {221, 015}},
- insert_emp(Me, 'B/SFR', [Erlang, mnesia, otp]).</code>
+ insert_emp(Emp, 'B/SFR', [Erlang, mnesia, otp]).</code>
<note><p>For information about Funs, see "Fun Expressions" in
section <c>Erlang Reference Manual</c> in System
Documentation..</p>
diff --git a/lib/stdlib/doc/src/erl_id_trans.xml b/lib/stdlib/doc/src/erl_id_trans.xml
index 153bd4148e..649490f8b3 100644
--- a/lib/stdlib/doc/src/erl_id_trans.xml
+++ b/lib/stdlib/doc/src/erl_id_trans.xml
@@ -49,7 +49,8 @@
<name>parse_transform(Forms, Options) -> Forms</name>
<fsummary>Transform Erlang forms</fsummary>
<type>
- <v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso>]</v>
+ <v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso>
+ | <seealso marker="erl_parse#type-form_info">erl_parse:form_info()</seealso>]</v>
<v>Options = [<seealso marker="compile#type-option">compile:option()</seealso>]</v>
</type>
<desc>
diff --git a/lib/stdlib/doc/src/erl_parse.xml b/lib/stdlib/doc/src/erl_parse.xml
index 32fce16d68..771ccc2dc6 100644
--- a/lib/stdlib/doc/src/erl_parse.xml
+++ b/lib/stdlib/doc/src/erl_parse.xml
@@ -73,6 +73,14 @@
<name name="error_info"></name>
</datatype>
<datatype>
+ <name name="form_info"></name>
+ <desc><p>Tuples <c>{error, error_info()}</c> and <c>{warning,
+ error_info()}</c>, denoting syntactically incorrect forms and
+ warnings, and <c>{eof, line()}</c>, denoting an end-of-stream
+ encountered before a complete form had been parsed.</p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="token"></name>
</datatype>
</datatypes>
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
index 9c0a7fb7d5..ebcbc54ab1 100644
--- a/lib/stdlib/src/erl_expand_records.erl
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. 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.
@@ -37,8 +37,9 @@
checked_ra=[] % successfully accessed records
}).
--spec(module(AbsForms, CompileOptions) -> AbsForms when
+-spec(module(AbsForms, CompileOptions) -> AbsForms2 when
AbsForms :: [erl_parse:abstract_form()],
+ AbsForms2 :: [erl_parse:abstract_form()],
CompileOptions :: [compile:option()]).
%% Is is assumed that Fs is a valid list of forms. It should pass
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 2508f96b91..e9332ce069 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -99,7 +99,7 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
module='', %Module
behaviour=[], %Behaviour
exports=gb_sets:empty() :: gb_sets:set(fa()),%Exports
- imports=[] :: [fa()], %Imports, an orddict()
+ imports=[] :: orddict:orddict(fa(), module()),%Imports
compile=[], %Compile flags
records=dict:new() %Record definitions
:: dict:dict(atom(), {line(),Fields :: term()}),
@@ -467,7 +467,7 @@ used_vars(Exprs, BindingsList) ->
%% really all ordsets!
-spec(module(AbsForms) -> {ok, Warnings} | {error, Errors, Warnings} when
- AbsForms :: [erl_parse:abstract_form()],
+ AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()],
Warnings :: [{file:filename(),[ErrorInfo]}],
Errors :: [{FileName2 :: file:filename(),[ErrorInfo]}],
ErrorInfo :: error_info()).
@@ -479,7 +479,7 @@ module(Forms) ->
-spec(module(AbsForms, FileName) ->
{ok, Warnings} | {error, Errors, Warnings} when
- AbsForms :: [erl_parse:abstract_form()],
+ AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()],
FileName :: atom() | string(),
Warnings :: [{file:filename(),[ErrorInfo]}],
Errors :: [{FileName2 :: file:filename(),[ErrorInfo]}],
@@ -492,7 +492,7 @@ module(Forms, FileName) ->
-spec(module(AbsForms, FileName, CompileOptions) ->
{ok, Warnings} | {error, Errors, Warnings} when
- AbsForms :: [erl_parse:abstract_form()],
+ AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()],
FileName :: atom() | string(),
CompileOptions :: [compile:option()],
Warnings :: [{file:filename(),[ErrorInfo]}],
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index a896de4f1c..85b2816451 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -170,9 +170,6 @@ fun_type -> '(' top_types ')' '->' top_type
: {type, ?anno('$1'), 'fun',
[{type, ?anno('$1'), product, '$2'},'$5']}.
-map_pair_types -> '...' : [{type, ?anno('$1'), map_field_assoc,
- [{type, ?anno('$1'), any, []},
- {type, ?anno('$1'), any, []}]}].
map_pair_types -> map_pair_type : ['$1'].
map_pair_types -> map_pair_type ',' map_pair_types : ['$1'|'$3'].
@@ -534,7 +531,7 @@ Erlang code.
-compile([{hipe,[{regalloc,linear_scan}]}]).
-export_type([abstract_clause/0, abstract_expr/0, abstract_form/0,
- abstract_type/0, error_info/0]).
+ abstract_type/0, form_info/0, error_info/0]).
%% Start of Abstract Format
@@ -546,7 +543,6 @@ Erlang code.
| af_export()
| af_import()
| af_export_type()
- | af_optional_callbacks()
| af_compile()
| af_file()
| af_record_decl()
@@ -573,9 +569,6 @@ Erlang code.
-type af_ta_list() :: [{type_name(), arity()}].
--type af_optional_callbacks() ::
- {'attribute', anno(), 'optional_callbacks', af_fa_list()}.
-
-type af_compile() :: {'attribute', anno(), 'compile', any()}.
-type af_file() :: {'attribute', anno(), 'file', {string(), anno()}}.
@@ -867,16 +860,22 @@ Erlang code.
| af_unary_op(af_singleton_integer_type())
| af_binary_op(af_singleton_integer_type()).
--type af_literal() :: af_atom() | af_integer() | af_float() | af_string().
+-type af_literal() :: af_atom()
+ | af_character()
+ | af_float()
+ | af_integer()
+ | af_string().
-type af_atom() :: af_lit_atom(atom()).
-type af_lit_atom(A) :: {'atom', anno(), A}.
--type af_integer() :: {'integer', anno(), non_neg_integer()}.
+-type af_character() :: {'char', anno(), char()}.
-type af_float() :: {'float', anno(), float()}.
+-type af_integer() :: {'integer', anno(), non_neg_integer()}.
+
-type af_string() :: {'string', anno(), string()}.
-type af_match(T) :: {'match', anno(), af_pattern(), T}.
@@ -944,6 +943,10 @@ Erlang code.
-type type_name() :: atom().
+-type form_info() :: {'eof', erl_anno:line()}
+ | {'error', erl_scan:error_info() | error_info()}
+ | {'warning', erl_scan:error_info() | error_info()}.
+
%% End of Abstract Format
%% XXX. To be refined.
@@ -1503,8 +1506,9 @@ type_preop_prec('#') -> {700,800}.
| abstract_type().
-spec map_anno(Fun, Abstr) -> NewAbstr when
- Fun :: fun((Anno) -> Anno),
+ Fun :: fun((Anno) -> NewAnno),
Anno :: erl_anno:anno(),
+ NewAnno :: erl_anno:anno(),
Abstr :: erl_parse_tree(),
NewAbstr :: erl_parse_tree().
@@ -1513,14 +1517,14 @@ map_anno(F0, Abstr) ->
{NewAbstr, []} = modify_anno1(Abstr, [], F),
NewAbstr.
--spec fold_anno(Fun, Acc0, Abstr) -> NewAbstr when
+-spec fold_anno(Fun, Acc0, Abstr) -> Acc1 when
Fun :: fun((Anno, AccIn) -> AccOut),
Anno :: erl_anno:anno(),
Acc0 :: term(),
+ Acc1 :: term(),
AccIn :: term(),
AccOut :: term(),
- Abstr :: erl_parse_tree(),
- NewAbstr :: erl_parse_tree().
+ Abstr :: erl_parse_tree().
fold_anno(F0, Acc0, Abstr) ->
F = fun(A, Acc) -> {A, F0(A, Acc)} end,
@@ -1528,8 +1532,9 @@ fold_anno(F0, Acc0, Abstr) ->
NewAcc.
-spec mapfold_anno(Fun, Acc0, Abstr) -> {NewAbstr, Acc1} when
- Fun :: fun((Anno, AccIn) -> {Anno, AccOut}),
+ Fun :: fun((Anno, AccIn) -> {NewAnno, AccOut}),
Anno :: erl_anno:anno(),
+ NewAnno :: erl_anno:anno(),
Acc0 :: term(),
Acc1 :: term(),
AccIn :: term(),
@@ -1545,7 +1550,9 @@ mapfold_anno(F, Acc0, Abstr) ->
Abstr :: erl_parse_tree().
new_anno(Term) ->
- map_anno(fun erl_anno:new/1, Term).
+ F = fun(L, Acc) -> {erl_anno:new(L), Acc} end,
+ {NewAbstr, []} = modify_anno1(Term, [], F),
+ NewAbstr.
-spec anno_to_term(Abstr) -> term() when
Abstr :: erl_parse_tree().
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index ca764675fc..016962f538 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -70,13 +70,13 @@
%%%
-spec(form(Form) -> io_lib:chars() when
- Form :: erl_parse:abstract_form()).
+ Form :: erl_parse:abstract_form() | erl_parse:form_info()).
form(Thing) ->
form(Thing, none).
-spec(form(Form, Options) -> io_lib:chars() when
- Form :: erl_parse:abstract_form(),
+ Form :: erl_parse:abstract_form() | erl_parse:form_info(),
Options :: options()).
form(Thing, Options) ->
@@ -344,27 +344,9 @@ binary_type(I1, I2) ->
map_type(Fs) ->
{first,[$#],map_pair_types(Fs)}.
-map_pair_types(Fs0) ->
- Fs = replace_any_map(Fs0),
+map_pair_types(Fs) ->
tuple_type(Fs, fun map_pair_type/2).
-replace_any_map([{type,Line,map_field_assoc,[KType,VType]}]=Fs) ->
- IsAny = fun({type,_,any,[]}) -> true;
- %% ({var,_,'_'}) -> true;
- (_) -> false
- end,
- case IsAny(KType) andalso IsAny(VType) of
- true ->
- [{type,Line,map_field_assoc,any}];
- false ->
- Fs
- end;
-replace_any_map([F|Fs]) ->
- [F|replace_any_map(Fs)];
-replace_any_map([]) -> [].
-
-map_pair_type({type,_Line,map_field_assoc,any}, _Prec) ->
- leaf("...");
map_pair_type({type,_Line,map_field_assoc,[KType,VType]}, Prec) ->
{list,[{cstep,[ltype(KType, Prec),leaf(" =>")],ltype(VType, Prec)}]};
map_pair_type({type,_Line,map_field_exact,[KType,VType]}, Prec) ->
diff --git a/lib/stdlib/src/lib.erl b/lib/stdlib/src/lib.erl
index 6fba63a895..56654097d9 100644
--- a/lib/stdlib/src/lib.erl
+++ b/lib/stdlib/src/lib.erl
@@ -73,7 +73,7 @@ nonl([H|T]) -> [H|nonl(T)].
send(To, Msg) -> To ! Msg.
--spec sendw(To, Msg) -> Msg when
+-spec sendw(To, Msg) -> term() when
To :: pid() | atom() | {atom(), node()},
Msg :: term().
diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
index 24b5fde1db..c0eea652e7 100644
--- a/lib/stdlib/src/ms_transform.erl
+++ b/lib/stdlib/src/ms_transform.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. 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.
@@ -224,8 +224,9 @@ transform_from_shell(Dialect, Clauses, BoundEnvironment) ->
%% Called when translating during compiling
%%
--spec parse_transform(Forms, Options) -> Forms when
- Forms :: [erl_parse:abstract_form()],
+-spec parse_transform(Forms, Options) -> Forms2 when
+ Forms :: [erl_parse:abstract_form() | erl_parse:form_info()],
+ Forms2 :: [erl_parse:abstract_form() | erl_parse:form_info()],
Options :: term().
parse_transform(Forms, _Options) ->
diff --git a/lib/stdlib/src/proplists.erl b/lib/stdlib/src/proplists.erl
index 8e99ec0ed9..5356467b19 100644
--- a/lib/stdlib/src/proplists.erl
+++ b/lib/stdlib/src/proplists.erl
@@ -438,8 +438,9 @@ substitute_aliases_1([], P) ->
%% @see normalize/2
-spec substitute_negations(Negations, ListIn) -> ListOut when
- Negations :: [{Key, Key}],
- Key :: term(),
+ Negations :: [{Key1, Key2}],
+ Key1 :: term(),
+ Key2 :: term(),
ListIn :: [term()],
ListOut :: [term()].
diff --git a/lib/stdlib/src/qlc.erl b/lib/stdlib/src/qlc.erl
index b396ba7057..f3665824f2 100644
--- a/lib/stdlib/src/qlc.erl
+++ b/lib/stdlib/src/qlc.erl
@@ -734,10 +734,11 @@ table(TraverseFun, Options) when is_function(TraverseFun) ->
table(T1, T2) ->
erlang:error(badarg, [T1, T2]).
--spec(transform_from_evaluator(LC, Bs) -> Expr when
+-spec(transform_from_evaluator(LC, Bs) -> Return when
LC :: abstract_expr(),
- Expr :: abstract_expr(),
- Bs :: erl_eval:binding_struct()).
+ Bs :: erl_eval:binding_struct(),
+ Return :: {ok, abstract_expr()}
+ | {not_ok, {error, module(), Reason :: term()}}).
transform_from_evaluator(LC, Bs0) ->
qlc_pt:transform_from_evaluator(LC, Bs0).
diff --git a/lib/stdlib/src/qlc_pt.erl b/lib/stdlib/src/qlc_pt.erl
index e4b9768b12..0db63b81f4 100644
--- a/lib/stdlib/src/qlc_pt.erl
+++ b/lib/stdlib/src/qlc_pt.erl
@@ -67,8 +67,8 @@
%%%
-spec(parse_transform(Forms, Options) -> Forms2 when
- Forms :: [erl_parse:abstract_form()],
- Forms2 :: [erl_parse:abstract_form()],
+ Forms :: [erl_parse:abstract_form() | erl_parse:form_info()],
+ Forms2 :: [erl_parse:abstract_form() | erl_parse:form_info()],
Options :: [Option],
Option :: type_checker | compile:option()).
@@ -117,19 +117,21 @@ parse_transform(Forms0, Options) ->
true = ets:delete(NodeInfo)
end.
--spec(transform_from_evaluator(LC, Bs) -> Expr when
+-spec(transform_from_evaluator(LC, Bs) -> Return when
LC :: erl_parse:abstract_expr(),
- Expr :: erl_parse:abstract_expr(),
- Bs :: erl_eval:binding_struct()).
+ Bs :: erl_eval:binding_struct(),
+ Return :: {ok, erl_parse:abstract_expr()}
+ | {not_ok, {error, module(), Reason :: term()}}).
transform_from_evaluator(LC, Bindings) ->
?DEBUG("qlc Parse Transform (Evaluator Version)~n", []),
transform_expression(LC, Bindings, false).
--spec(transform_expression(LC, Bs) -> Expr when
+-spec(transform_expression(LC, Bs) -> Return when
LC :: erl_parse:abstract_expr(),
- Expr :: erl_parse:abstract_expr(),
- Bs :: erl_eval:binding_struct()).
+ Bs :: erl_eval:binding_struct(),
+ Return :: {ok, erl_parse:abstract_expr()}
+ | {not_ok, [{error, Reason :: term()}]}).
transform_expression(LC, Bindings) ->
transform_expression(LC, Bindings, true).
diff --git a/lib/stdlib/src/sofs.erl b/lib/stdlib/src/sofs.erl
index b18df2ad09..c244e06ca4 100644
--- a/lib/stdlib/src/sofs.erl
+++ b/lib/stdlib/src/sofs.erl
@@ -621,6 +621,9 @@ canonical_relation(Sets) when ?IS_SET(Sets) ->
%%% Functions on binary relations only.
%%%
+-spec(rel2fam(BinRel) -> Family when
+ Family :: family(),
+ BinRel :: binary_relation()).
rel2fam(R) ->
relation_to_family(R).
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index a48ba7b5b7..951a17d3eb 100644
--- a/lib/stdlib/test/erl_pp_SUITE.erl
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -1112,15 +1112,14 @@ pr_1014(Config) ->
ok = pp_forms(<<"-type t() :: #{any() => _}. ">>),
ok = pp_forms(<<"-type t() :: #{_ => any()}. ">>),
ok = pp_forms(<<"-type t() :: #{any() => any()}. ">>),
- ok = pp_forms(<<"-type t() :: #{...}. ">>),
- ok = pp_forms(<<"-type t() :: #{atom() := integer(), ...}. ">>),
+ ok = pp_forms(<<"-type t() :: #{atom() := integer(), any() => any()}. ">>),
FileName = filename('pr_1014.erl', Config),
C = <<"-module pr_1014.\n"
"-compile export_all.\n"
"-type m() :: #{..., a := integer()}.\n">>,
ok = file:write_file(FileName, C),
- {error,[{_,[{3,erl_parse,["syntax error before: ","','"]}]}],_} =
+ {error,[{_,[{3,erl_parse,["syntax error before: ","'...'"]}]}],_} =
compile:file(FileName, [return]),
ok.
diff --git a/lib/syntax_tools/src/Makefile b/lib/syntax_tools/src/Makefile
index 2e91adf8af..8325db45a8 100644
--- a/lib/syntax_tools/src/Makefile
+++ b/lib/syntax_tools/src/Makefile
@@ -29,7 +29,7 @@ ERL_COMPILE_FLAGS += -pa $(EBIN) -pa ./ -I$(INCLUDE)
ifeq ($(NATIVE_LIBS_ENABLED),yes)
ERL_COMPILE_FLAGS += +native
endif
-ERL_COMPILE_FLAGS += +nowarn_shadow_vars +warn_unused_import -Werror # +warn_missing_spec +warn_untyped_record
+ERL_COMPILE_FLAGS += +nowarn_shadow_vars +warn_unused_import #-Werror # +warn_missing_spec +warn_untyped_record
SOURCES=erl_syntax.erl erl_prettypr.erl erl_syntax_lib.erl \
erl_comment_scan.erl erl_recomment.erl erl_tidy.erl \
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index 119d375746..f1615b2610 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -1130,13 +1130,14 @@ lay_2(Node, Ctxt) ->
any_size ->
text("map()");
Fs ->
- {Prec, _PrecR} = type_preop_prec('#'),
- Es = lay_map_fields(Fs,
- floating(text(",")),
- reset_prec(Ctxt)),
+ Ctxt1 = reset_prec(Ctxt),
+ Es = seq(Fs,
+ floating(text(",")), Ctxt1,
+ fun lay/2),
D = beside(floating(text("#{")),
beside(par(Es),
floating(text("}")))),
+ {Prec, _PrecR} = type_preop_prec('#'),
maybe_parentheses(D, Prec, Ctxt)
end;
@@ -1400,36 +1401,6 @@ lay_error_info(T, Ctxt) ->
lay_concrete(T, Ctxt) ->
lay(erl_syntax:abstract(T), Ctxt).
-lay_map_fields([H | T], Separator, Ctxt) ->
- case T of
- [] ->
- [case erl_syntax:type(H) of
- map_type_assoc ->
- lay_last_type_assoc(H, Ctxt);
- _ ->
- lay(H, Ctxt)
- end];
- _ ->
- [maybe_append(Separator, lay(H, Ctxt))
- | lay_map_fields(T, Separator, Ctxt)]
- end;
-lay_map_fields([], _, _) ->
- [empty()].
-
-lay_last_type_assoc(Node, Ctxt) ->
- Name = erl_syntax:map_type_assoc_name(Node),
- Value = erl_syntax:map_type_assoc_value(Node),
- IsAny = fun({type,_,any,[]}) -> true;
- %% ({var,_,'_'}) -> true;
- (_) -> false
- end,
- case IsAny(Name) andalso IsAny(Value) of
- true ->
- text("...");
- false ->
- lay_type_assoc(Name, Value, Ctxt)
- end.
-
lay_type_assoc(Name, Value, Ctxt) ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(Name, Ctxt1),
diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl
index 5ce533285d..c1141b2bc6 100644
--- a/lib/syntax_tools/src/erl_recomment.erl
+++ b/lib/syntax_tools/src/erl_recomment.erl
@@ -601,16 +601,16 @@ expand_comment(C) ->
-record(leaf, {min = 0 :: integer(),
max = 0 :: integer(),
- precomments = [] :: [erl_syntax:syntaxTree()],
- postcomments = [] :: [erl_syntax:syntaxTree()],
+ precomments = [] :: [erl_comment_scan:comment()],
+ postcomments = [] :: [erl_comment_scan:comment()],
value :: erl_syntax:syntaxTree()}).
-record(tree, {min = 0 :: integer(),
max = 0 :: integer(),
type :: atom(),
attrs :: erl_syntax:syntaxTreeAttributes(),
- precomments = [] :: [erl_syntax:syntaxTree()],
- postcomments = [] :: [erl_syntax:syntaxTree()],
+ precomments = [] :: [erl_comment_scan:comment()],
+ postcomments = [] :: [erl_comment_scan:comment()],
subtrees = [] :: [extendedSyntaxTree()]}).
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index f4cda814fc..ee42e56172 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -443,7 +443,14 @@
-type syntaxTree() :: #tree{} | #wrapper{} | erl_parse().
--type erl_parse() :: erl_parse:abstract_form() | erl_parse:abstract_expr().
+-type erl_parse() :: erl_parse:abstract_clause()
+ | erl_parse:abstract_expr()
+ | erl_parse:abstract_form()
+ | erl_parse:abstract_type()
+ | erl_parse:form_info()
+ %% To shut up Dialyzer:
+ | {bin_element, _, _, _, _}.
+
%% The representation built by the Erlang standard library parser
%% `erl_parse'. This is a subset of the {@link syntaxTree()} type.
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl
index 5621d3a293..e4f8a1c3de 100644
--- a/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl
+++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl
@@ -49,9 +49,12 @@
-type m1() :: #{} | map().
-type m2() :: #{a := m1(), b => #{} | fy:m2()}.
--type m3() :: #{...}.
--type m4() :: #{_ => _, ...}.
--type m5() :: #{any() => any(), ...}. % Currently printed as `#{..., ...}'.
+%-type m3() :: #{...}.
+%-type m4() :: #{_ => _, ...}.
+%-type m5() :: #{any() => any(), ...}.
+-type m3() :: #{any() => any()}.
+-type m4() :: #{_ => _, any() => any()}.
+-type m5() :: #{any() => any(), any() => any()}.
-type b1() :: B1 :: binary() | (BitString :: bitstring()).
-define(PAIR(A, B), {(A), (B)}).
diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl
index 4322943c59..bb9815f9b0 100644
--- a/lib/tools/src/xref_base.erl
+++ b/lib/tools/src/xref_base.erl
@@ -669,16 +669,45 @@ do_add_directory(Dir, AppName, Bui, Rec, Ver, War, State) ->
warnings(War, unreadable, Unreadable),
case Errors of
[] ->
- do_add_modules(FileNames, AppName, Bui, Ver, War, State, []);
+ do_add_modules(FileNames, AppName, Bui, Ver, War, State);
[Error | _] ->
throw(Error)
end.
-do_add_modules([], _AppName, _OB, _OV, _OW, State, Modules) ->
+do_add_modules(Files, AppName, OB, OV, OW, State0) ->
+ NFiles = length(Files),
+ Reader = fun(SplitName, State) ->
+ _Pid = read_module(SplitName, AppName, OB, OV, OW, State)
+ end,
+ N = parallelism(),
+ Files1 = start_readers(Files, Reader, State0, N),
+ %% Increase the number of readers towards the end to decrease the
+ %% waiting time for the collecting process:
+ Nx = N,
+ add_mods(Files1, Reader, State0, [], NFiles, Nx).
+
+add_mods(_, _ReaderFun, State, Modules, 0, _Nx) ->
{ok, sort(Modules), State};
-do_add_modules([File | Files], AppName, OB, OV, OW, State, Modules) ->
- {ok, M, NewState} = do_add_module(File, AppName, OB, OV, OW, State),
- do_add_modules(Files, AppName, OB, OV, OW, NewState, M ++ Modules).
+add_mods(Files, ReaderFun, State, Modules, N, Nx) ->
+ {I, Nx1} = case Nx > 0 of
+ false -> {1, Nx};
+ true -> {2, Nx - 1}
+ end,
+ Files1 = start_readers(Files, ReaderFun, State, I),
+ {ok, M, NewState} = process_module(State),
+ add_mods(Files1, ReaderFun, NewState, M ++ Modules, N - 1, Nx1).
+
+start_readers([SplitName|Files], ReaderFun, State, N) when N > 0 ->
+ _Pid = ReaderFun(SplitName, State),
+ start_readers(Files, ReaderFun, State, N - 1);
+start_readers(Files, _ReaderFun, _State, _) ->
+ Files.
+
+parallelism() ->
+ case erlang:system_info(multi_scheduling) of
+ enabled -> erlang:system_info(schedulers_online);
+ _ -> 1
+ end.
%% -> {ok, Module, State} | throw(Error)
do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
@@ -692,50 +721,75 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
%% -> {ok, Module, State} | throw(Error)
%% Options: verbose, warnings, builtins
-do_add_module({Dir, Basename}, AppName, Builtins, Verbose, Warnings, State) ->
- File = filename:join(Dir, Basename),
- {ok, M, Bad, NewState} =
- do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State),
- _ = filter(fun({Tag,B}) -> warnings(Warnings, Tag, [[File,B]]) end, Bad),
- {ok, M, NewState}.
-
-do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State) ->
- message(Verbose, reading_beam, [File]),
- Mode = State#xref.mode,
+do_add_module(SplitName, AppName, Builtins, Verbose, Warnings, State) ->
+ _Pid = read_module(SplitName, AppName, Builtins, Verbose, Warnings, State),
+ process_module(State).
+
+read_module(SplitName, AppName, Builtins, Verbose, Warnings, State) ->
Me = self(),
- Fun = fun() -> Me ! {self(), abst(File, Builtins, Mode)} end,
- case xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]) of
+ #xref{mode = Mode} = State,
+ Fun =
+ fun() ->
+ Me ! {?MODULE,
+ read_a_module(SplitName, AppName, Builtins, Verbose,
+ Warnings, Mode)}
+ end,
+ spawn_opt(Fun, [link, {min_heap_size, 1000000}, {priority, high}]).
+
+read_a_module({Dir, BaseName}, AppName, Builtins, Verbose, Warnings, Mode) ->
+ File = filename:join(Dir, BaseName),
+ case abst(File, Builtins, Mode) of
{ok, _M, no_abstract_code} when Verbose ->
- message(Verbose, skipped_beam, []),
- {ok, [], [], State};
+ message(Verbose, no_debug_info, [File]),
+ no;
{ok, _M, no_abstract_code} when not Verbose ->
message(Warnings, no_debug_info, [File]),
- {ok, [], [], State};
+ no;
{ok, M, Data, UnresCalls0} ->
- %% Remove duplicates. Identical unresolved calls on the
- %% same line are counted as _one_ unresolved call.
- UnresCalls = usort(UnresCalls0),
- message(Verbose, done, []),
- NoUnresCalls = length(UnresCalls),
- case NoUnresCalls of
- 0 -> ok;
- 1 -> warnings(Warnings, unresolved_summary1, [[M]]);
- N -> warnings(Warnings, unresolved_summary, [[M, N]])
- end,
- T = case xref_utils:file_info(File) of
- {ok, {_, _, _, Time}} -> Time;
- Error -> throw(Error)
- end,
- XMod = #xref_mod{name = M, app_name = AppName, dir = Dir,
- mtime = T, builtins = Builtins,
- no_unresolved = NoUnresCalls},
- do_add_module(State, XMod, UnresCalls, Data);
+ message(Verbose, done, [File]),
+ %% Remove duplicates. Identical unresolved calls on the
+ %% same line are counted as _one_ unresolved call.
+ UnresCalls = usort(UnresCalls0),
+ NoUnresCalls = length(UnresCalls),
+ case NoUnresCalls of
+ 0 -> ok;
+ 1 -> warnings(Warnings, unresolved_summary1, [[M]]);
+ N -> warnings(Warnings, unresolved_summary, [[M, N]])
+ end,
+ case xref_utils:file_info(File) of
+ {ok, {_, _, _, Time}} ->
+ XMod = #xref_mod{name = M, app_name = AppName,
+ dir = Dir, mtime = Time,
+ builtins = Builtins,
+ no_unresolved = NoUnresCalls},
+ {ok, PrepMod, Bad} =
+ prepare_module(Mode, XMod, UnresCalls, Data),
+ foreach(fun({Tag,B}) ->
+ warnings(Warnings, Tag,
+ [[File,B]])
+ end, Bad),
+ {ok, PrepMod};
+ Error -> Error
+ end;
Error ->
message(Verbose, error, []),
- throw(Error)
+ Error
end.
-abst(File, Builtins, Mode) when Mode =:= functions ->
+process_module(State) ->
+ receive
+ {?MODULE, Reply} ->
+ case Reply of
+ no ->
+ {ok, [], State};
+ {ok, PrepMod} ->
+ finish_module(PrepMod, State);
+ Error ->
+ throw(Error)
+ end
+ end.
+
+abst(File, Builtins, _Mode = functions) ->
case beam_lib:chunks(File, [abstract_code, exports, attributes]) of
{ok, {M,[{abstract_code,NoA},_X,_A]}} when NoA =:= no_abstract_code ->
{ok, M, NoA};
@@ -762,7 +816,7 @@ abst(File, Builtins, Mode) when Mode =:= functions ->
Error when element(1, Error) =:= error ->
Error
end;
-abst(File, Builtins, Mode) when Mode =:= modules ->
+abst(File, Builtins, _Mode = modules) ->
case beam_lib:chunks(File, [exports, imports, attributes]) of
{ok, {Mod, [{exports,X0}, {imports,I0}, {attributes,At}]}} ->
X1 = mfa_exports(X0, At, Mod),
@@ -856,19 +910,13 @@ deprecated_flag(_) -> undefined.
%% dom CallAt = LC U XC
%% Attrs is collected from the attribute 'xref' (experimental).
do_add_module(S, XMod, Unres, Data) ->
- M = XMod#xref_mod.name,
- case dict:find(M, S#xref.modules) of
- {ok, OldXMod} ->
- BF2 = module_file(XMod),
- BF1 = module_file(OldXMod),
- throw_error({module_clash, {M, BF1, BF2}});
- error ->
- do_add_module(S, M, XMod, Unres, Data)
- end.
+ #xref{mode = Mode} = S,
+ Mode = S#xref.mode,
+ {ok, PrepMod, Bad} = prepare_module(Mode, XMod, Unres, Data),
+ {ok, Ms, NS} = finish_module(PrepMod, S),
+ {ok, Ms, Bad, NS}.
-%%do_add_module(S, M, _XMod, _Unres, Data)->
-%% {ok, M, [], S};
-do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
+prepare_module(_Mode = functions, XMod, Unres0, Data) ->
{DefAt0, LPreCAt0, XPreCAt0, LC0, XC0, X0, Attrs, Depr} = Data,
%% Bad is a list of bad values of 'xref' attributes.
{ALC0,AXC0,Bad0} = Attrs,
@@ -904,26 +952,27 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
LC = union(LC1, ALC),
{DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X),
+ {EE, ECallAt} = inter_graph(X, L, LC, XC, CallAt),
+ {ok, {functions, XMod, [DefAt,L,X,LCallAt,XCallAt,CallAt,LC,XC,EE,ECallAt,
+ DF1,DF_11,DF_21,DF_31], NoCalls, Unres},
+ DBad++Bad};
+prepare_module(_Mode = modules, XMod, _Unres, Data) ->
+ {X0, I0, Depr} = Data,
+ X1 = xref_utils:xset(X0, [tspec(func)]),
+ I1 = xref_utils:xset(I0, [tspec(func)]),
+ {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X1),
+ {ok, {modules, XMod, [X1,I1,DF1,DF_11,DF_21,DF_31]}, DBad}.
- %% {EE, ECallAt} = inter_graph(X, L, LC, XC, LCallAt, XCallAt),
- Self = self(),
- Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end,
- {EE, ECallAt} =
- xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]),
-
+finish_module({functions, XMod, List, NoCalls, Unres}, S) ->
+ ok = check_module(XMod, S),
[DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
- DF2,DF_12,DF_22,DF_32] =
- pack([DefAt,L,X,LCallAt,XCallAt,CallAt,LC,XC,EE,ECallAt,
- DF1,DF_11,DF_21,DF_31]),
-
- %% Foo = [DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
- %% DF2,DF_12,DF_22,DF_32],
- %% io:format("{~p, ~p, ~p},~n", [M, pack:lsize(Foo), pack:usize(Foo)]),
+ DF2,DF_12,DF_22,DF_32] = pack(List),
LU = range(LC2),
LPredefined = predefined_funs(LU),
+ M = XMod#xref_mod.name,
MS = xref_utils:xset(M, atom),
T = from_sets({MS,DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,
LC2,XC2,LU,EE2,ECallAt2,Unres,LPredefined,
@@ -934,19 +983,28 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
XMod1 = XMod#xref_mod{data = T, info = Info},
S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
- {ok, [M], DBad++Bad, take_down(S1)};
-do_add_module(S, M, XMod, _Unres, Data) when S#xref.mode =:= modules ->
- {X0, I0, Depr} = Data,
- X1 = xref_utils:xset(X0, [tspec(func)]),
- I1 = xref_utils:xset(I0, [tspec(func)]),
- {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X1),
- [X2,I2,DF2,DF_12,DF_22,DF_32] = pack([X1,I1,DF1,DF_11,DF_21,DF_31]),
+ {ok, [M], take_down(S1)};
+finish_module({modules, XMod, List}, S) ->
+ ok = check_module(XMod, S),
+ [X2,I2,DF2,DF_12,DF_22,DF_32] = pack(List),
+ M = XMod#xref_mod.name,
MS = xref_utils:xset(M, atom),
T = from_sets({MS, X2, I2, DF2, DF_12, DF_22, DF_32}),
Info = [],
XMod1 = XMod#xref_mod{data = T, info = Info},
S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
- {ok, [M], DBad, take_down(S1)}.
+ {ok, [M], take_down(S1)}.
+
+check_module(XMod, State) ->
+ M = XMod#xref_mod.name,
+ case dict:find(M, State#xref.modules) of
+ {ok, OldXMod} ->
+ BF2 = module_file(XMod),
+ BF1 = module_file(OldXMod),
+ throw_error({module_clash, {M, BF1, BF2}});
+ error ->
+ ok
+ end.
depr_mod({Depr,Bad0}, X) ->
%% Bad0 are badly formed deprecated attributes.
@@ -992,9 +1050,6 @@ no_info(X, L, LC, XC, EE, Unres, NoCalls, NoUnresCalls) ->
%% Note: this is overwritten in do_set_up():
{no_inter_function_calls, no_elements(EE)}].
-inter_graph(Pid, X, L, LC, XC, CallAt) ->
- Pid ! {self(), inter_graph(X, L, LC, XC, CallAt)}.
-
%% Inter Call Graph.
%inter_graph(_X, _L, _LC, _XC, _CallAt) ->
% {empty_set(), empty_set()};
@@ -1766,10 +1821,6 @@ tpack(T, I, L) ->
message(true, What, Arg) ->
case What of
- reading_beam ->
- io:format("~ts... ", Arg);
- skipped_beam ->
- io:format("skipped (no debug information)~n", Arg);
no_debug_info ->
io:format("Skipping ~ts (no debug information)~n", Arg);
unresolved_summary1 ->
@@ -1791,7 +1842,7 @@ message(true, What, Arg) ->
set_up ->
io:format("Setting up...", Arg);
done ->
- io:format("done~n", Arg);
+ io:format("done reading ~ts~n", Arg);
error ->
io:format("error~n", Arg);
Else ->
diff --git a/lib/tools/src/xref_utils.erl b/lib/tools/src/xref_utils.erl
index f69aa70244..b0c168e018 100644
--- a/lib/tools/src/xref_utils.erl
+++ b/lib/tools/src/xref_utils.erl
@@ -47,8 +47,6 @@
-export([options/2]).
--export([subprocess/2]).
-
-export([format_error/1]).
-import(lists, [append/1, delete/2, filter/2, foldl/3, foreach/2,
@@ -512,12 +510,6 @@ find_beam(Culprit) ->
options(Options, Valid) ->
split_options(Options, [], [], [], Valid).
-subprocess(Fun, Opts) ->
- Pid = spawn_opt(Fun, Opts),
- receive
- {Pid, Reply} -> Reply
- end.
-
format_error({error, Module, Error}) ->
Module:format_error(Error);
format_error({file_error, FileName, Reason}) ->
diff --git a/system/doc/reference_manual/typespec.xml b/system/doc/reference_manual/typespec.xml
index 9e26e9058d..f17e5df277 100644
--- a/system/doc/reference_manual/typespec.xml
+++ b/system/doc/reference_manual/typespec.xml
@@ -142,7 +142,7 @@
PairList :: Pair
| Pair, PairList
- Pair :: Type := Type %% notes a pair that must be present
+ Pair :: Type := Type %% denotes a pair that must be present
| Type => Type
TList :: Type
@@ -174,19 +174,13 @@
</p>
<p>
The general form of maps is <c>#{PairList}</c>. The key types in
- <c>PairList</c> are allowed to overlap, and if they do, the leftmost pair
- takes precedence. A map value does not belong to this type if contains a key
- that is not in <c>PairList</c>.
+ <c>PairList</c> are allowed to overlap, and if they do, the
+ leftmost pair takes precedence. A map pair has a key in
+ <c>PairList</c> if it belongs to this type.
</p>
<p>
- Because it is common to end a map type with <c>any() =&gt; any()</c> to denote
- that keys that do not belong to any other pair in <c>PairList</c> are
- allowed, and may map to any value, the shorthand notation <c>...</c> is
- allowed as the last pair of a map type.
- </p>
- <p>
- Notice that the syntactic representation of <c>map()</c> is <c>#{...}</c>
- (or <c>#{_ =&gt; _}</c>, or <c>#{any() =&gt; any()}</c>), not <c>#{}</c>.
+ Notice that the syntactic representation of <c>map()</c> is
+ <c>#{any() =&gt; any()}</c> (or <c>#{_ =&gt; _}</c>), not <c>#{}</c>.
The notation <c>#{}</c> specifies the singleton type for the empty map.
</p>
<p>