summaryrefslogtreecommitdiff
path: root/lib/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel')
-rw-r--r--lib/kernel/Makefile2
-rw-r--r--lib/kernel/doc/src/Makefile110
-rw-r--r--lib/kernel/doc/src/code.xml49
-rw-r--r--lib/kernel/doc/src/disk_log.xml14
-rw-r--r--lib/kernel/doc/src/erpc.xml513
-rw-r--r--lib/kernel/doc/src/file.xml28
-rw-r--r--lib/kernel/doc/src/gen_sctp.xml6
-rw-r--r--lib/kernel/doc/src/gen_udp.xml1
-rw-r--r--lib/kernel/doc/src/kernel_app.xml10
-rw-r--r--lib/kernel/doc/src/net.xml2
-rw-r--r--lib/kernel/doc/src/pg.xml195
-rw-r--r--lib/kernel/doc/src/pg2.xml10
-rw-r--r--lib/kernel/doc/src/ref_man.xml2
-rw-r--r--lib/kernel/doc/src/rpc.xml153
-rw-r--r--lib/kernel/doc/src/seq_trace.xml139
-rw-r--r--lib/kernel/doc/src/specs.xml2
-rw-r--r--lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl7
-rw-r--r--lib/kernel/include/dist.hrl13
-rw-r--r--lib/kernel/include/dist_util.hrl5
-rw-r--r--lib/kernel/include/eep48.hrl14
-rw-r--r--lib/kernel/src/Makefile5
-rw-r--r--lib/kernel/src/application_controller.erl152
-rw-r--r--lib/kernel/src/auth.erl6
-rw-r--r--lib/kernel/src/code.erl163
-rw-r--r--lib/kernel/src/code_server.erl33
-rw-r--r--lib/kernel/src/disk_log_server.erl6
-rw-r--r--lib/kernel/src/dist_util.erl325
-rw-r--r--lib/kernel/src/erl_epmd.erl13
-rw-r--r--lib/kernel/src/erpc.erl473
-rw-r--r--lib/kernel/src/error_logger.erl13
-rw-r--r--lib/kernel/src/erts_debug.erl64
-rw-r--r--lib/kernel/src/file.erl28
-rw-r--r--lib/kernel/src/file_io_server.erl8
-rw-r--r--lib/kernel/src/global.erl2
-rw-r--r--lib/kernel/src/group.erl10
-rw-r--r--lib/kernel/src/group_history.erl30
-rw-r--r--lib/kernel/src/inet_db.erl455
-rw-r--r--lib/kernel/src/inet_hosts.erl26
-rw-r--r--lib/kernel/src/kernel.app.src8
-rw-r--r--lib/kernel/src/kernel.erl19
-rw-r--r--lib/kernel/src/logger.erl21
-rw-r--r--lib/kernel/src/logger_backend.erl3
-rw-r--r--lib/kernel/src/logger_config.erl150
-rw-r--r--lib/kernel/src/logger_formatter.erl4
-rw-r--r--lib/kernel/src/logger_h_common.erl24
-rw-r--r--lib/kernel/src/logger_handler_watcher.erl29
-rw-r--r--lib/kernel/src/logger_internal.hrl17
-rw-r--r--lib/kernel/src/logger_olp.erl97
-rw-r--r--lib/kernel/src/logger_olp.hrl19
-rw-r--r--lib/kernel/src/logger_proxy.erl12
-rw-r--r--lib/kernel/src/logger_server.erl40
-rw-r--r--lib/kernel/src/logger_simple_h.erl15
-rw-r--r--lib/kernel/src/logger_std_h.erl18
-rw-r--r--lib/kernel/src/net.erl12
-rw-r--r--lib/kernel/src/pg.erl507
-rw-r--r--lib/kernel/src/pg2.erl4
-rw-r--r--lib/kernel/src/raw_file_io_compressed.erl6
-rw-r--r--lib/kernel/src/raw_file_io_delayed.erl6
-rw-r--r--lib/kernel/src/raw_file_io_list.erl7
-rw-r--r--lib/kernel/src/rpc.erl523
-rw-r--r--lib/kernel/src/seq_trace.erl16
-rw-r--r--lib/kernel/src/user.erl53
-rw-r--r--lib/kernel/test/Makefile2
-rw-r--r--lib/kernel/test/application_SUITE.erl214
-rw-r--r--lib/kernel/test/code_SUITE.erl55
-rw-r--r--lib/kernel/test/erl_distribution_SUITE.erl332
-rw-r--r--lib/kernel/test/erl_distribution_wb_SUITE.erl269
-rw-r--r--lib/kernel/test/erpc_SUITE.erl796
-rw-r--r--lib/kernel/test/file_SUITE.erl232
-rw-r--r--lib/kernel/test/gen_tcp_api_SUITE.erl7
-rw-r--r--lib/kernel/test/global_SUITE.erl14
-rw-r--r--lib/kernel/test/inet_SUITE.erl225
-rw-r--r--lib/kernel/test/init_SUITE.erl20
-rw-r--r--lib/kernel/test/interactive_shell_SUITE.erl28
-rw-r--r--lib/kernel/test/kernel_SUITE.erl3
-rw-r--r--lib/kernel/test/logger_SUITE.erl42
-rw-r--r--lib/kernel/test/logger_formatter_SUITE.erl7
-rw-r--r--lib/kernel/test/logger_legacy_SUITE.erl6
-rw-r--r--lib/kernel/test/logger_std_h_SUITE.erl15
-rw-r--r--lib/kernel/test/os_SUITE.erl10
-rw-r--r--lib/kernel/test/pg_SUITE.erl619
-rw-r--r--lib/kernel/test/rpc_SUITE.erl190
-rw-r--r--lib/kernel/test/seq_trace_SUITE.erl452
83 files changed, 7096 insertions, 1139 deletions
diff --git a/lib/kernel/Makefile b/lib/kernel/Makefile
index b956f5eaf5..5ab8ac63b9 100644
--- a/lib/kernel/Makefile
+++ b/lib/kernel/Makefile
@@ -34,3 +34,5 @@ SPECIAL_TARGETS =
# Default Subdir Targets
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/kernel/doc/src/Makefile b/lib/kernel/doc/src/Makefile
index fe3dc9dab5..9b004b3781 100644
--- a/lib/kernel/doc/src/Makefile
+++ b/lib/kernel/doc/src/Makefile
@@ -28,11 +28,6 @@ VSN=$(KERNEL_VSN)
APPLICATION=kernel
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -46,6 +41,7 @@ XML_REF3_FILES = application.xml \
erl_epmd.xml \
erl_prim_loader_stub.xml \
erlang_stub.xml \
+ erpc.xml \
error_handler.xml \
error_logger.xml \
file.xml \
@@ -67,6 +63,7 @@ XML_REF3_FILES = application.xml \
net_adm.xml \
net_kernel.xml \
os.xml \
+ pg.xml \
pg2.xml \
rpc.xml \
seq_trace.xml \
@@ -96,86 +93,8 @@ XML_FILES = \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF4_FILES)\
$(XML_REF6_FILES) $(XML_APPLICATION_FILES)
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN4_FILES = $(XML_REF4_FILES:%.xml=$(MAN4DIR)/%.4)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
-
TOP_SPECS_FILE = specs.xml
-
-# ----------------------------------------------------
-# FIGURES
-# ----------------------------------------------------
-# In order to update the figures you have to have both dia
-# and imagemagick installed.
-# The generated .png file must be committed.
-
-update_png:
- dia --export=logger_arch.eps logger_arch.dia
- convert logger_arch.eps -resize 65% logger_arch.png
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-SPECS_ESRC = ../../src
-
-SPECS_FLAGS = -I../../include
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%: %
- $(INSTALL_DATA) $< $@
-
-docs: man pdf html
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: images $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES) $(MAN4_FILES) $(MAN6_FILES)
-
-images: $(IMAGE_FILES:%=$(HTMLDIR)/%)
-
-info:
- @echo "XML_APPLICATION_FILES: $(XML_APPLICATION_FILES)"
- @echo "XML_REF3_ESOCK_FILES: $(XML_REF3_ESOCK_FILES)"
- @echo "XML_REF3_FILES: $(XML_REF3_FILES)"
- @echo "XML_REF4_FILES: $(XML_REF4_FILES)"
- @echo "XML_REF6_FILES: $(XML_REF6_FILES)"
- @echo "XML_PART_FILES: $(XML_PART_FILES)"
- @echo "XML_CHAPTER_FILES: $(XML_CHAPTER_FILES)"
- @echo "BOOK_FILES: $(BOOK_FILES)"
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(MAN4DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECDIR)/*
- rm -f errs core *~ *.eps
-
$(SPECDIR)/specs_erl_prim_loader_stub.xml:
$(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) \
-o$(dir $@) -module erl_prim_loader_stub
@@ -189,24 +108,17 @@ $(SPECDIR)/specs_zlib_stub.xml:
$(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) \
-o$(dir $@) -module zlib_stub
+NO_CHUNKS = erl_prim_loader_stub.xml erlang_stub.xml init_stub.xml zlib_stub.xml
# ----------------------------------------------------
-# Release Target
+# FIGURES
# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
+# In order to update the figures you have to have both dia
+# and imagemagick installed.
+# The generated .png file must be committed.
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man4"
- $(INSTALL_DATA) $(MAN4_FILES) "$(RELEASE_PATH)/man/man4"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
+update_png:
+ dia --export=logger_arch.eps logger_arch.dia
+ convert logger_arch.eps -resize 65% logger_arch.png
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml
index 4aa9e8b9d2..45538d3239 100644
--- a/lib/kernel/doc/src/code.xml
+++ b/lib/kernel/doc/src/code.xml
@@ -315,6 +315,9 @@ zip:create("mnesia-4.4.7.ez",
<name name="load_error_rsn"/>
</datatype>
<datatype>
+ <name name="module_status"/>
+ </datatype>
+ <datatype>
<name name="prepared_code"/>
<desc><p>An opaque term holding prepared code.</p></desc>
</datatype>
@@ -702,6 +705,21 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
+ <name name="all_available" arity="0" since="OTP @OTP-16494@"/>
+ <fsummary>Get all available modules.</fsummary>
+ <type name="loaded_filename"/>
+ <type name="loaded_ret_atoms"/>
+ <type_desc name="loaded_filename"><c><anno>Filename</anno></c> is an absolute
+ filename.</type_desc>
+ <desc>
+ <p>Returns a list of tuples <c>{<anno>Module</anno>, <anno>Filename</anno>,
+ <anno>Loaded</anno>}</c> for all available modules. A module is considered
+ to be available if it either is loaded or would be loaded if called.
+ <c><anno>Filename</anno></c> is normally the absolute filename, as described for
+ <seealso marker="#is_loaded/1"><c>is_loaded/1</c></seealso>.</p>
+ </desc>
+ </func>
+ <func>
<name name="all_loaded" arity="0" since=""/>
<fsummary>Get all loaded modules.</fsummary>
<type name="loaded_filename"/>
@@ -718,6 +736,7 @@ ok = code:finish_loading(Prepared),
<func>
<name name="which" arity="1" since=""/>
<fsummary>The object code file of a module.</fsummary>
+ <type name="loaded_filename"/>
<type name="loaded_ret_atoms"/>
<desc>
<p>If the module is not loaded, this function searches the code
@@ -750,6 +769,22 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
+ <name name="get_doc" arity="1" since="OTP @OTP-16406@"/>
+ <fsummary>Gets the documentation for a module.</fsummary>
+ <desc>
+ <p>Searches the code path for a documentation chunk
+ and returns ut if available. If no documentation chunk
+ can be found the function tries to generate documentation
+ from the debug information in the module. If no debug
+ information is available, this function will return
+ <c>{error,missing}</c>.
+ </p>
+ <p>For more information about the documentation chunk see
+ <seealso marker="erl_docgen:doc_storage">Documentation Storage</seealso>
+ in Erl_Docgen's User's Guide.</p>
+ </desc>
+ </func>
+ <func>
<name name="root_dir" arity="0" since=""/>
<fsummary>Root directory of Erlang/OTP.</fsummary>
<desc>
@@ -901,10 +936,20 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
+ <name name="module_status" arity="0" since="OTP 23.0"/>
+ <fsummary>Return the statuses of all loaded modules.</fsummary>
+ <type name="module_status"/>
+ <desc>
+ <p>See <seealso marker="#module_status/1"><c>module_status/1</c></seealso> and <seealso marker="#all_loaded/0"><c>all_loaded/0</c></seealso> for details.</p>
+ </desc>
+ </func>
+ <func>
<name name="module_status" arity="1" since="OTP 20.0"/>
- <fsummary>Return the status of the module in relation to object file on disk.</fsummary>
+ <fsummary>Return the status of a module or modules in relation to the
+ object files on disk.</fsummary>
+ <type name="module_status"/>
<desc>
- <p>Returns:</p>
+ <p>The status of a module can be one of:</p>
<taglist>
<tag><c>not_loaded</c></tag>
<item><p>If <c><anno>Module</anno></c> is not currently loaded.</p></item>
diff --git a/lib/kernel/doc/src/disk_log.xml b/lib/kernel/doc/src/disk_log.xml
index e308b06f3c..4bfe9cb0db 100644
--- a/lib/kernel/doc/src/disk_log.xml
+++ b/lib/kernel/doc/src/disk_log.xml
@@ -127,6 +127,10 @@
functions fail. The corresponding terms (not the binaries)
are returned when <c>chunk/2,3</c> is called.
</p>
+ <note><p>
+ The distributed disk log feature has been deprecated. This
+ feature has also been scheduled for removal in OTP 24.
+ </p></note>
<p>A collection of open disk logs with the same name running on
different nodes is said to be a <em>distributed disk log</em>
if requests made to any of the logs are automatically made to
@@ -609,6 +613,10 @@
the current node, <c><anno>Dist</anno></c> has the value <c>local</c>,
otherwise all nodes where the log is distributed
are returned as a list.</p>
+ <warning><p>
+ The distributed disk log feature has been deprecated. This
+ feature has also been scheduled for removal in OTP 24.
+ </p></warning>
</item>
</taglist>
<p>The following pairs are returned for all logs opened in
@@ -871,7 +879,11 @@
adding members to a distributed disk log.
Defaults to <c>[]</c>, which means that
the log is local on the current node.
- </p>
+ </p>
+ <warning><p>
+ The distributed disk log feature has been deprecated. This
+ feature has also been scheduled for removal in OTP 24.
+ </p></warning>
</item>
<tag><c>{notify, boolean()}</c><marker id="notify"></marker></tag>
<item>
diff --git a/lib/kernel/doc/src/erpc.xml b/lib/kernel/doc/src/erpc.xml
new file mode 100644
index 0000000000..43e25b016b
--- /dev/null
+++ b/lib/kernel/doc/src/erpc.xml
@@ -0,0 +1,513 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2020</year><year>2020</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>erpc</title>
+ <prepared>Rickard Green</prepared>
+ <docno>1</docno>
+ <date>2020-02-20</date>
+ <rev>A</rev>
+ </header>
+ <module since="OTP @OTP-13450@">erpc</module>
+ <modulesummary>Enhanced Remote Procedure Call</modulesummary>
+ <description>
+ <p>
+ This module provide services similar to Remote Procedure Calls.
+ A remote procedure call is a method to call a function on a remote
+ node and collect the answer. It is used for collecting information
+ on a remote node, or for running a function with some specific side
+ effects on the remote node.
+ </p>
+ <p>
+ This is an enhanced subset of the operations provided by the
+ <seealso marker="rpc"><c>rpc</c></seealso> module. Enhanced in the
+ sense that it makes it possible to distinguish between returned
+ value, raised exceptions, and other errors. <c>erpc</c> also has
+ better performance and scalability than the original <c>rpc</c>
+ implementation. However, current <c>rpc</c> module will utilize
+ <c>erpc</c> in order to also provide these properties when
+ possible.
+ </p>
+ <p>
+ In order for an <c>erpc</c> operation to succeed, the remote
+ node also needs to support <c>erpc</c>. Typically only ordinary
+ Erlang nodes as of OTP 23 have <c>erpc</c> support.
+ </p>
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="request_id"/>
+ <desc>
+ <p>
+ An opaque type of call request identifiers. For more
+ information see
+ <seealso marker="#send_request/4"><c>send_request/4</c></seealso>.
+ </p>
+ </desc>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+
+ <func>
+ <name name="call" arity="4" since="OTP @OTP-13450@"/>
+ <name name="call" arity="5" since="OTP @OTP-13450@"/>
+ <fsummary>Evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>)</c> on node <c><anno>Node</anno></c> and returns
+ the corresponding value <c><anno>Result</anno></c>.
+ <c><anno>Timeout</anno></c> is an integer representing
+ the timeout in milliseconds or the atom <c>infinity</c>.
+ </p>
+ <p>The call <c>erpc:call(<anno>Node</anno>, <anno>Module</anno>,
+ <anno>Function</anno>, <anno>Args</anno>)</c> is equivalent
+ to the call <c>erpc:call(<anno>Node</anno>, <anno>Module</anno>,
+ <anno>Function</anno>, <anno>Args</anno>, infinity)</c></p>
+ <p>
+ The <c>call()</c> function only returns if the applied
+ function successfully returned without raising any uncaught
+ exceptions, the operation did not time out, and no failures
+ occurred. In all other cases an exception is raised. The
+ following exceptions, listed by exception class, can
+ currently be raised by <c>erpc:call()</c>:
+ </p>
+ <taglist>
+ <tag><c>throw</c></tag>
+ <item><p>
+ The applied function called <c>throw(Value)</c>
+ and did not catch this exception. The exception
+ reason <c>Value</c> equals the argument passed to
+ <c>throw/1</c>.
+ </p></item>
+
+ <tag><c>exit</c></tag>
+ <item><p>
+ Exception reason:
+ </p>
+ <taglist>
+ <tag><c>{exception, ExitReason}</c></tag>
+ <item><p>
+ The applied function called <c>exit(ExitReason)</c>
+ and did not catch this exception. The exit
+ reason <c>ExitReason</c> equals the argument passed
+ to <c>exit/1</c>.
+ </p></item>
+ <tag><c>{signal, ExitReason}</c></tag>
+ <item><p>
+ The process that applied the function received an
+ exit signal and terminated due to this signal. The
+ process terminated with exit reason <c>ExitReason</c>.
+ </p></item>
+ </taglist>
+ </item>
+
+ <tag><c>error</c></tag>
+ <item><p>
+ Exception reason:
+ </p>
+ <taglist>
+
+ <tag><c>{exception, ErrorReason, StackTrace}</c></tag>
+ <item><p>
+ A runtime error occurred which raised and error
+ exception while applying the function,
+ and the applied function did not catch the
+ exception. The error reason <c>ErrorReason</c>
+ indicates the type of error that occurred.
+ <c>StackTrace</c> is formatted as when caught in a
+ <c>try/catch</c> construct. The <c>StackTrace</c>
+ is limited to the applied function and functions
+ called by it.
+ </p></item>
+
+ <tag><c>{erpc, ERpcErrorReason}</c></tag>
+ <item><p>
+ The <c>erpc</c> operation failed. The following
+ <c>ERpcErrorReason</c>s are the most common ones:
+ </p>
+
+ <taglist>
+ <tag><c>badarg</c></tag>
+ <item>
+ <p>If any one of these are true:</p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not a valid
+ node name atom.</p></item>
+ <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Args</anno></c> is not a proper list
+ of terms.</p></item>
+ <item><p><c><anno>Timeout</anno></c> is not the
+ atom <c>infinity</c> or an integer in valid
+ range.</p></item>
+ </list>
+ </item>
+
+ <tag><c>noconnection</c></tag>
+ <item><p>
+ The connection to <c>Node</c> was lost or could
+ not be established. The function may or may not
+ be applied.
+ </p></item>
+
+ <tag><c>system_limit</c></tag>
+ <item><p>
+ The <c>erpc</c> operation failed due to some system
+ limit being reached. This typically due to failure
+ to create a process on the remote node <c>Node</c>,
+ but can be other things as well.
+ </p></item>
+
+ <tag><c>timeout</c></tag>
+ <item><p>
+ The <c>erpc</c> operation timed out. The function may
+ or may not be applied.
+ </p></item>
+
+ <tag><c>notsup</c></tag>
+ <item><p>
+ The remote node <c>Node</c> does not support
+ this <c>erpc</c> operation.
+ </p>
+ </item>
+
+ </taglist>
+ </item>
+
+ </taglist>
+ </item>
+ </taglist>
+
+ <p>
+ If the <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a timeout or
+ a connection loss), the caller will not receive any
+ further information about the result if/when the applied
+ function completes. If the applied function explicitly
+ communicates with the calling process, such communication
+ may, of course, reach the calling process.
+ </p>
+
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be the calling process itself, a server, or a freshly
+ spawned process.
+ </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="cast" arity="4" since="OTP @OTP-13450@"/>
+ <fsummary>Evaluate a function call on a node ignoring the result.</fsummary>
+ <desc>
+ <p>
+ Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>)</c> on node
+ <c><anno>Node</anno></c>. No response is delivered to the
+ calling process. <c>erpc:cast()</c> returns immediately
+ after the cast request has been sent. Any failures beside
+ bad arguments are silently ignored.
+ </p>
+ <p><c>erpc:cast/4</c> fails with an <c>{erpc, badarg}</c>
+ <c>error</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not a valid
+ node name atom.</p></item>
+ <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Args</anno></c> is not a proper list
+ of terms.</p></item>
+ </list>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be a server, or a freshly spawned process.
+ </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="check_response" arity="2" since="OTP @OTP-13450@"/>
+ <fsummary>Check if a message is a response corresponding to a
+ previously sent call request.</fsummary>
+ <desc>
+ <p>
+ Check if a message is a response to a <c>call</c> request
+ previously made by the calling process using
+ <seealso marker="#send_request/4"><c>erpc:send_request/4</c></seealso>.
+ <c><anno>RequestId</anno></c> should be the value returned
+ from the previously made <c>erpc:send_request()</c> call,
+ and the corresponding response should not already have been
+ received and handled to completion by <c>erpc:check_response()</c>,
+ <seealso marker="#receive_response/2"><c>erpc:receive_response()</c></seealso>, or
+ <seealso marker="#wait_response/2"><c>erpc:wait_response()</c></seealso>.
+ <c><anno>Message</anno></c> is the message to check.
+ </p>
+ <p>
+ If <c><anno>Message</anno></c> does not correspond to the
+ response, the atom <c>no_response</c> is returned. If
+ <c><anno>Message</anno></c> corresponds to the response, the
+ <c>call</c> operation is completed and either the result is
+ returned as <c>{response, Result}</c> where <c>Result</c>
+ corresponds to the value returned from the applied function
+ or an exception is raised. The exceptions that can be raised
+ corresponds to the same exceptions as can be raised by
+ <seealso marker="#call/4"><c>erpc:call/4</c></seealso>.
+ That is, no <c>{erpc, timeout}</c> <c>error</c> exception
+ can be raised.
+ </p>
+ <p>
+ If the <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a connection loss),
+ the caller will not receive any further information about the
+ result if/when the applied function completes. If the applied
+ function explicitly communicates with the calling process,
+ such communication may, of course, reach the calling process.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="multicall" arity="4" since="OTP @OTP-13450@"/>
+ <name name="multicall" arity="5" since="OTP @OTP-13450@"/>
+ <fsummary>Evaluate a function call on a number of nodes.</fsummary>
+ <type name="caught_call_exception"/>
+ <type name="stack_item"/>
+ <desc>
+ <p>
+ Performs multiple <c>call</c> operations in parallel
+ on multiple nodes. The result is returned as a list where
+ the result from each node is placed at the same position
+ as the node name is placed in <c><anno>Nodes</anno></c>.
+ Each item in the resulting list is formatted as either:
+ </p>
+ <taglist>
+ <tag><c>{ok, Result}</c></tag>
+ <item><p>
+ The <c>call</c> operation for this specific node
+ returned <c>Result</c>.
+ </p></item>
+ <tag><c>{Class, ExceptionReason}</c></tag>
+ <item><p>
+ The <c>call</c> operation for this specific node
+ raised an exception of class <c>Class</c> with
+ exception reason <c>ExceptionReason</c>. These
+ corresponds the the exceptions that
+ <seealso marker="#call/5"><c>erpc:call/5</c></seealso>
+ can raise.
+ </p></item>
+ </taglist>
+ <p>
+ The call <c>erpc:multicall(<anno>Nodes</anno>, <anno>Module</anno>,
+ <anno>Function</anno>, <anno>Args</anno>)</c> is equivalent
+ to the call <c>erpc:multicall(<anno>Nodes</anno>, <anno>Module</anno>,
+ <anno>Function</anno>, <anno>Args</anno>, infinity)</c>. These
+ calls are also equivalent to calling <c>my_multicall(Nodes, Module,
+ Function, Args)</c> if one disregards performance:
+ </p>
+ <pre>
+my_multicall(Nodes, Module, Function, Args) ->
+ ReqIds = lists:map(fun (Node) ->
+ <seealso marker="#send_request/4">erpc:send_request(Node, Module, Function, Args)</seealso>
+ end,
+ Nodes),
+ lists:map(fun (ReqId) ->
+ try
+ {ok, <seealso marker="#receive_response/2">erpc:receive_response(ReqId, infinity)</seealso>}
+ catch
+ Class:Reason ->
+ {Class, Reason}
+ end
+ end,
+ ReqIds).
+</pre>
+
+ <p>
+ The <c><anno>Timeout</anno></c> value in milliseconds
+ sets an upper time limit for all <c>call</c> operations
+ to complete.
+ </p>
+
+ <p>
+ If an <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a timeout or
+ connection loss), the caller will not receive any
+ further information about the result if/when the applied
+ function completes. If the applied function communicates
+ with the calling process, such communication may, of
+ course, reach the calling process.
+ </p>
+ </desc>
+
+ </func>
+
+ <func>
+ <name name="receive_response" arity="1" since="OTP @OTP-13450@"/>
+ <name name="receive_response" arity="2" since="OTP @OTP-13450@"/>
+ <fsummary>Receive a call response corresponding to a
+ previously sent call request.</fsummary>
+ <desc>
+ <p>
+ Receive a response to a <c>call</c> request previously
+ made by the calling process using
+ <seealso marker="#send_request/4"><c>erpc:send_request/4</c></seealso>.
+ <c><anno>RequestId</anno></c> should be the value returned from
+ the previously made <c>erpc:send_request()</c> call, and
+ the corresponding response should not already have been received
+ and handled to completion by
+ <seealso marker="#check_response/2"><c>erpc:check_response()</c></seealso>,
+ <c>erpc:receive_response()</c>, or
+ <seealso marker="#wait_response/2"><c>erpc:wait_response()</c></seealso>.
+ <c><anno>Timeout</anno></c> equals the timeout time in milliseconds
+ or the atom <c>infinity</c>. The <c>call</c> operation is completed
+ once the <c>erpc:receive_response()</c> call returns or raise an
+ exception.
+ </p>
+ <p>
+ The call <c>erpc:receive_response(<anno>RequestId</anno>)</c> is
+ equivalent to the call
+ <c>erpc:receive_response(<anno>RequestId</anno>, infinity)</c>.
+ </p>
+ <p>
+ A call to the function
+ <c>my_call(Node, Module, Function, Args, Timeout)</c>
+ below is equivalent to the call
+ <seealso marker="#call/5"><c>erpc:call(Node, Module, Function, Args,
+ Timeout)</c></seealso> if one disregards performance. <c>erpc:call()</c>
+ can utilize a message queue optimization which removes the need to scan
+ the whole message queue which the combination
+ <c>erpc:send_request()/erpc:receive_response()</c> cannot.
+ </p>
+ <pre>
+my_call(Node, Module, Function, Args, Timeout) ->
+ RequestId = <seealso marker="#send_request/4">erpc:send_request(Node, Module, Function, Args)</seealso>,
+ erpc:receive_response(RequestId, Timeout).
+</pre>
+ <p>
+ If the <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a timeout, or
+ a connection loss), the caller will not receive any
+ further information about the result if/when the applied
+ function completes. If the applied function explicitly
+ communicates with the calling process, such communication
+ may, of course, reach the calling process.
+ </p>
+
+ <p>
+ <c>erpc:receive_response()</c> will return or raise exceptions the
+ same way as <seealso marker="#call/5"><c>erpc:call/5</c></seealso>
+ does.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="send_request" arity="4" since="OTP @OTP-13450@"/>
+ <fsummary>Send a request to evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ Send an asynchronous <c>call</c> request to the node
+ <c><anno>Node</anno></c>. <c>erpc:send_request()</c>
+ returns a request identifier that later is to be passed
+ as argument to either
+ <seealso marker="#receive_response/1"><c>erpc:receive_response()</c></seealso>,
+ <seealso marker="#wait_response/1"><c>erpc:wait_response()</c></seealso>,
+ or,
+ <seealso marker="#check_response/2"><c>erpc:check_response()</c></seealso>
+ in order to get the response of the call request.
+ </p>
+ <p><c>erpc:send_request()</c> fails with an <c>{erpc, badarg}</c>
+ <c>error</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not a valid
+ node name atom.</p></item>
+ <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Args</anno></c> is not a proper list
+ of terms.</p></item>
+ </list>
+ </desc>
+ </func>
+
+ <func>
+ <name name="wait_response" arity="1" since="OTP @OTP-13450@"/>
+ <name name="wait_response" arity="2" since="OTP @OTP-13450@"/>
+ <fsummary>Wait or poll for a call response corresponding to a previously
+ sent call request.</fsummary>
+ <desc>
+ <p>
+ Wait or poll for a response message to a <c>call</c> request
+ previously made by the calling process using
+ <seealso marker="#send_request/4"><c>erpc:send_request/4</c></seealso>.
+ <c><anno>RequestId</anno></c> should be the value returned from
+ the previously made <c>erpc:send_request()</c> call, and the
+ corresponding response should not already have been received and handled
+ to completion by
+ <seealso marker="#check_response/2"><c>erpc:check_response()</c></seealso>,
+ <seealso marker="#receive_response/2"><c>erpc:receive_response()</c></seealso>,
+ or <c>erpc:wait_response()</c>. <c><anno>WaitTime</anno></c> equals the
+ time to wait in milliseconds (or the atom <c>infinity</c>) during the wait.
+ </p>
+ <p>
+ The call <c>erpc:wait_response(<anno>RequestId</anno>)</c> is equivalent
+ to the call <c>erpc:wait_response(<anno>RequestId</anno>, 0)</c>. That is,
+ poll for a response message to a <c>call</c> request previously made by
+ the calling process.
+ </p>
+ <p>
+ If no response is received before <c><anno>WaitTime</anno></c> milliseconds,
+ the atom <c>no_response</c> is returned. It is valid to continue waiting
+ for a response as many times as needed up until a response has
+ been received and completed by <c>erpc:check_response()</c>,
+ <c>erpc:receive_response()</c>, or <c>erpc:wait_response()</c>. If a
+ response is received, the <c>call</c> operation is completed and either
+ the result is returned as <c>{response, Result}</c> where <c>Result</c>
+ corresponds to the value returned from the applied function or an
+ exception is raised. The exceptions that can be raised corresponds to the
+ same exceptions as can be raised by
+ <seealso marker="#call/4"><c>erpc:call/4</c></seealso>.
+ That is, no <c>{erpc, timeout}</c> <c>error</c> exception can be raised.
+ </p>
+ <p>
+ If the <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a too large wait time
+ value, or a connection loss), the caller will not receive any
+ further information about the result if/when the applied function
+ completes. If the applied function explicitly communicates with the
+ calling process, such communication may, of course, reach the
+ calling process.
+ </p>
+ </desc>
+ </func>
+
+ </funcs>
+</erlref>
+
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml
index fc25e83d40..c4073f13a2 100644
--- a/lib/kernel/doc/src/file.xml
+++ b/lib/kernel/doc/src/file.xml
@@ -202,10 +202,7 @@
<desc>
<p><c>allocate/3</c> can be used to preallocate space for a file.</p>
<p>This function only succeeds in platforms that provide this
- feature. When it succeeds, space is preallocated for the file but
- the file size might not be updated. This behaviour depends on the
- preallocation implementation. To guarantee that the file size is updated,
- truncate the file to the new size.</p>
+ feature.</p>
</desc>
</func>
<func>
@@ -939,6 +936,10 @@ f.txt: {person, "kalle", 25}.
support for POSIX <c>O_SYNC</c> or equivalent, use of the <c>sync</c>
flag causes <c>open</c> to return <c>{error, enotsup}</c>.</p>
</item>
+ <tag><c>directory</c></tag>
+ <item>
+ <p>Allows <c>open</c> to work on directories.</p>
+ </item>
</taglist>
<p>Returns:</p>
<taglist>
@@ -953,10 +954,9 @@ f.txt: {person, "kalle", 25}.
</item>
</taglist>
<p><c><anno>IoDevice</anno></c> is really the pid of the process that
- handles the file. This process is linked to the process
- that originally opened the file. If any process to which
- the <c><anno>IoDevice</anno></c> is linked terminates, the file is
- closed and the process itself is terminated.
+ handles the file. This process monitors the process that originally
+ opened the file (the owner process). If the owner process terminates,
+ the file is closed and the process itself terminates too.
An <c><anno>IoDevice</anno></c> returned from this call can be used
as an argument to the I/O functions (see
<seealso marker="stdlib:io"><c>io(3)</c></seealso>).</p>
@@ -985,8 +985,10 @@ f.txt: {person, "kalle", 25}.
</item>
<tag><c>enotdir</c></tag>
<item>
- <p>A component of the filename is not a directory. On some
- platforms, <c>enoent</c> is returned instead.</p>
+ <p>A component of the filename is not a directory, or the
+ filename itself is not a directory if <c>directory</c>
+ mode was specified. On some platforms, <c>enoent</c> is
+ returned instead.</p>
</item>
<tag><c>enospc</c></tag>
<item>
@@ -1438,7 +1440,11 @@ f.txt: {person, "kalle", 25}.
break this module's atomicity guarantees as it can race with a
concurrent call to
<seealso marker="#write_file_info/2"><c>write_file_info/1,2</c>
- </seealso></p>
+ </seealso>.</p>
+ <p>This option has no effect when the function is
+ given an I/O device instead of a file name. Use
+ <seealso marker="#open/2"><c>open/2</c></seealso> with the
+ <c>raw</c> mode to obtain a file descriptor first.</p>
<note>
<p>As file times are stored in POSIX time on most OS, it is faster to
query file information with option <c>posix</c>.</p>
diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml
index 61ac1485c1..d9194da4f2 100644
--- a/lib/kernel/doc/src/gen_sctp.xml
+++ b/lib/kernel/doc/src/gen_sctp.xml
@@ -165,7 +165,7 @@
<pre>
#sctp_assoc_change{
state = atom(),
- error = atom(),
+ error = integer(),
outbound_streams = integer(),
inbound_streams = integer(),
assoc_id = assoc_id()
@@ -208,7 +208,9 @@ connect(Socket, Ip, Port>,
<tag><c>shutdown_comp</c></tag>
<item></item>
</taglist>
- <p>Field <c>error</c> can provide more detailed diagnostics.</p>
+ <p>Field <c>error</c> can provide more detailed diagnostics.
+ The <c>error</c> field value can be converted into a string using
+ <seealso marker="#error_string/1"><c>error_string/1</c></seealso>.</p>
</desc>
</func>
diff --git a/lib/kernel/doc/src/gen_udp.xml b/lib/kernel/doc/src/gen_udp.xml
index 14819aa938..9ecadc5498 100644
--- a/lib/kernel/doc/src/gen_udp.xml
+++ b/lib/kernel/doc/src/gen_udp.xml
@@ -305,4 +305,3 @@
</func>
</funcs>
</erlref>
-
diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml
index 7f9609d5c1..7ad3d15cd6 100644
--- a/lib/kernel/doc/src/kernel_app.xml
+++ b/lib/kernel/doc/src/kernel_app.xml
@@ -414,6 +414,15 @@ MaxT = TickTime + TickTime / 4</code>
using this service.</p>
<p>Defaults to <c>false</c>.</p>
</item>
+ <tag><c>start_pg = true | false</c></tag>
+ <item>
+ <marker id="start_pg"></marker>
+ <p>Starts the default <c>pg</c> scope server (see
+ <seealso marker="pg"><c>pg(3)</c></seealso>) if
+ the parameter is <c>true</c>. This parameter is to be set to
+ <c>true</c> in an embedded system that uses this service.</p>
+ <p>Defaults to <c>false</c>.</p>
+ </item>
<tag><c>start_pg2 = true | false</c></tag>
<item>
<marker id="start_pg2"></marker>
@@ -556,6 +565,7 @@ erl -kernel logger '[{handler,default,logger_std_h,#{formatter=>{logger_formatte
<seealso marker="logger"><c>logger(3)</c></seealso>,
<seealso marker="net_kernel"><c>net_kernel(3)</c></seealso>,
<seealso marker="os"><c>os(3)</c></seealso>,
+ <seealso marker="pg"><c>pg(3)</c></seealso>,
<seealso marker="pg2"><c>pg2(3)</c></seealso>,
<seealso marker="rpc"><c>rpc(3)</c></seealso>,
<seealso marker="seq_trace"><c>seq_trace(3)</c></seealso>,
diff --git a/lib/kernel/doc/src/net.xml b/lib/kernel/doc/src/net.xml
index d60e1af311..d7732e714a 100644
--- a/lib/kernel/doc/src/net.xml
+++ b/lib/kernel/doc/src/net.xml
@@ -122,7 +122,7 @@
<funcs>
<func>
- <name name="gethostname" arity="0"/>
+ <name name="gethostname" arity="0" since="OTP 22.0"/>
<fsummary>Get hostname.</fsummary>
<desc>
<p>Returns the name of the current host.</p>
diff --git a/lib/kernel/doc/src/pg.xml b/lib/kernel/doc/src/pg.xml
new file mode 100644
index 0000000000..f04d9561e9
--- /dev/null
+++ b/lib/kernel/doc/src/pg.xml
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<!-- %ExternalCopyright% -->
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2020</year><year>2020</year>
+ <holder>Maxim Fedorov, WhatsApp Inc.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>pg</title>
+ <prepared>maximfca@gmail.com</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date></date>
+ <rev>A</rev>
+ <file>pg.xml</file>
+ </header>
+ <module since="OTP 23.0">pg</module>
+ <modulesummary>Distributed named process groups.</modulesummary>
+ <description>
+ <p>This module implements process groups. A message can be sent
+ to one, some, or all group members.</p>
+
+ <p>Up until OTP 17 there used to exist an experimental <c>pg</c>
+ module in <c>stdlib</c>. This <c>pg</c> module is not the same
+ module as that experimental <c>pg</c> module, and only share
+ the same module name.</p>
+
+ <p>A group of processes can be accessed by a common name. For
+ example, if there is a group named <c>foobar</c>, there can be a
+ set of processes (which can be located on different nodes) that
+ are all members of the group <c>foobar</c>. There are no special
+ functions for sending a message to the group. Instead, client
+ functions are to be written with the functions
+ <seealso marker="#get_members/1"><c>get_members/1</c></seealso> and
+ <seealso marker="#get_local_members/1"><c>get_local_members/1</c></seealso>
+ to determine which processes are members of the group.
+ Then the message can be sent to one or more group members.</p>
+ <p>If a member terminates, it is automatically removed from the group.</p>
+
+ <p>A process may join multiple groups. It may join the same group multiple times.
+ It is only allowed to join processes running on local node.
+ </p>
+
+ <p>Process Groups implement strong eventual consistency.
+ Unlike <seealso marker="pg2"><c>pg2</c></seealso>, that provides
+ strong ordering guarantees, Process Groups membership view may temporarily
+ diverge. For example, when processes on <c>node1</c> and <c>node2</c>
+ join concurrently, <c>node3</c> and <c>node4</c> may receive updates in
+ a different order.</p>
+
+ <p> Membership view is not transitive. If <c>node1</c> is not directly
+ connected to <c>node2</c>, they will not see each other groups. But if
+ both are connected to <c>node3</c>, <c>node3</c> will have the full view. </p>
+
+ <p>Groups are automatically created when any process joins,
+ and are removed when all processes leave the group. Non-existing group is
+ considered empty (containing no processes).</p>
+
+ <p>Process groups can be organised into multiple scopes. Scopes are
+ completely independent of each other. A process may join any
+ number of groups in any number of scopes. Scopes are designed to
+ decouple single mesh into a set of overlay networks, reducing
+ amount of traffic required to propagate group membership
+ information. Default scope <c>pg</c> is started automatically
+ when <seealso marker="kernel_app#start_pg"><c>kernel(6)</c></seealso>
+ is configured to do so.
+ </p>
+
+ <note><p>
+ Scope name is used to register process locally, and to name an ETS table.
+ If there is another process registered under this name, or another ETS table
+ exists, scope fails to start.</p>
+ <p>Local membership is not preserved if scope process exits and
+ restarts. This behaviour is different from
+ <seealso marker="pg2"><c>pg2</c></seealso>, that recovers
+ local membership from remote nodes.
+ </p></note>
+
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="group"/>
+ <desc><p>The identifier of a process group.</p></desc>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+
+ <func>
+ <name name="start_link" arity="0" since="OTP 23.0"/>
+ <fsummary>Start the default <c>pg</c> scope.</fsummary>
+ <desc>
+ <p>Starts the default <c>pg</c> scope within supervision tree.
+ Kernel may be configured to do it automatically, see
+ <seealso marker="kernel_app#start_pg"><c>kernel(6)</c></seealso>
+ configuration manual.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="start" arity="1" since="OTP 23.0"/>
+ <name name="start_link" arity="1" since="OTP 23.0"/>
+ <fsummary>Start additional scope.</fsummary>
+ <desc>
+ <p>Starts additional scope.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="join" arity="2" since="OTP 23.0"/>
+ <name name="join" arity="3" since="OTP 23.0"/>
+ <fsummary>Join a process or a list of processes to a group.</fsummary>
+ <desc>
+ <p>Joins single process or multiple processes to the
+ group <c>Name</c>. A process can join a group many times and
+ must then leave the group the same number of times.</p>
+ <p><c>PidOrPids</c> may contain the same process multiple times.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="leave" arity="2" since="OTP 23.0"/>
+ <name name="leave" arity="3" since="OTP 23.0"/>
+ <fsummary>Make a process leave a group.</fsummary>
+ <desc>
+ <p>Makes the process <c>PidOrPids</c> leave the group <c>Name</c>.
+ If the process is not a member of the group, <c>not_joined</c> is
+ returned.</p>
+ <p>When list of processes is passed as <c>PidOrPids</c>, function
+ returns <c>not_joined</c> only when all processes of the list
+ are not joined.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="get_local_members" arity="1" since="OTP 23.0"/>
+ <name name="get_local_members" arity="2" since="OTP 23.0"/>
+ <fsummary>Return all local processes in a group.</fsummary>
+ <desc>
+ <p>Returns all processes running on the local node in the
+ group <c>Name</c>. Processes are returned in no specific order.
+ This function is optimised for speed.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="get_members" arity="1" since="OTP 23.0"/>
+ <name name="get_members" arity="2" since="OTP 23.0"/>
+ <fsummary>Return all processes in a group.</fsummary>
+ <desc>
+ <p>Returns all processes in the group <c>Name</c>.
+ Processes are returned in no specific order.
+ This function is optimised for speed.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="which_groups" arity="0" since="OTP 23.0"/>
+ <name name="which_groups" arity="1" since="OTP 23.0"/>
+ <fsummary>Return a list of all known groups.</fsummary>
+ <desc>
+ <p>Returns a list of all known groups.</p>
+ </desc>
+ </func>
+
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="kernel_app"><c>kernel(6)</c></seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/kernel/doc/src/pg2.xml b/lib/kernel/doc/src/pg2.xml
index 058d711756..510dea0b92 100644
--- a/lib/kernel/doc/src/pg2.xml
+++ b/lib/kernel/doc/src/pg2.xml
@@ -35,6 +35,16 @@
<module since="">pg2</module>
<modulesummary>Distributed named process groups.</modulesummary>
<description>
+ <warning>
+ <p>
+ The <c>pg2</c> module is deprecated as of OTP 23 and scheduled
+ for removal in OTP 24. You are advised to replace the usage of
+ <c>pg2</c> with <seealso marker="kernel:pg">pg</seealso>.
+ <c>pg</c> has a similar API, but with an implementation that
+ is more scalable. See the documentation of <c>pg</c> for more
+ information about differences.
+ </p>
+ </warning>
<p>This module implements process groups. Each message can be sent
to one, some, or all group members.</p>
<p>A group of processes can be accessed by a common name. For
diff --git a/lib/kernel/doc/src/ref_man.xml b/lib/kernel/doc/src/ref_man.xml
index 9df51dee22..9127157eb5 100644
--- a/lib/kernel/doc/src/ref_man.xml
+++ b/lib/kernel/doc/src/ref_man.xml
@@ -43,6 +43,7 @@
<xi:include href="erl_epmd.xml"/>
<xi:include href="erl_prim_loader_stub.xml"/>
<xi:include href="erlang_stub.xml"/>
+ <xi:include href="erpc.xml"/>
<xi:include href="error_handler.xml"/>
<xi:include href="error_logger.xml"/>
<xi:include href="file.xml"/>
@@ -64,6 +65,7 @@
<xi:include href="net_adm.xml"/>
<xi:include href="net_kernel.xml"/>
<xi:include href="os.xml"/>
+ <xi:include href="pg.xml"/>
<xi:include href="pg2.xml"/>
<xi:include href="rpc.xml"/>
<xi:include href="seq_trace.xml"/>
diff --git a/lib/kernel/doc/src/rpc.xml b/lib/kernel/doc/src/rpc.xml
index 0e07d334d8..03bfc97c5b 100644
--- a/lib/kernel/doc/src/rpc.xml
+++ b/lib/kernel/doc/src/rpc.xml
@@ -37,13 +37,29 @@
a function on a remote node and collect the answer. It is used
for collecting information on a remote node, or for running a
function with some specific side effects on the remote node.</p>
+ <note><p>
+ <c>rpc:call()</c> and friends makes it quite hard to distinguish
+ between successful results, raised exceptions, and other errors.
+ This cannot be changed due to compatibility reasons. As of OTP 23,
+ a new module <seealso marker="erpc"><c>erpc</c></seealso> was
+ introduced in order to provide an API that makes it possible
+ to distingush between the different results. The <c>erpc</c>
+ module provides a subset (however, the central subset) of the
+ functionality available in the <c>rpc</c> module. The <c>erpc</c>
+ implementation also provides a more scalable implementation with
+ better performance than the original <c>rpc</c> implementation.
+ However, since the introduction of <c>erpc</c>, the <c>rpc</c>
+ module implements large parts of its central functionality using
+ <c>erpc</c>, so the <c>rpc</c> module wont not suffer scalability
+ wise and performance wise compared to <c>erpc</c>.
+ </p></note>
</description>
<datatypes>
<datatype>
<name name="key"/>
<desc>
- <p>As returned by
+ <p>Opaque value returned by
<seealso marker="#async_call/4"><c>async_call/4</c></seealso>.</p>
</desc>
</datatype>
@@ -87,7 +103,17 @@
<seealso marker="#nb_yield/1"><c>nb_yield/1,2</c></seealso>
to retrieve the value of evaluating <c>apply(<anno>Module</anno>,
<anno>Function</anno>, <anno>Args</anno>)</c> on node
- <c><anno>Node</anno></c>.</p>
+ <c><anno>Node</anno></c>.</p>
+ <note>
+ <p>
+ If you want the ability to distinguish between results,
+ you may want to consider using the
+ <seealso marker="erpc#send_request/4"><c>erpc:send_request()</c></seealso>
+ function from the <c>erpc</c> module instead. This also
+ gives you the ability retrieve the results in other useful
+ ways.
+ </p>
+ </note>
<note>
<p><seealso marker="#yield/1"><c>yield/1</c></seealso> and
<seealso marker="#nb_yield/1"><c>nb_yield/1,2</c></seealso>
@@ -99,34 +125,34 @@
<func>
<name name="block_call" arity="4" since=""/>
- <fsummary>Evaluate a function call on a node in the RPC server's
- context.</fsummary>
- <desc>
- <p>Same as <seealso marker="#call/4"><c>call/4</c></seealso>,
- but the RPC server at <c><anno>Node</anno></c> does
- not create a separate process to handle the call. Thus,
- this function can be used if the intention of the call is to
- block the RPC server from any other incoming requests until
- the request has been handled. The function can also be used
- for efficiency reasons when very small fast functions are
- evaluated, for example, BIFs that are guaranteed not to
- suspend.</p>
- <p>See the note in <seealso marker="#call/4"><c>call/4</c></seealso>
- for more details of the return value.</p>
+ <fsummary>Evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ The same as calling
+ <seealso marker="#block_call/5"><c>rpc:block_call(<anno>Node</anno>,
+ <anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>, infinity)</c></seealso>.
+ </p>
</desc>
</func>
<func>
<name name="block_call" arity="5" since=""/>
- <fsummary>Evaluate a function call on a node in the RPC server's
- context.</fsummary>
+ <fsummary>Evaluate a function call on a node.</fsummary>
<desc>
- <p>Same as
- <seealso marker="#block_call/4"><c>block_call/4</c></seealso>,
- but with a time-out value in the same manner as
- <seealso marker="#call/5"><c>call/5</c></seealso>.</p>
- <p>See the note in <seealso marker="#call/4"><c>call/4</c></seealso>
- for more details of the return value.</p>
+ <p>
+ The same as calling
+ <seealso marker="#call/5"><c>rpc:call(<anno>Node</anno>,
+ <anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>, <anno>Timeout</anno>)</c></seealso> with
+ the exception that it also blocks other <c>rpc:block_call()</c>
+ operations from executing concurrently on the node
+ <c><anno>Node</anno></c>.
+ </p>
+ <warning><p>
+ Note that it also blocks other operations than just
+ <c>rpc:block_call()</c> operations, so use it with care.
+ </p></warning>
</desc>
</func>
@@ -135,9 +161,38 @@
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>)</c> on node <c><anno>Node</anno></c> and returns
+ the corresponding value <c><anno>Res</anno></c>, or
+ <c>{badrpc, <anno>Reason</anno>}</c> if the call fails.
+ The same as calling
+ <seealso marker="#call/5"><c>rpc:call(<anno>Node</anno>,
+ <anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>, infinity)</c></seealso>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="call" arity="5" since=""/>
+ <fsummary>Evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
<anno>Args</anno>)</c> on node <c><anno>Node</anno></c> and returns
the corresponding value <c><anno>Res</anno></c>, or
- <c>{badrpc, <anno>Reason</anno>}</c> if the call fails.</p>
+ <c>{badrpc, <anno>Reason</anno>}</c> if the call fails.
+ <c><anno>Timeout</anno></c> is
+ a time-out value in milliseconds. If the call times out,
+ <c><anno>Reason</anno></c> is <c>timeout</c>.</p>
+ <p>If the reply arrives after the call times out, no message
+ contaminates the caller's message queue.</p>
+ <note>
+ <p>
+ If you want the ability to distinguish between results,
+ you may want to consider using the
+ <seealso marker="erpc#call/4"><c>erpc:call()</c></seealso>
+ function from the <c>erpc</c> module instead.
+ </p>
+ </note>
<note>
<p>Here follows the details of what exactly is returned.</p>
<p><c>{badrpc, <anno>Reason</anno>}</c> will be returned in the
@@ -159,28 +214,14 @@
<strong>not</strong> match <c>{'EXIT',_}</c>.</item>
</list>
</note>
- </desc>
- </func>
-
- <func>
- <name name="call" arity="5" since=""/>
- <fsummary>Evaluate a function call on a node.</fsummary>
- <desc>
- <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
- <anno>Args</anno>)</c> on node <c><anno>Node</anno></c> and returns
- the corresponding value <c><anno>Res</anno></c>, or
- <c>{badrpc, <anno>Reason</anno>}</c> if the call fails.
- <c><anno>Timeout</anno></c> is
- a time-out value in milliseconds. If the call times out,
- <c><anno>Reason</anno></c> is <c>timeout</c>.
- See the note in <seealso marker="#call/4"><c>call/4</c></seealso>
- for more details of the return value.</p>
- <p>If the reply arrives after the call times out, no message
- contaminates the caller's message queue, as this
- function spawns off a middleman process to act as (a void)
- destination for such an orphan reply. This feature also makes
- this function more expensive than <c>call/4</c> at
- the caller's end.</p>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be the calling process itself, an <c>rpc</c> server,
+ another server, or a freshly spawned process.
+ </p>
+ </note>
</desc>
</func>
@@ -194,6 +235,14 @@
process is not suspended until the evaluation is complete, as
is the case with
<seealso marker="#call/4"><c>call/4,5</c></seealso>.</p>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be an <c>rpc</c> server, another server, or a
+ freshly spawned process.
+ </p>
+ </note>
</desc>
</func>
@@ -226,7 +275,7 @@
<anno>Name</anno>, <anno>Msg</anno>)</c>.</p>
</desc>
</func>
-
+
<func>
<name name="multi_server_call" arity="3" since=""/>
<fsummary>Interact with the servers on a number of nodes.</fsummary>
@@ -311,6 +360,14 @@
{ResL, _} = rpc:multicall(code, load_binary, [Mod, File, Bin]),
%% and then maybe check the ResL list.</code>
+ <note>
+ <p>
+ If you want the ability to distinguish between results,
+ you may want to consider using the
+ <seealso marker="erpc#multicall/4"><c>erpc:multicall()</c></seealso>
+ function from the <c>erpc</c> module instead.
+ </p>
+ </note>
</desc>
</func>
diff --git a/lib/kernel/doc/src/seq_trace.xml b/lib/kernel/doc/src/seq_trace.xml
index 9aef748594..059f03593f 100644
--- a/lib/kernel/doc/src/seq_trace.xml
+++ b/lib/kernel/doc/src/seq_trace.xml
@@ -29,10 +29,11 @@
<rev>A</rev>
</header>
<module since="">seq_trace</module>
- <modulesummary>Sequential tracing of messages.</modulesummary>
+ <modulesummary>Sequential tracing of information transfers.</modulesummary>
<description>
- <p>Sequential tracing makes it possible to trace all messages
- resulting from one initial message. Sequential tracing is
+ <p>Sequential tracing makes it possible to trace information
+ flows between processes resulting from one initial transfer
+ of information. Sequential tracing is
independent of the ordinary tracing in Erlang, which
is controlled by the <c>erlang:trace/3</c> BIF. For more information
about what sequential tracing is and how it can be used, see section
@@ -104,13 +105,13 @@ seq_trace:set_token(OldToken), % activate the trace token again
<tag><c>set_token(send, <anno>Bool</anno>)</c></tag>
<item>
<p>A trace token flag (<c>true | false</c>) which
- enables/disables tracing on message sending. Default is
+ enables/disables tracing on information sending. Default is
<c>false</c>.</p>
</item>
<tag><c>set_token('receive', <anno>Bool</anno>)</c></tag>
<item>
<p>A trace token flag (<c>true | false</c>) which
- enables/disables tracing on message reception. Default is
+ enables/disables tracing on information reception. Default is
<c>false</c>.</p>
</item>
<tag><c>set_token(print, <anno>Bool</anno>)</c></tag>
@@ -257,12 +258,26 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
<tag><c>{send, Serial, From, To, Message}</c></tag>
<item>
<p>Used when a process <c>From</c> with its trace token flag
- <c>send</c> set to <c>true</c> has sent a message.</p>
+ <c>send</c> set to <c>true</c> has sent information. <c>To</c>
+ may be a process identifier, a registered name on a node
+ represented as <c>{NameAtom, NodeAtom}</c>, or a node name
+ represented as an atom. <c>From</c> may be a process identifier
+ or a node name represented as an atom. <c>Message</c> contains
+ the information passed along in this information transfer. If
+ the transfer is done via message passing, it is the actual
+ message.
+ </p>
</item>
<tag><c>{'receive', Serial, From, To, Message}</c></tag>
<item>
- <p>Used when a process <c>To</c> receives a message with a
- trace token that has flag <c>'receive'</c> set to <c>true</c>.</p>
+ <p>Used when a process <c>To</c> receives information with a
+ trace token that has flag <c>'receive'</c> set to <c>true</c>.
+ <c>To</c> may be a process identifier, or a node name
+ represented as an atom. <c>From</c> may be a process identifier
+ or a node name represented as an atom. <c>Message</c> contains
+ the information passed along in this information transfer. If
+ the transfer is done via message passing, it is the actual
+ message.</p>
</item>
<tag><c>{print, Serial, From, _, Info}</c></tag>
<item>
@@ -276,7 +291,7 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
where:</p>
<list type="bulleted">
<item><p>Integer <c>PreviousSerial</c> denotes the serial
- counter passed in the last received message that carried a trace
+ counter passed in the last received information that carried a trace
token. If the process is the first in a new sequential trace,
<c>PreviousSerial</c> is set to the value of the process internal
"trace clock".</p></item>
@@ -290,22 +305,32 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
<section>
<marker id="whatis"></marker>
<title>Sequential Tracing</title>
- <p>Sequential tracing is a way to trace a sequence of messages sent
- between different local or remote processes, where the sequence
- is initiated by a single message. In short, it works as follows:</p>
+ <p>Sequential tracing is a way to trace a sequence of information
+ transfers between different local or remote processes, where the
+ sequence is initiated by a single transfer. The typical information
+ transfer is an ordinary Erlang message passed between two processes,
+ but information is transferred also in other ways. In short, it works
+ as follows:</p>
<p>Each process has a <em>trace token</em>, which can be empty or
not empty. When not empty, the trace token can be seen as
the tuple <c>{Label, Flags, Serial, From}</c>. The trace token is
- passed invisibly with each message.</p>
+ passed invisibly when information is passed between processes.
+ In most cases the information is passed in ordinary messages
+ between processes, but information is also passed between processes
+ by other means. For example, by spawning a new process. An information
+ transfer between two processes is represented by a send event and a
+ receive event regardless of how it is passed.
+ </p>
<p>To start a sequential trace, the user must explicitly set
- the trace token in the process that will send the first message
+ the trace token in the process that will send the first information
in a sequence.</p>
<p>The trace token of a process is set each time the process
+ receives information. This is typically when the process
matches a message in a receive statement, according to the trace
token carried by the received message, empty or not.</p>
<p>On each Erlang node, a process can be set as the <em>system tracer</em>.
This process will receive trace messages each time
- a message with a trace token is sent or received (if the trace
+ information with a trace token is sent or received (if the trace
token flag <c>send</c> or <c>'receive'</c> is set). The system
tracer can then print each trace event, write it to a file, or
whatever suitable.</p>
@@ -321,11 +346,58 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
</section>
<section>
+ <title>Different Information Transfers</title>
+ <p>
+ Information flows between processes in a lot of different
+ ways. Not all flows of information will be covered by
+ sequential tracing. One example is information passed via
+ ETS tables. Below is a list of information paths that are
+ covered by sequential tracing:</p>
+ <taglist>
+ <tag>Message Passing</tag>
+ <item><p>
+ All ordinary messages passed between Erlang processes.
+ </p></item>
+ <tag>Exit signals</tag>
+ <item><p>
+ An exit signal is represented as an <c>{'EXIT', Pid, Reason}</c>
+ tuple.
+ </p></item>
+ <tag>Process Spawn</tag>
+ <item><p>
+ A process spawn is represented as multiple information
+ transfers. At least one spawn request and one spawn reply. The
+ actual amount of information transfers depends on what type
+ of spawn it is and may also change in future implementations.
+ Note that this is more or less an internal protocol that you
+ are peeking at. The spawn request will be represented as a
+ tuple with the first element containing the atom
+ <c>spawn_request</c>, but this is more or less all that you
+ can depend on.
+ </p></item>
+ </taglist>
+ <note>
+ <p>
+ If you do ordinary <c>send</c> or <c>receive</c>
+ trace on the system, you will only see ordinary message
+ passing, not the other information transfers listed above.
+ </p>
+ </note>
+ <note>
+ <p>
+ When a send event and corresponding receive event do not
+ both correspond to ordinary Erlang messages, the <c>Message</c>
+ part of the trace messages may not be identical. This since
+ all information not necessarily are available when generating
+ the trace messages.
+ </p>
+ </note>
+ </section>
+
+ <section>
<title>Trace Token</title>
- <p>Each process has a current trace token. Initially, the token is
- empty. When the process sends a message to another process, a
- copy of the current token is sent "invisibly" along with
- the message.</p>
+ <p>Each process has a current trace token which is "invisibly" passed
+ from the parent process on creation of the process.</p>
<p>The current token of a process is set in one of the following two
ways:</p>
<list type="bulleted">
@@ -334,7 +406,9 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
<c>seq_trace:set_token/1,2</c></p>
</item>
<item>
- <p>When a message is received</p>
+ <p>When information is received. This is typically when
+ a received message is matched out in a receive expression,
+ but also when information is received in other ways.</p>
</item>
</list>
<p>In both cases, the current token is set. In particular, if
@@ -354,12 +428,16 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
<p>The algorithm for updating <c>Serial</c> can be described as
follows:</p>
<p>Let each process have two counters, <c>prev_cnt</c> and
- <c>curr_cnt</c>, both are set to <c>0</c> when a process is created.
- The counters are updated at the following occasions:</p>
+ <c>curr_cnt</c>, both are set to <c>0</c> when a process is created
+ outside of a trace sequence. The counters are updated at the following
+ occasions:</p>
<list type="bulleted">
<item>
- <p><em>When the process is about to send a message and the trace token
- is not empty.</em></p>
+ <p><em>When the process is about to pass along information to
+ another process and the trace token is not empty.</em> This
+ typically occurs when sending a message, but also, for example,
+ when spawning another process.
+ </p>
<p>Let the serial of the trace token be <c>tprev</c> and
<c>tcurr</c>.</p>
<pre>
@@ -367,7 +445,7 @@ curr_cnt := curr_cnt + 1
tprev := prev_cnt
tcurr := curr_cnt</pre>
<p>The trace token with <c>tprev</c> and <c>tcurr</c> is then
- passed along with the message.</p>
+ passed along with the information passed to the other process.</p>
</item>
<item>
<p><em>When the process calls</em> <c>seq_trace:print(Label, Info)</c>,
@@ -376,8 +454,9 @@ tcurr := curr_cnt</pre>
<p>The algorithm is the same as for send above.</p>
</item>
<item>
- <p><em>When a message is received and contains a non-empty trace
- token.</em></p>
+ <p><em>When information is received that also contains a non-empty
+ trace token. For example, when a message is matched out in a
+ receive expression, or when a new process is spawned.</em></p>
<p>The process trace token is set to the trace token from
the message.</p>
<p>Let the serial of the trace token be <c>tprev</c> and
@@ -487,9 +566,9 @@ tracer() ->
print_trace(Label,TraceInfo,false);
{seq_trace,Label,TraceInfo,Ts} ->
print_trace(Label,TraceInfo,Ts);
- Other -> ignore
+ _Other -> ignore
end,
- tracer().
+ tracer().
print_trace(Label,TraceInfo,false) ->
io:format("~p:",[Label]),
@@ -504,7 +583,7 @@ print_trace({'receive',Serial,From,To,Message}) ->
io:format("~p Received ~p FROM ~p WITH~n~p~n",
[To,Serial,From,Message]);
print_trace({send,Serial,From,To,Message}) ->
- io:format("~p Sent ~p TO ~p WITH~n~p~n",
+ io:format("~p Sent ~p TO ~p WITH~n~p~n",
[From,Serial,To,Message]).</code>
<p>The code that creates a process that runs this tracer function
and sets that process as the system tracer can look like this:</p>
diff --git a/lib/kernel/doc/src/specs.xml b/lib/kernel/doc/src/specs.xml
index 9e258910db..00f6f04218 100644
--- a/lib/kernel/doc/src/specs.xml
+++ b/lib/kernel/doc/src/specs.xml
@@ -9,6 +9,7 @@
<xi:include href="../specs/specs_erl_epmd.xml"/>
<xi:include href="../specs/specs_erl_prim_loader_stub.xml"/>
<xi:include href="../specs/specs_erlang_stub.xml"/>
+ <xi:include href="../specs/specs_erpc.xml"/>
<xi:include href="../specs/specs_error_handler.xml"/>
<xi:include href="../specs/specs_error_logger.xml"/>
<xi:include href="../specs/specs_file.xml"/>
@@ -30,6 +31,7 @@
<xi:include href="../specs/specs_net_adm.xml"/>
<xi:include href="../specs/specs_net_kernel.xml"/>
<xi:include href="../specs/specs_os.xml"/>
+ <xi:include href="../specs/specs_pg.xml"/>
<xi:include href="../specs/specs_pg2.xml"/>
<xi:include href="../specs/specs_rpc.xml"/>
<xi:include href="../specs/specs_seq_trace.xml"/>
diff --git a/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl b/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl
index bc2e07ddf5..f4eb12818d 100644
--- a/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl
+++ b/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl
@@ -400,6 +400,10 @@ is_node_name(_Node) ->
hs_data_common(DistCtrl) ->
TickHandler = call_ctrlr(DistCtrl, tick_handler),
Socket = call_ctrlr(DistCtrl, socket),
+ RejectFlags = case init:get_argument(gen_tcp_dist_reject_flags) of
+ {ok,[[Flags]]} -> list_to_integer(Flags);
+ _ -> #hs_data{}#hs_data.reject_flags
+ end,
#hs_data{f_send = send_fun(),
f_recv = recv_fun(),
f_setopts_pre_nodeup = setopts_pre_nodeup_fun(),
@@ -410,7 +414,8 @@ hs_data_common(DistCtrl) ->
mf_setopts = setopts_fun(DistCtrl, Socket),
mf_getopts = getopts_fun(DistCtrl, Socket),
mf_getstat = getstat_fun(DistCtrl, Socket),
- mf_tick = tick_fun(DistCtrl, TickHandler)}.
+ mf_tick = tick_fun(DistCtrl, TickHandler),
+ reject_flags = RejectFlags}.
%%% ------------------------------------------------------------
%%% Distribution controller processes
diff --git a/lib/kernel/include/dist.hrl b/lib/kernel/include/dist.hrl
index f06fc328d7..3cc825fca6 100644
--- a/lib/kernel/include/dist.hrl
+++ b/lib/kernel/include/dist.hrl
@@ -44,7 +44,18 @@
-define(DFLAG_BIG_SEQTRACE_LABELS, 16#100000).
%% -define(DFLAG_NO_MAGIC, 16#200000). %% Used internally only
-define(DFLAG_EXIT_PAYLOAD, 16#400000).
--define(DFLAG_FRAGMENTS, 16#800000).
+-define(DFLAG_FRAGMENTS, 16#00800000).
+-define(DFLAG_HANDSHAKE_23, 16#01000000).
+-define(DFLAG_RESERVED, 16#fe000000).
+-define(DFLAG_SPAWN, 16#100000000).
%% Also update dflag2str() in ../src/dist_util.erl
%% when adding flags...
+
+
+-define(ERL_DIST_VER_5, 5). % OTP-22 or (much) older
+-define(ERL_DIST_VER_6, 6). % OTP-23 (or maybe newer?)
+
+-define(ERL_DIST_VER_LOW, ?ERL_DIST_VER_5).
+-define(ERL_DIST_VER_HIGH, ?ERL_DIST_VER_6).
+
diff --git a/lib/kernel/include/dist_util.hrl b/lib/kernel/include/dist_util.hrl
index 56f775f060..05c7eee795 100644
--- a/lib/kernel/include/dist_util.hrl
+++ b/lib/kernel/include/dist_util.hrl
@@ -84,7 +84,10 @@
f_handshake_complete, %% Notify handshake complete
add_flags, %% dflags to add
reject_flags, %% dflags not to use (not all can be rejected)
- require_flags %% dflags that are required
+ require_flags, %% dflags that are required
+
+ %% New in kernel-@master@ (OTP-23.0)
+ other_creation
}).
diff --git a/lib/kernel/include/eep48.hrl b/lib/kernel/include/eep48.hrl
new file mode 100644
index 0000000000..2ce9a1430a
--- /dev/null
+++ b/lib/kernel/include/eep48.hrl
@@ -0,0 +1,14 @@
+-define(NATIVE_FORMAT,<<"application/erlang+html">>).
+-define(CURR_DOC_VERSION, {1,0,0}).
+-record(docs_v1, {anno,
+ beam_language = erlang,
+ format = ?NATIVE_FORMAT,
+ module_doc,
+ metadata = #{ otp_doc_vsn => ?CURR_DOC_VERSION },
+ docs}).
+
+-record(docs_v1_entry, {kind_name_arity,
+ anno,
+ signature,
+ doc,
+ metadata}).
diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile
index 2d2b84c206..a5e24c9d67 100644
--- a/lib/kernel/src/Makefile
+++ b/lib/kernel/src/Makefile
@@ -73,6 +73,7 @@ MODULES = \
erl_epmd \
erl_reply \
erl_signal_handler \
+ erpc \
erts_debug \
error_handler \
error_logger \
@@ -128,6 +129,7 @@ MODULES = \
net_adm \
net_kernel \
os \
+ pg \
pg2 \
ram_file \
rpc \
@@ -147,7 +149,8 @@ MODULES = \
HRL_FILES= ../include/file.hrl ../include/inet.hrl ../include/inet_sctp.hrl \
../include/dist.hrl ../include/dist_util.hrl \
- ../include/net_address.hrl ../include/logger.hrl
+ ../include/net_address.hrl ../include/logger.hrl ../include/eep48.hrl
+
INTERNAL_HRL_FILES= application_master.hrl disk_log.hrl \
erl_epmd.hrl file_int.hrl hipe_ext_format.hrl \
diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl
index 7715dca7c6..3e1a49733a 100644
--- a/lib/kernel/src/application_controller.erl
+++ b/lib/kernel/src/application_controller.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. 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,9 @@
-export([handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3, init_starter/4, get_loaded/1]).
+%% logger callback
+-export([format_log/1, format_log/2]).
+
%% Test exports, only to be used from the test suites
-export([test_change_apps/2]).
@@ -1804,7 +1807,7 @@ check_conf() ->
case init:get_argument(config) of
{ok, Files} ->
{ok, lists:foldl(
- fun([File], Env) ->
+ fun(File, Env) ->
BFName = filename:basename(File,".config"),
FName = filename:join(filename:dirname(File),
BFName ++ ".config"),
@@ -1836,7 +1839,7 @@ check_conf() ->
{error, {Line, _Mod, Str}} ->
throw({error, {FName, Line, Str}})
end
- end, [], Files)};
+ end, [], lists:append(Files))};
_ -> {ok, []}
end.
@@ -1930,9 +1933,12 @@ info_started(Name, Node) ->
report=>[{application, Name},
{started_at, Node}]},
#{domain=>[otp,sasl],
- report_cb=>fun logger:format_otp_report/1,
+ report_cb=>fun application_controller:format_log/2,
logger_formatter=>#{title=>"PROGRESS REPORT"},
- error_logger=>#{tag=>info_report,type=>progress}}).
+ error_logger=>#{tag=>info_report,
+ type=>progress,
+ report_cb=>
+ fun application_controller:format_log/1}}).
info_exited(Name, Reason, Type) ->
?LOG_NOTICE(#{label=>{application_controller,exit},
@@ -1940,8 +1946,140 @@ info_exited(Name, Reason, Type) ->
{exited, Reason},
{type, Type}]},
#{domain=>[otp],
- report_cb=>fun logger:format_otp_report/1,
- error_logger=>#{tag=>info_report,type=>std_info}}).
+ report_cb=>fun application_controller:format_log/2,
+ error_logger=>#{tag=>info_report,
+ type=>std_info,
+ report_cb=>
+ fun application_controller:format_log/1}}).
+
+%% format_log/1 is the report callback used by Logger handler
+%% error_logger only. It is kept for backwards compatibility with
+%% legacy error_logger event handlers. This function must always
+%% return {Format,Args} compatible with the arguments in this module's
+%% calls to error_logger prior to OTP-21.0.
+format_log(LogReport) ->
+ Depth = error_logger:get_format_depth(),
+ FormatOpts = #{chars_limit => unlimited,
+ depth => Depth,
+ single_line => false,
+ encoding => utf8},
+ format_log_multi(limit_report(LogReport, Depth), FormatOpts).
+
+limit_report(LogReport, unlimited) ->
+ LogReport;
+limit_report(#{label:={application_controller,progress},
+ report:=[{application,_}=Application,
+ {started_at,Node}]}=LogReport,
+ Depth) ->
+ LogReport#{report=>[Application,
+ {started_at,io_lib:limit_term(Node, Depth)}]};
+limit_report(#{label:={application_controller,exit},
+ report:=[{application,_}=Application,
+ {exited,Reason},{type,Type}]}=LogReport,
+ Depth) ->
+ LogReport#{report=>[Application,
+ {exited,io_lib:limit_term(Reason, Depth)},
+ {type,io_lib:limit_term(Type, Depth)}]}.
+
+%% format_log/2 is the report callback for any Logger handler, except
+%% error_logger.
+format_log(Report, FormatOpts0) ->
+ Default = #{chars_limit => unlimited,
+ depth => unlimited,
+ single_line => false,
+ encoding => utf8},
+ FormatOpts = maps:merge(Default, FormatOpts0),
+ IoOpts =
+ case FormatOpts of
+ #{chars_limit:=unlimited} ->
+ [];
+ #{chars_limit:=Limit} ->
+ [{chars_limit,Limit}]
+ end,
+ {Format,Args} = format_log_single(Report, FormatOpts),
+ io_lib:format(Format, Args, IoOpts).
+
+format_log_single(#{label:={application_controller,progress},
+ report:=[{application,Name},{started_at,Node}]},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format = "Application: "++P++". Started at: "++P++".",
+ Args =
+ case Depth of
+ unlimited ->
+ [Name,Node];
+ _ ->
+ [Name,Depth,Node,Depth]
+ end,
+ {Format,Args};
+format_log_single(#{label:={application_controller,exit},
+ report:=[{application,Name},
+ {exited,Reason},
+ {type,Type}]},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format = lists:append(["Application: ",P,". Exited: ",P,
+ ". Type: ",P,"."]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Name,Reason,Type];
+ _ ->
+ [Name,Depth,Reason,Depth,Type,Depth]
+ end,
+ {Format,Args};
+format_log_single(Report,FormatOpts) ->
+ format_log_multi(Report,FormatOpts).
+
+format_log_multi(#{label:={application_controller,progress},
+ report:=[{application,Name},
+ {started_at,Node}]},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ lists:append(
+ [" application: ",P,"~n",
+ " started_at: ",P,"~n"]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Name,Node];
+ _ ->
+ [Name,Depth,Node,Depth]
+ end,
+ {Format,Args};
+format_log_multi(#{label:={application_controller,exit},
+ report:=[{application,Name},
+ {exited,Reason},
+ {type,Type}]},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ lists:append(
+ [" application: ",P,"~n",
+ " exited: ",P,"~n",
+ " type: ",P,"~n"]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Name,Reason,Type];
+ _ ->
+ [Name,Depth,Reason,Depth,Type,Depth]
+ end,
+ {Format,Args}.
+
+p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
+ "~"++single(Single)++mod(Enc)++p(Depth);
+p(unlimited) ->
+ "p";
+p(_Depth) ->
+ "P".
+
+single(true) -> "0";
+single(false) -> "".
+
+mod(latin1) -> "";
+mod(_) -> "t".
%%-----------------------------------------------------------------
%% Reply to all processes waiting this application to be started.
diff --git a/lib/kernel/src/auth.erl b/lib/kernel/src/auth.erl
index 4d18daf9e4..1f4a507766 100644
--- a/lib/kernel/src/auth.erl
+++ b/lib/kernel/src/auth.erl
@@ -24,7 +24,11 @@
%% Old documented interface - deprecated
-export([is_auth/1, cookie/0, cookie/1, node_cookie/1, node_cookie/2]).
--deprecated([{is_auth,1}, {cookie,'_'}, {node_cookie, '_'}]).
+-deprecated([{is_auth,1,"use net_adm:ping/1 instead"},
+ {cookie,0,"use erlang:get_cookie/0 instead"},
+ {cookie,1,"use erlang:set_cookie/2 instead"},
+ {node_cookie, '_',
+ "use erlang:set_cookie/2 and net_adm:ping/1 instead"}]).
%% New interface - meant for internal use within kernel only
-export([get_cookie/0, get_cookie/1,
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
index 964ede9bc9..71a20231d4 100644
--- a/lib/kernel/src/code.erl
+++ b/lib/kernel/src/code.erl
@@ -20,6 +20,7 @@
-module(code).
-include_lib("kernel/include/logger.hrl").
+-include("eep48.hrl").
%% This is the interface module to the code server. It also contains
%% some implementation details. See also related modules: code_*.erl
@@ -44,6 +45,7 @@
soft_purge/1,
is_loaded/1,
all_loaded/0,
+ all_available/0,
stop/0,
root_dir/0,
lib_dir/0,
@@ -68,18 +70,21 @@
rehash/0,
start_link/0,
which/1,
+ get_doc/1,
where_is_file/1,
where_is_file/2,
set_primary_archive/4,
clash/0,
+ module_status/0,
module_status/1,
modified_modules/0,
get_mode/0]).
--deprecated({rehash,0,next_major_release}).
+-deprecated({rehash,0,"the code path cache feature has been removed"}).
-export_type([load_error_rsn/0, load_ret/0]).
-export_type([prepared_code/0]).
+-export_type([module_status/0]).
-include_lib("kernel/include/file.hrl").
@@ -218,6 +223,53 @@ get_object_code(Mod) when is_atom(Mod) -> call({get_object_code, Mod}).
Loaded :: loaded_filename().
all_loaded() -> call(all_loaded).
+-spec all_available() -> [{Module, Filename, Loaded}] when
+ Module :: string(),
+ Filename :: loaded_filename(),
+ Loaded :: boolean().
+all_available() ->
+ case code:get_mode() of
+ interactive ->
+ all_available(get_path(), #{});
+ embedded ->
+ all_available([], #{})
+ end.
+all_available([Path|Tail], Acc) ->
+ case erl_prim_loader:list_dir(Path) of
+ {ok, Files} ->
+ all_available(Tail, all_available(Path, Files, Acc));
+ _Error ->
+ all_available(Tail, Acc)
+ end;
+all_available([], AllModules) ->
+ AllLoaded = [{atom_to_list(M),Path,true} || {M,Path} <- all_loaded()],
+ AllAvailable =
+ maps:fold(
+ fun(File, Path, Acc) ->
+ [{filename:rootname(File), filename:append(Path, File), false} | Acc]
+ end, [], AllModules),
+ OrderFun = fun F({A,_,_},{B,_,_}) ->
+ F(A,B);
+ F(A,B) ->
+ A =< B
+ end,
+ lists:umerge(OrderFun, lists:sort(OrderFun, AllLoaded), lists:sort(OrderFun, AllAvailable)).
+
+all_available(Path, [File | T], Acc) ->
+ case filename:extension(File) of
+ ".beam" ->
+ case maps:is_key(File, Acc) of
+ false ->
+ all_available(Path, T, Acc#{ File => Path });
+ true ->
+ all_available(Path, T, Acc)
+ end;
+ _Else ->
+ all_available(Path, T, Acc)
+ end;
+all_available(_Path, [], Acc) ->
+ Acc.
+
-spec stop() -> no_return().
stop() -> call(stop).
@@ -734,7 +786,7 @@ start_get_mode() ->
-spec which(Module) -> Which when
Module :: module(),
- Which :: file:filename() | loaded_ret_atoms() | non_existing.
+ Which :: loaded_filename() | non_existing.
which(Module) when is_atom(Module) ->
case is_loaded(Module) of
false ->
@@ -783,6 +835,94 @@ where_is_file(Tail, File, Path, Files) ->
where_is_file(Tail, File)
end.
+-spec get_doc(Mod) -> {ok, Res} | {error, Reason} when
+ Mod :: module(),
+ Res :: #docs_v1{},
+ Reason :: non_existing | missing | file:posix().
+get_doc(Mod) when is_atom(Mod) ->
+ case which(Mod) of
+ preloaded ->
+ Fn = filename:join([code:lib_dir(erts),"ebin",atom_to_list(Mod) ++ ".beam"]),
+ get_doc_chunk(Fn, Mod);
+ Error when is_atom(Error) ->
+ {error, Error};
+ Fn ->
+ get_doc_chunk(Fn, Mod)
+ end.
+
+get_doc_chunk(Filename, Mod) when is_atom(Mod) ->
+ case beam_lib:chunks(Filename, ["Docs"]) of
+ {error,beam_lib,{missing_chunk,_,_}} ->
+ case get_doc_chunk(Filename, atom_to_list(Mod)) of
+ {error,missing} ->
+ get_doc_chunk_from_ast(Filename);
+ Error ->
+ Error
+ end;
+ {error,beam_lib,{file_error,_Filename,enoent}} ->
+ get_doc_chunk(Filename, atom_to_list(Mod));
+ {ok, {Mod, [{"Docs",Bin}]}} ->
+ binary_to_term(Bin)
+ end;
+get_doc_chunk(Filename, Mod) ->
+ case filename:dirname(Filename) of
+ Filename ->
+ {error,missing};
+ Dir ->
+ ChunkFile = filename:join([Dir,"doc","chunks",Mod ++ ".chunk"]),
+ case file:read_file(ChunkFile) of
+ {ok, Bin} ->
+ {ok, binary_to_term(Bin)};
+ {error,enoent} ->
+ get_doc_chunk(Dir, Mod);
+ {error,Reason} ->
+ {error,Reason}
+ end
+ end.
+
+get_doc_chunk_from_ast(Filename) ->
+ case beam_lib:chunks(Filename, [abstract_code]) of
+ {error,beam_lib,{missing_chunk,_,_}} ->
+ {error,missing};
+ {ok, {_Mod, [{abstract_code,
+ {raw_abstract_v1, AST}}]}} ->
+ Docs = get_function_docs_from_ast(AST),
+ {ok, #docs_v1{ anno = 0, beam_language = erlang,
+ module_doc = none,
+ metadata = #{ generated => true, otp_doc_vsn => ?CURR_DOC_VERSION },
+ docs = Docs }};
+ {ok, {_Mod, [{abstract_code,no_abstract_code}]}} ->
+ {error,missing};
+ Error ->
+ Error
+ end.
+
+get_function_docs_from_ast(AST) ->
+ lists:flatmap(fun(E) -> get_function_docs_from_ast(E, AST) end, AST).
+get_function_docs_from_ast({function,Ln,Name,Arity,_Code}, AST) ->
+ Signature = io_lib:format("~p/~p",[Name,Arity]),
+ Anno = erl_anno:new(Ln),
+ Specs = lists:filter(fun({attribute,_Ln,spec,{FA,_}}) ->
+ case FA of
+ {F,A} ->
+ F =:= Name andalso A =:= Arity;
+ {_, F, A} ->
+ F =:= Name andalso A =:= Arity
+ end;
+ (_) -> false
+ end, AST),
+ SpecMd = case Specs of
+ [S] -> #{ spec => [S] };
+ [] -> #{}
+ end,
+ FnDocs = [],
+ Md = SpecMd#{},
+ [{{function, Name, Arity}, Anno, [unicode:characters_to_binary(Signature)],
+ #{ <<"en">> => FnDocs },
+ Md#{}}];
+get_function_docs_from_ast(_, _) ->
+ [].
+
-spec set_primary_archive(ArchiveFile :: file:filename(),
ArchiveBin :: binary(),
FileInfo :: file:file_info(),
@@ -804,7 +944,7 @@ set_primary_archive(ArchiveFile0, ArchiveBin, #file_info{} = FileInfo,
{error, _Reason} = Error ->
Error
end.
-
+
%% Search the entire path system looking for name clashes
-spec clash() -> 'ok'.
@@ -915,8 +1055,19 @@ load_all_native_1([{Mod,BeamFilename}|T], ChunkTag) ->
load_all_native_1([], _) ->
ok.
+-type module_status() :: not_loaded | loaded | modified | removed.
+
+%% Returns the list of all loaded modules and their current status
+-spec module_status() -> [{module(), module_status()}].
+module_status() ->
+ module_status([M || {M, _} <- all_loaded()]).
+
%% Returns the status of the module in relation to object file on disk.
--spec module_status(Module :: module()) -> not_loaded | loaded | modified | removed.
+-spec module_status (Module :: module() | [module()]) ->
+ module_status() | [{module(), module_status()}].
+module_status(Modules) when is_list(Modules) ->
+ PathFiles = path_files(),
+ [{M, module_status(M, PathFiles)} || M <- Modules];
module_status(Module) ->
module_status(Module, code:get_path()).
@@ -991,9 +1142,7 @@ get_beam_chunk(Path, Chunk) ->
%% Returns a list of all modules modified on disk.
-spec modified_modules() -> [module()].
modified_modules() ->
- PathFiles = path_files(),
- [M || {M, _} <- code:all_loaded(),
- module_status(M, PathFiles) =:= modified].
+ [M || {M, modified} <- module_status()].
%% prefetch the directory contents of code path directories
path_files() ->
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index 5469d8694c..8ef54dd0e1 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -93,7 +93,7 @@ init(Ref, Parent, [Root,Mode]) ->
root = Root,
path = Path,
moddb = Db,
- namedb = init_namedb(Path),
+ namedb = create_namedb(Path, Root),
mode = Mode},
Parent ! {Ref,{ok,self()}},
@@ -265,8 +265,8 @@ handle_call({add_paths,Where,Dirs0}, _From,
{reply,Resp,S#state{path=Path}};
handle_call({set_path,PathList}, _From,
- #state{path=Path0,namedb=Namedb}=S) ->
- {Resp,Path,NewDb} = set_path(PathList, Path0, Namedb),
+ #state{root=Root,path=Path0,namedb=Namedb}=S) ->
+ {Resp,Path,NewDb} = set_path(PathList, Path0, Namedb, Root),
{reply,Resp,S#state{path=Path,namedb=NewDb}};
handle_call({del_path,Name}, _From,
@@ -755,12 +755,12 @@ update(Dir, NameDb) ->
%%
%% Set a completely new path.
%%
-set_path(NewPath0, OldPath, NameDb) ->
+set_path(NewPath0, OldPath, NameDb, Root) ->
NewPath = normalize(NewPath0),
case check_path(NewPath) of
{ok, NewPath2} ->
ets:delete(NameDb),
- NewDb = init_namedb(NewPath2),
+ NewDb = create_namedb(NewPath2, Root),
{true, NewPath2, NewDb};
Error ->
{Error, OldPath, NameDb}
@@ -788,11 +788,27 @@ normalize(Other) ->
%% Handle a table of name-directory pairs.
%% The priv_dir/1 and lib_dir/1 functions will have
%% an O(1) lookup.
-init_namedb(Path) ->
- Db = ets:new(code_names,[private]),
+create_namedb(Path, Root) ->
+ Db = ets:new(code_names,[named_table, public]),
init_namedb(lists:reverse(Path), Db),
+
+ case lookup_name("erts", Db) of
+ {ok, _, _, _} ->
+ %% erts is part of code path
+ ok;
+ false ->
+ %% No erts in code path, check if this is a source
+ %% repo and if so use that.
+ ErtsDir = filename:join(Root, "erts"),
+ case erl_prim_loader:read_file_info(ErtsDir) of
+ error ->
+ ok;
+ _ ->
+ do_insert_name("erts", ErtsDir, Db)
+ end
+ end,
Db.
-
+
init_namedb([P|Path], Db) ->
insert_dir(P, Db),
init_namedb(Path, Db);
@@ -997,7 +1013,6 @@ lookup_name(Name, Db) ->
_ -> false
end.
-
%%
%% Fetch a directory.
%%
diff --git a/lib/kernel/src/disk_log_server.erl b/lib/kernel/src/disk_log_server.erl
index 78c15d0ad8..2e22f28b14 100644
--- a/lib/kernel/src/disk_log_server.erl
+++ b/lib/kernel/src/disk_log_server.erl
@@ -38,6 +38,12 @@
-record(state, {pending = [] :: [#pending{}]}).
+-compile({nowarn_deprecated_function, [{pg2, create, 1}]}).
+-compile({nowarn_deprecated_function, [{pg2, join, 2}]}).
+-compile({nowarn_deprecated_function, [{pg2, leave, 2}]}).
+-compile({nowarn_deprecated_function, [{pg2, which_groups, 0}]}).
+-compile({nowarn_deprecated_function, [{pg2, get_members, 1}]}).
+
%%%-----------------------------------------------------------------
%%% This module implements the disk_log server. Its primary purpose
%%% is to keep the ets table 'disk_log_names' updated and to handle
diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl
index d8571c01be..3b9d88da6a 100644
--- a/lib/kernel/src/dist_util.erl
+++ b/lib/kernel/src/dist_util.erl
@@ -27,7 +27,7 @@
%%-compile(export_all).
-export([handshake_we_started/1, handshake_other_started/1,
- strict_order_flags/0,
+ strict_order_flags/0, rejectable_flags/0,
start_timer/1, setup_timer/2,
reset_timer/1, cancel_timer/1,
is_node_name/1, split_node/1, is_allowed/2,
@@ -70,6 +70,8 @@
-define(u32(X3,X2,X1,X0),
(((X3) bsl 24) bor ((X2) bsl 16) bor ((X1) bsl 8) bor (X0))).
+-define(CREATION_UNKNOWN,0).
+
-record(tick, {read = 0,
write = 0,
tick = 0,
@@ -120,6 +122,10 @@ dflag2str(?DFLAG_EXIT_PAYLOAD) ->
"EXIT_PAYLOAD";
dflag2str(?DFLAG_FRAGMENTS) ->
"FRAGMENTS";
+dflag2str(?DFLAG_HANDSHAKE_23) ->
+ "HANDSHAKE_23";
+dflag2str(?DFLAG_SPAWN) ->
+ "SPAWN";
dflag2str(_) ->
"UNKNOWN".
@@ -152,6 +158,11 @@ strict_order_flags() ->
EDF = erts_internal:get_dflags(),
EDF#erts_dflags.strict_order.
+-spec rejectable_flags() -> integer().
+rejectable_flags() ->
+ EDF = erts_internal:get_dflags(),
+ EDF#erts_dflags.rejectable.
+
make_this_flags(RequestType, AddFlags, RejectFlags, OtherNode,
#erts_dflags{}=EDF) ->
case RejectFlags band (bnot EDF#erts_dflags.rejectable) of
@@ -174,30 +185,35 @@ handshake_other_started(#hs_data{request_type=ReqType,
AddFlgs = convert_flags(AddFlgs0),
RejFlgs = convert_flags(RejFlgs0),
ReqFlgs = convert_flags(ReqFlgs0),
- {PreOtherFlags,Node,Version} = recv_name(HSData0),
+ {PreOtherFlags,Node,Creation,SendNameVersion} = recv_name(HSData0),
EDF = erts_internal:get_dflags(),
PreThisFlags = make_this_flags(ReqType, AddFlgs, RejFlgs, Node, EDF),
- ChosenFlags = adjust_flags(PreThisFlags, PreOtherFlags),
- HSData = HSData0#hs_data{this_flags=ChosenFlags,
- other_flags=ChosenFlags,
- other_version=Version,
- other_node=Node,
- other_started=true,
- add_flags=AddFlgs,
- reject_flags=RejFlgs,
- require_flags=ReqFlgs},
- check_dflags(HSData, EDF),
- ?debug({"MD5 connection from ~p (V~p)~n",
- [Node, HSData#hs_data.other_version]}),
- mark_pending(HSData),
+ HSData1 = HSData0#hs_data{this_flags=PreThisFlags,
+ other_flags=PreOtherFlags,
+ other_version=flags_to_version(PreOtherFlags),
+ other_node=Node,
+ other_started=true,
+ other_creation=Creation,
+ add_flags=AddFlgs,
+ reject_flags=RejFlgs,
+ require_flags=ReqFlgs},
+ check_dflags(HSData1, EDF),
+ ?debug({"MD5 connection from ~p~n", [Node]}),
+ mark_pending(HSData1),
{MyCookie,HisCookie} = get_cookies(Node),
ChallengeA = gen_challenge(),
- send_challenge(HSData, ChallengeA),
- reset_timer(HSData#hs_data.timer),
- ChallengeB = recv_challenge_reply(HSData, ChallengeA, MyCookie),
- send_challenge_ack(HSData, gen_digest(ChallengeB, HisCookie)),
+ send_challenge(HSData1, ChallengeA),
+ reset_timer(HSData1#hs_data.timer),
+ HSData2 = recv_complement(HSData1, SendNameVersion),
+ check_dflags(HSData2, EDF),
+ ChosenFlags = adjust_flags(HSData2#hs_data.this_flags,
+ HSData2#hs_data.other_flags),
+ HSData3 = HSData2#hs_data{this_flags = ChosenFlags,
+ other_flags = ChosenFlags},
+ ChallengeB = recv_challenge_reply(HSData3, ChallengeA, MyCookie),
+ send_challenge_ack(HSData3, gen_digest(ChallengeB, HisCookie)),
?debug({dist_util, self(), accept_connection, Node}),
- connection(HSData);
+ connection(HSData3);
handshake_other_started(OldHsData) when element(1,OldHsData) =:= hs_data ->
handshake_other_started(convert_old_hsdata(OldHsData)).
@@ -363,16 +379,19 @@ handshake_we_started(#hs_data{request_type=ReqType,
add_flags = AddFlgs,
reject_flags = RejFlgs,
require_flags = ReqFlgs},
- send_name(HSData),
+ SendNameVersion = send_name(HSData),
recv_status(HSData),
- {PreOtherFlags,ChallengeA} = recv_challenge(HSData),
+ {PreOtherFlags, ChallengeA, Creation} = recv_challenge(HSData),
ChosenFlags = adjust_flags(PreThisFlags, PreOtherFlags),
NewHSData = HSData#hs_data{this_flags = ChosenFlags,
other_flags = ChosenFlags,
- other_started = false},
+ other_started = false,
+ other_version = flags_to_version(PreOtherFlags),
+ other_creation = Creation},
check_dflags(NewHSData, EDF),
MyChallenge = gen_challenge(),
{MyCookie,HisCookie} = get_cookies(Node),
+ send_complement(NewHSData, SendNameVersion),
send_challenge_reply(NewHSData,MyChallenge,
gen_digest(ChallengeA,HisCookie)),
reset_timer(NewHSData#hs_data.timer),
@@ -393,6 +412,16 @@ convert_flags(Flags) when is_integer(Flags) ->
convert_flags(_Undefined) ->
0.
+flags_to_version(Flags) ->
+ case Flags band ?DFLAG_HANDSHAKE_23 of
+ 0 ->
+ ?ERL_DIST_VER_5;
+ ?DFLAG_HANDSHAKE_23 ->
+ ?ERL_DIST_VER_6
+ end.
+
+
+
%% --------------------------------------------------------------
%% The connection has been established.
%% --------------------------------------------------------------
@@ -470,15 +499,14 @@ get_cookies(Node) ->
%% No error return; either succeeds or terminates the process.
do_setnode(#hs_data{other_node = Node, socket = Socket,
other_flags = Flags, other_version = Version,
- f_getll = GetLL}) ->
+ f_getll = GetLL,
+ other_creation = Creation}) ->
case GetLL(Socket) of
{ok,Port} ->
- ?trace("setnode(md5,~p ~p ~p)~n",
- [Node, Port, {publish_type(Flags),
- '(', Flags, ')',
- Version}]),
+ ?trace("setnode: node=~p port=~p flags=~p(~p) ver=~p creation=~p~n",
+ [Node, Port, Flags, publish_type(Flags), Version, Creation]),
try
- erlang:setnode(Node, Port, {Flags, Version, '', ''})
+ erlang:setnode(Node, Port, {Flags, Version, Creation})
catch
error:system_limit ->
error_msg("** Distribution system limit reached, "
@@ -585,21 +613,77 @@ send_name(#hs_data{socket = Socket, this_node = Node,
f_send = FSend,
this_flags = Flags,
other_version = Version}) ->
- ?trace("send_name: node=~w, version=~w\n",
- [Node,Version]),
- ?to_port(FSend, Socket,
- [$n, ?int16(Version), ?int32(Flags), atom_to_list(Node)]).
+ NameBin = atom_to_binary(Node, latin1),
+ if Version =:= undefined;
+ Version =:= ?ERL_DIST_VER_5 ->
+ %% We treat "5" the same as 'undefined' as there are
+ %% custom made epmd modules out there with a hardcoded "5".
+ %%
+ %% Send old 'n' message but with DFLAG_HANDSHAKE_23
+ %% Old nodes will ignore DFLAG_HANDSHAKE_23 and reply old 'n' challenge.
+ %% New nodes will see DFLAG_HANDSHAKE_23 and reply new 'N' challenge.
+ ?trace("send_name: 'n' node=~p, version=~w\n",
+ [Node, ?ERL_DIST_VER_5]),
+ _ = ?to_port(FSend, Socket,
+ [<<$n, ?ERL_DIST_VER_5:16, Flags:32>>, NameBin]),
+ ?ERL_DIST_VER_5;
+
+ is_integer(Version), Version >= ?ERL_DIST_VER_6 ->
+ Creation = erts_internal:get_creation(),
+ NameLen = byte_size(NameBin),
+ ?trace("send_name: 'N' node=~p creation=~w\n",
+ [Node, Creation]),
+ _ = ?to_port(FSend, Socket,
+ [<<$N, Flags:64, Creation:32, NameLen:16>>, NameBin]),
+ ?ERL_DIST_VER_6
+ end.
send_challenge(#hs_data{socket = Socket, this_node = Node,
- other_version = Version,
- this_flags = Flags,
+ this_flags = ThisFlags,
+ other_flags = OtherFlags,
f_send = FSend},
Challenge ) ->
- ?trace("send: challenge=~w version=~w\n",
- [Challenge,Version]),
- ?to_port(FSend, Socket, [$n,?int16(Version), ?int32(Flags),
- ?int32(Challenge),
- atom_to_list(Node)]).
+ case OtherFlags band ?DFLAG_HANDSHAKE_23 of
+ 0 ->
+ %% Reply with old 'n' message
+ ?trace("send: 'n' challenge=~w\n", [Challenge]),
+
+ ?to_port(FSend, Socket, [<<$n,
+ ?ERL_DIST_VER_5:16, % echo same Version back
+ ThisFlags:32,
+ Challenge:32>>,
+ atom_to_list(Node)]);
+
+ ?DFLAG_HANDSHAKE_23 ->
+ %% Reply with new 'N' message
+ Creation = erts_internal:get_creation(),
+ NodeName = atom_to_binary(Node, latin1),
+ NameLen = byte_size(NodeName),
+ ?trace("send: 'N' challenge=~w creation=~w\n",
+ [Challenge,Creation]),
+ ?to_port(FSend, Socket, [<<$N,
+ ThisFlags:64,
+ Challenge:32,
+ Creation:32,
+ NameLen:16>>, NodeName])
+ end.
+
+send_complement(#hs_data{socket = Socket,
+ f_send = FSend,
+ this_flags = Flags,
+ other_flags = Flags},
+ SendNameVersion) ->
+ if SendNameVersion =:= ?ERL_DIST_VER_5,
+ (Flags band ?DFLAG_HANDSHAKE_23) =/= 0 ->
+ %% We sent an old 'n' name message and need to complement
+ %% with creation value.
+ Creation = erts_internal:get_creation(),
+ FlagsHigh = Flags bsr 32,
+ ?trace("send_complement: 'c' flags_high=~w creation=~w\n", [FlagsHigh,Creation]),
+ ?to_port(FSend, Socket, [<<$c, FlagsHigh:32, Creation:32>>]);
+ true->
+ ok % no complement msg needed
+ end.
send_challenge_reply(#hs_data{socket = Socket, f_send = FSend},
Challenge, Digest) ->
@@ -614,31 +698,50 @@ send_challenge_ack(#hs_data{socket = Socket, f_send = FSend},
%%
-%% Get the name of the other side.
+%% Receive first handshake message sent from connecting side.
%% Close the connection if invalid data.
-%% The IP address sent is not interesting (as in the old
-%% tcp_drv.c which used it to detect simultaneous connection
-%% attempts).
%%
recv_name(#hs_data{socket = Socket, f_recv = Recv} = HSData) ->
case Recv(Socket, 0, infinity) of
- {ok,
- [$n,VersionA, VersionB, Flag1, Flag2, Flag3, Flag4
- | OtherNode] = Data} ->
- case is_node_name(OtherNode) of
- true ->
- Flags = ?u32(Flag1, Flag2, Flag3, Flag4),
- Version = ?u16(VersionA,VersionB),
- is_allowed(HSData, Flags, OtherNode, Version);
- false ->
- ?shutdown(Data)
- end;
+ {ok, [$n | _] = Data} ->
+ recv_name_old(HSData, Data);
+ {ok, [$N | _] = Data} ->
+ recv_name_new(HSData, Data);
_ ->
?shutdown(no_node)
end.
-is_node_name(OtherNodeName) ->
- case string:split(OtherNodeName, "@", all) of
+recv_name_old(HSData,
+ [$n, V1, V0, F3, F2, F1, F0 | Node] = Data) ->
+ <<_Version:16>> = <<V1,V0>>,
+ <<Flags:32>> = <<F3,F2,F1,F0>>,
+ ?trace("recv_name: 'n' node=~p version=~w\n", [Node, _Version]),
+ case is_node_name(Node) of
+ true ->
+ check_allowed(HSData, Node),
+ {Flags, list_to_atom(Node), ?CREATION_UNKNOWN, ?ERL_DIST_VER_5};
+ false ->
+ ?shutdown(Data)
+ end.
+
+recv_name_new(HSData,
+ [$N, F7,F6,F5,F4,F3,F2,F1,F0, Cr3,Cr2,Cr1,Cr0,
+ NL1, NL0 | Rest] = Data) ->
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
+ <<NameLen:16>> = <<NL1,NL0>>,
+ {Node, _Residue} = lists:split(NameLen, Rest),
+ ?trace("recv_name: 'N' node=~p creation=~w\n", [Node, Creation]),
+ case is_node_name(Node) of
+ true ->
+ check_allowed(HSData, Node),
+ {Flags, list_to_atom(Node), Creation, ?ERL_DIST_VER_6};
+ false ->
+ ?shutdown(Data)
+ end.
+
+is_node_name(NodeName) ->
+ case string:split(NodeName, "@", all) of
[Name,Host] ->
(not string:is_empty(Name))
andalso (not string:is_empty(Host));
@@ -674,12 +777,12 @@ split_node(Node) ->
%% with allow-node-scheme. An empty allowed list
%% allows all nodes.
%%
-is_allowed(#hs_data{allowed = []}, Flags, Node, Version) ->
- {Flags,list_to_atom(Node),Version};
-is_allowed(#hs_data{allowed = Allowed} = HSData, Flags, Node, Version) ->
+check_allowed(#hs_data{allowed = []}, _Node) ->
+ ok;
+check_allowed(#hs_data{allowed = Allowed} = HSData, Node) ->
case is_allowed(Node, Allowed) of
true ->
- {Flags,list_to_atom(Node),Version};
+ ok;
false ->
send_status(HSData#hs_data{other_node = Node}, not_allowed),
error_msg("** Connection attempt from "
@@ -753,25 +856,91 @@ publish_type(Flags) ->
end.
%% wait for challenge after connect
-recv_challenge(#hs_data{socket=Socket,other_node=Node,
- other_version=Version,f_recv=Recv}) ->
+recv_challenge(#hs_data{socket=Socket, f_recv=Recv}=HSData) ->
case Recv(Socket, 0, infinity) of
- {ok,[$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
- Flags = ?u32(Fl1,Fl2,Fl3,Fl4),
- try {list_to_existing_atom(Ns),?u16(V1,V0)} of
- {Node,Version} ->
- Challenge = ?u32(CA3,CA2,CA1,CA0),
- ?trace("recv: node=~w, challenge=~w version=~w\n",
- [Node, Challenge,Version]),
- {Flags,Challenge};
- _ ->
- ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
- catch
- error:badarg ->
- ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
- end;
+ {ok, [$n | _]=Msg} ->
+ recv_challenge_old(HSData, Msg);
+ {ok,[$N | _]=Msg} ->
+ recv_challenge_new(HSData, Msg);
Other ->
- ?shutdown2(no_node, {recv_challenge_failed, Other})
+ ?shutdown2(no_node, {recv_challenge_failed, Other})
+ end.
+
+recv_challenge_old(#hs_data{other_node=Node},
+ [$n, V1,V0, F3,F2,F1,F0, C3,C2,C1,C0 | Ns]=Msg) ->
+ <<_Version:16>> = <<V1,V0>>,
+ <<Flags:32>> = <<F3,F2,F1,F0>>,
+ <<Challenge:32>> = <<C3,C2,C1,C0>>,
+ ?trace("recv: 'n' node=~p, challenge=~w version=~w\n",
+ [Ns, Challenge, _Version]),
+ try {list_to_existing_atom(Ns), Flags band ?DFLAG_HANDSHAKE_23} of
+ {Node, 0} ->
+ {Flags, Challenge, ?CREATION_UNKNOWN};
+ _ ->
+ ?shutdown2(no_node, {recv_challenge_failed, version, Msg})
+ catch
+ error:badarg ->
+ ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
+ end;
+recv_challenge_old(_, Other) ->
+ ?shutdown2(no_node, {recv_challenge_failed, Other}).
+
+recv_challenge_new(#hs_data{other_node=Node},
+ [$N,
+ F7,F6,F5,F4,F3,F2,F1,F0,
+ Ch3,Ch2,Ch1,Ch0,
+ Cr3,Cr2,Cr1,Cr0,
+ NL1,NL0 | Rest] = Msg) ->
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ <<Challenge:32>> = <<Ch3,Ch2,Ch1,Ch0>>,
+ <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
+ <<NameLen:16>> = <<NL1,NL0>>,
+ {Ns, _Residue} =
+ try
+ lists:split(NameLen, Rest)
+ catch
+ error:badarg ->
+ ?shutdown2(no_node, {recv_challenge_failed, no_node, Msg})
+ end,
+ ?trace("recv: 'N' node=~p, challenge=~w creation=~w\n",
+ [Ns, Challenge, Creation]),
+
+ case Flags band ?DFLAG_HANDSHAKE_23 of
+ ?DFLAG_HANDSHAKE_23 ->
+ try list_to_existing_atom(Ns) of
+ Node ->
+ {Flags, Challenge, Creation};
+ _ ->
+ ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
+ catch
+ error:badarg ->
+ ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
+ end;
+ 0 ->
+ ?shutdown2(no_node, {recv_challenge_failed, version, Msg})
+ end;
+recv_challenge_new(_, Other) ->
+ ?shutdown2(no_node, {recv_challenge_failed, Other}).
+
+
+recv_complement(#hs_data{socket = Socket,
+ f_recv = Recv,
+ other_flags = Flags} = HSData,
+ SendNameVersion) ->
+ if SendNameVersion =:= ?ERL_DIST_VER_5,
+ (Flags band ?DFLAG_HANDSHAKE_23) =/= 0 ->
+ case Recv(Socket, 0, infinity) of
+ {ok, [$c, F7,F6,F5,F4, Cr3,Cr2,Cr1,Cr0]} ->
+ <<FlagsHigh:32>> = <<F7,F6,F5,F4>>,
+ <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
+ ?trace("recv_complement: creation=~w\n", [Creation]),
+ HSData#hs_data{other_creation = Creation,
+ other_flags = Flags bor (FlagsHigh bsl 32)};
+ Other ->
+ ?shutdown2(no_node, {recv_complement_failed, Other})
+ end;
+ true ->
+ HSData
end.
diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl
index ac4fe2b78b..fecb1cd3e0 100644
--- a/lib/kernel/src/erl_epmd.erl
+++ b/lib/kernel/src/erl_epmd.erl
@@ -29,14 +29,16 @@
-define(port_please_failure2(Term), noop).
-endif.
+-include("dist.hrl").
+
-ifndef(erlang_daemon_port).
-define(erlang_daemon_port, 4369).
-endif.
-ifndef(epmd_dist_high).
--define(epmd_dist_high, 4370).
+-define(epmd_dist_high, ?ERL_DIST_VER_HIGH).
-endif.
-ifndef(epmd_dist_low).
--define(epmd_dist_low, 4370).
+-define(epmd_dist_low, ?ERL_DIST_VER_LOW).
-endif.
%% External exports
@@ -347,6 +349,13 @@ wait_for_reg_reply(Socket, SoFar) ->
receive
{tcp, Socket, Data0} ->
case SoFar ++ Data0 of
+ [$v, Result, A, B, C, D] ->
+ case Result of
+ 0 ->
+ {alive, Socket, ?u32(A, B, C, D)};
+ _ ->
+ {error, duplicate_name}
+ end;
[$y, Result, A, B] ->
case Result of
0 ->
diff --git a/lib/kernel/src/erpc.erl b/lib/kernel/src/erpc.erl
new file mode 100644
index 0000000000..a73598c019
--- /dev/null
+++ b/lib/kernel/src/erpc.erl
@@ -0,0 +1,473 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. 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.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Author: Rickard Green
+%%
+
+-module(erpc).
+
+%% Exported API
+
+-export([call/4,
+ call/5,
+ cast/4,
+ send_request/4,
+ receive_response/1,
+ receive_response/2,
+ wait_response/1,
+ wait_response/2,
+ check_response/2,
+ multicall/4,
+ multicall/5]).
+
+-export_type([request_id/0]).
+
+%% Internal exports (also used by the 'rpc' module)
+
+-export([execute_call/4,
+ execute_call/3,
+ is_arg_error/4,
+ trim_stack/4,
+ call_result/4]).
+
+%%------------------------------------------------------------------------
+
+-compile({inline,[{result,4}]}). %% Nicer error stack trace...
+
+-define(MAX_INT_TIMEOUT, 4294967295).
+-define(TIMEOUT_TYPE, 0..?MAX_INT_TIMEOUT | 'infinity').
+-define(IS_VALID_TMO_INT(TI_), (is_integer(TI_)
+ andalso (0 =< TI_)
+ andalso (TI_ =< ?MAX_INT_TIMEOUT))).
+-define(IS_VALID_TMO(T_), ((T_ == infinity) orelse ?IS_VALID_TMO_INT(T_))).
+
+%%------------------------------------------------------------------------
+%% Exported API
+%%------------------------------------------------------------------------
+
+-spec call(Node, Module, Function, Args) -> Result when
+ Node :: node(),
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()],
+ Result :: term().
+
+call(N, M, F, A) ->
+ call(N, M, F, A, infinity).
+
+-dialyzer([{nowarn_function, call/5}, no_return]).
+
+-spec call(Node, Module, Function, Args, Timeout) -> Result when
+ Node :: node(),
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()],
+ Timeout :: ?TIMEOUT_TYPE,
+ Result :: term().
+
+call(N, M, F, A, infinity) when node() =:= N, %% Optimize local call
+ is_atom(M),
+ is_atom(F),
+ is_list(A) ->
+ try
+ {return, Return} = execute_call(M,F,A),
+ Return
+ catch
+ exit:Reason ->
+ exit({exception, Reason});
+ error:Reason:Stack ->
+ case is_arg_error(Reason, M, F, A) of
+ true ->
+ error({?MODULE, Reason});
+ false ->
+ ErpcStack = trim_stack(Stack, M, F, A),
+ error({exception, Reason, ErpcStack})
+ end
+ end;
+call(N, _M, _F, _A, infinity) when node() =:= N ->
+ error({?MODULE, badarg});
+call(N, M, F, A, T) when ?IS_VALID_TMO(T) ->
+ try
+ Res = make_ref(),
+ ReqId = erlang:spawn_request(N, ?MODULE, execute_call,
+ [Res, M, F, A],
+ [{reply, error_only},
+ monitor]),
+ receive
+ {spawn_reply, ReqId, error, Reason} ->
+ result(spawn_reply, ReqId, Res, Reason);
+ {'DOWN', ReqId, process, _Pid, Reason} ->
+ result(down, ReqId, Res, Reason)
+ after T ->
+ result(timeout, ReqId, Res, undefined)
+ end
+ catch
+ error:badarg ->
+ error({?MODULE, badarg})
+ end;
+call(_N, _M, _F, _A, _T) ->
+ error({?MODULE, badarg}).
+
+%% Asynchronous call
+
+-opaque request_id() :: {reference(), reference()}.
+
+-spec send_request(Node, Module, Function, Args) -> RequestId when
+ Node :: node(),
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ RequestId :: request_id().
+
+send_request(N, M, F, A) ->
+ try
+ Res = make_ref(),
+ ReqId = erlang:spawn_request(N, ?MODULE, execute_call,
+ [Res, M, F, A],
+ [{reply, error_only},
+ monitor]),
+ {Res, ReqId}
+ catch
+ error:badarg ->
+ error({?MODULE, badarg})
+ end.
+
+-spec receive_response(RequestId) -> Result when
+ RequestId :: request_id(),
+ Result :: term().
+
+receive_response({Res, ReqId} = RId) when is_reference(Res),
+ is_reference(ReqId) ->
+ receive_response(RId, infinity);
+receive_response(_) ->
+ error({?MODULE, badarg}).
+
+-dialyzer([{nowarn_function, receive_response/2}, no_return]).
+
+-spec receive_response(RequestId, Timeout) -> Result when
+ RequestId :: request_id(),
+ Timeout :: ?TIMEOUT_TYPE,
+ Result :: term().
+
+receive_response({Res, ReqId}, Tmo) when is_reference(Res),
+ is_reference(ReqId),
+ ?IS_VALID_TMO(Tmo) ->
+ receive
+ {spawn_reply, ReqId, error, Reason} ->
+ result(spawn_reply, ReqId, Res, Reason);
+ {'DOWN', ReqId, process, _Pid, Reason} ->
+ result(down, ReqId, Res, Reason)
+ after Tmo ->
+ result(timeout, ReqId, Res, undefined)
+ end;
+receive_response(_, _) ->
+ error({?MODULE, badarg}).
+
+-spec wait_response(RequestId) -> {'response', Result} | 'no_response' when
+ RequestId :: request_id(),
+ Result :: term().
+
+wait_response({Res, ReqId} = RId) when is_reference(Res),
+ is_reference(ReqId) ->
+ wait_response(RId, 0).
+
+-dialyzer([{nowarn_function, wait_response/2}, no_return]).
+
+-spec wait_response(RequestId, WaitTime) ->
+ {'response', Result} | 'no_response' when
+ RequestId :: request_id(),
+ WaitTime :: ?TIMEOUT_TYPE,
+ Result :: term().
+
+wait_response({Res, ReqId}, WT) when is_reference(Res),
+ is_reference(ReqId),
+ ?IS_VALID_TMO(WT) ->
+ receive
+ {spawn_reply, ReqId, error, Reason} ->
+ result(spawn_reply, ReqId, Res, Reason);
+ {'DOWN', ReqId, process, _Pid, Reason} ->
+ {response, result(down, ReqId, Res, Reason)}
+ after WT ->
+ no_response
+ end;
+wait_response(_, _) ->
+ error({?MODULE, badarg}).
+
+-dialyzer([{nowarn_function, check_response/2}, no_return]).
+
+-spec check_response(Message, RequestId) ->
+ {'response', Result} | 'no_response' when
+ Message :: term(),
+ RequestId :: request_id(),
+ Result :: term().
+
+check_response({spawn_reply, ReqId, error, Reason},
+ {Res, ReqId}) when is_reference(Res),
+ is_reference(ReqId) ->
+ result(spawn_reply, ReqId, Res, Reason);
+check_response({'DOWN', ReqId, process, _Pid, Reason},
+ {Res, ReqId}) when is_reference(Res),
+ is_reference(ReqId) ->
+ {response, result(down, ReqId, Res, Reason)};
+check_response(_Msg, {Res, ReqId}) when is_reference(Res),
+ is_reference(ReqId) ->
+ no_response;
+check_response(_, _) ->
+ error({?MODULE, badarg}).
+
+-type stack_item() ::
+ {Module :: atom(),
+ Function :: atom(),
+ Arity :: arity() | (Args :: [term()]),
+ Location :: [{file, Filename :: string()} |
+ {line, Line :: pos_integer()}]}.
+
+-type caught_call_exception() ::
+ {throw, Throw :: term()}
+ | {exit, {exception, Reason :: term()}}
+ | {error, {exception, Reason :: term(), StackTrace :: [stack_item()]}}
+ | {exit, {signal, Reason :: term()}}
+ | {error, {?MODULE, Reason :: term()}}.
+
+
+-spec multicall(Nodes, Module, Function, Args) -> Result when
+ Nodes :: [atom()],
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()],
+ Result :: [{ok, ReturnValue :: term()} | caught_call_exception()].
+
+multicall(Ns, M, F, A) ->
+ multicall(Ns, M, F, A, infinity).
+
+-spec multicall(Nodes, Module, Function, Args, Timeout) -> Result when
+ Nodes :: [atom()],
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()],
+ Timeout :: ?TIMEOUT_TYPE,
+ Result :: [{ok, ReturnValue :: term()} | caught_call_exception()].
+
+multicall(Ns, M, F, A, T) when ?IS_VALID_TMO(T) ->
+ EndTime = if T == infinity ->
+ infinity;
+ T == 0 ->
+ erlang:monotonic_time(millisecond);
+ T == ?MAX_INT_TIMEOUT ->
+ erlang:monotonic_time(millisecond)
+ + ?MAX_INT_TIMEOUT;
+ true ->
+ Start = erlang:monotonic_time(),
+ NTmo = erlang:convert_time_unit(T,
+ millisecond,
+ native),
+ erlang:convert_time_unit(Start + NTmo - 1,
+ native,
+ millisecond) + 1
+ end,
+ try
+ ReqIds = mcall_send_requests(Ns, M, F, A, []),
+ mcall_wait_replies(ReqIds, [], EndTime)
+ catch
+ error:{?MODULE, badarg} = Reason ->
+ error(Reason)
+ end;
+multicall(_Ns, _M, _F, _A, _T) ->
+ error({?MODULE, badarg}).
+
+-spec cast(Node, Module, Function, Args) -> ok when
+ Node :: node(),
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()].
+
+cast(Node, Mod, Fun, Args) ->
+ %% Fire and forget...
+ try
+ _ = erlang:spawn_request(Node, Mod, Fun, Args, [{reply, no}]),
+ ok
+ catch
+ error:badarg ->
+ error({?MODULE, badarg})
+ end.
+
+%%------------------------------------------------------------------------
+%% Exported internals
+%%------------------------------------------------------------------------
+
+%% Note that most of these are used by 'rpc' as well...
+
+execute_call(Ref, M, F, A) ->
+ Reply = try
+ {Ref, return, apply(M, F, A)}
+ catch
+ throw:Reason ->
+ {Ref, throw, Reason};
+ exit:Reason ->
+ {Ref, exit, Reason};
+ error:Reason:Stack ->
+ case is_arg_error(Reason, M, F, A) of
+ true ->
+ {Ref, error, {?MODULE, Reason}};
+ false ->
+ ErpcStack = trim_stack(Stack, M, F, A),
+ {Ref, error, Reason, ErpcStack}
+ end
+ end,
+ exit(Reply).
+
+execute_call(M,F,A) ->
+ {return, apply(M, F, A)}.
+
+call_result(Type, ReqId, Res, Reason) ->
+ result(Type, ReqId, Res, Reason).
+
+is_arg_error(badarg, M, F, A) ->
+ try
+ true = is_atom(M),
+ true = is_atom(F),
+ true = is_integer(length(A)),
+ false
+ catch
+ error:badarg ->
+ true
+ end;
+is_arg_error(system_limit, _M, _F, A) ->
+ try
+ apply(?MODULE, nonexisting, A),
+ false
+ catch
+ error:system_limit -> true;
+ _:_ -> false
+ end;
+is_arg_error(_R, _M, _F, _A) ->
+ false.
+
+trim_stack([{?MODULE, execute_call, _, _} | _], M, F, A) ->
+ [{M, F, A, []}];
+trim_stack([{M, F, A, _} = SF, {?MODULE, execute_call, _, _} | _], M, F, A) ->
+ [SF];
+trim_stack(S, M, F, A) ->
+ try
+ trim_stack_aux(S, M, F, A)
+ catch
+ throw:use_all -> S
+ end.
+
+%%------------------------------------------------------------------------
+%% Internals
+%%------------------------------------------------------------------------
+
+trim_stack_aux([], _M, _F, _A) ->
+ throw(use_all);
+trim_stack_aux([{M, F, AL, _} = SF, {?MODULE, execute_call, _, _} | _],
+ M, F, A) when AL == length(A) ->
+ [SF];
+trim_stack_aux([{?MODULE, execute_call, _, _} | _], M, F, A) ->
+ [{M, F, length(A), []}];
+trim_stack_aux([SF|SFs], M, F, A) ->
+ [SF|trim_stack_aux(SFs, M, F, A)].
+
+call_abandon(ReqId) ->
+ case erlang:spawn_request_abandon(ReqId) of
+ true -> true;
+ false -> erlang:demonitor(ReqId, [info])
+ end.
+
+-dialyzer([{nowarn_function, result/4}, no_return]).
+
+-spec result('down', ReqId, Res, Reason) -> term() when
+ ReqId :: reference(),
+ Res :: reference(),
+ Reason :: term();
+ ('spawn_reply', ReqId, Res, Reason) -> no_return() when
+ ReqId :: reference(),
+ Res :: reference(),
+ Reason :: term();
+ ('timeout', ReqId, Res, Reason) -> term() when
+ ReqId :: reference(),
+ Res :: reference(),
+ Reason :: term().
+
+result(down, _ReqId, Res, {Res, return, Return}) ->
+ Return;
+result(down, _ReqId, Res, {Res, throw, Throw}) ->
+ throw(Throw);
+result(down, _ReqId, Res, {Res, exit, Exit}) ->
+ exit({exception, Exit});
+result(down, _ReqId, Res, {Res, error, Error, Stack}) ->
+ error({exception, Error, Stack});
+result(down, _ReqId, Res, {Res, error, {?MODULE, _} = ErpcErr}) ->
+ error(ErpcErr);
+result(down, _ReqId, _Res, noconnection) ->
+ error({?MODULE, noconnection});
+result(down, _ReqId, _Res, Reason) ->
+ exit({signal, Reason});
+result(spawn_reply, _ReqId, _Res, Reason) ->
+ error({?MODULE, Reason});
+result(timeout, ReqId, Res, _Reason) ->
+ case call_abandon(ReqId) of
+ true ->
+ error({?MODULE, timeout});
+ false ->
+ %% Spawn error or DOWN has arrived. Return
+ %% a result instead of a timeout since we
+ %% just got the result...
+ receive
+ {spawn_reply, ReqId, error, Reason} ->
+ result(spawn_reply, ReqId, Res, Reason);
+ {'DOWN', ReqId, process, _Pid, Reason} ->
+ result(down, ReqId, Res, Reason)
+ after
+ 0 ->
+ %% Invalid request id...
+ error({?MODULE, badarg})
+ end
+ end.
+
+mcall_send_requests([], _M, _F, _A, RIDs) ->
+ RIDs;
+mcall_send_requests([N|Ns], M, F, A, RIDs) ->
+ RID = send_request(N, M, F, A),
+ mcall_send_requests(Ns, M, F, A, [RID|RIDs]);
+mcall_send_requests(_, _M, _F, _A, _RIDs) ->
+ error({?MODULE, badarg}).
+
+mcall_wait_replies([], Replies, _Tmo) ->
+ Replies;
+mcall_wait_replies([RID|RIDs], Replies, infinity) ->
+ Reply = mcall_wait_reply(RID, infinity),
+ mcall_wait_replies(RIDs, [Reply|Replies], infinity);
+mcall_wait_replies([RID|RIDs], Replies, EndTime) ->
+ Now = erlang:monotonic_time(millisecond),
+ Tmo = case EndTime - Now of
+ T when T =< 0 -> 0;
+ T -> T
+ end,
+ Reply = mcall_wait_reply(RID, Tmo),
+ mcall_wait_replies(RIDs, [Reply|Replies], EndTime).
+
+mcall_wait_reply(RID, Tmo) ->
+ try
+ {ok, receive_response(RID, Tmo)}
+ catch
+ Class:Reason ->
+ {Class, Reason}
+ end.
+
diff --git a/lib/kernel/src/error_logger.erl b/lib/kernel/src/error_logger.erl
index e324be5290..37771f4e60 100644
--- a/lib/kernel/src/error_logger.erl
+++ b/lib/kernel/src/error_logger.erl
@@ -147,15 +147,15 @@ do_log(Level,{report,Msg},#{?MODULE:=#{tag:=Tag}}=Meta) ->
_ ->
%% From logger call which added error_logger data to
%% obtain backwards compatibility with error_logger:*_msg/1,2
- case maps:get(report_cb,Meta,fun logger:format_report/1) of
+ case get_report_cb(Meta) of
RCBFun when is_function(RCBFun,1) ->
try RCBFun(Msg) of
{F,A} when is_list(F), is_list(A) ->
{F,A};
Other ->
{"REPORT_CB ERROR: ~tp; Returned: ~tp",[Msg,Other]}
- catch C:R ->
- {"REPORT_CB CRASH: ~tp; Reason: ~tp",[Msg,{C,R}]}
+ catch C:R:S ->
+ {"REPORT_CB CRASH: ~tp; Reason: ~tp",[Msg,{C,R,S}]}
end;
RCBFun when is_function(RCBFun,2) ->
try RCBFun(Msg,#{depth=>get_format_depth(),
@@ -216,6 +216,13 @@ fix_warning_type(error,std_warning) -> std_error;
fix_warning_type(info,std_warning) -> std_info;
fix_warning_type(_,Type) -> Type.
+get_report_cb(#{?MODULE:=#{report_cb:=RBFun}}) ->
+ RBFun;
+get_report_cb(#{report_cb:=RBFun}) ->
+ RBFun;
+get_report_cb(_) ->
+ fun logger:format_report/1.
+
%%-----------------------------------------------------------------
%% These two simple old functions generate events tagged 'error'
%% Used for simple messages; error or information.
diff --git a/lib/kernel/src/erts_debug.erl b/lib/kernel/src/erts_debug.erl
index e6a30d0b92..da86679532 100644
--- a/lib/kernel/src/erts_debug.erl
+++ b/lib/kernel/src/erts_debug.erl
@@ -40,6 +40,15 @@
lc_graph/0, lc_graph_to_dot/2, lc_graph_merge/2,
alloc_blocks_size/1]).
+%% Reroutes calls to the given MFA to error_handler:breakpoint/3
+%%
+%% Note that this is potentially unsafe as compiled code may assume that the
+%% targeted function returns a specific type, triggering undefined behavior if
+%% this function were to return something else.
+%%
+%% For reference, the debugger avoids the issue by purging the affected module
+%% and interpreting all functions in the module, ensuring that no assumptions
+%% are made with regard to return or argument types.
-spec breakpoint(MFA, Flag) -> non_neg_integer() when
MFA :: {Module :: module(),
Function :: atom(),
@@ -92,7 +101,7 @@ copy_shared(_) ->
-spec get_internal_state(W) -> term() when
W :: reds_left | node_and_dist_references | monitoring_nodes
- | next_pid | 'DbTable_words' | check_io_debug
+ | next_pid | 'DbTable_words' | check_io_debug | lc_graph
| process_info_args | processes | processes_bif_info
| max_atom_out_cache_index | nbalance | available_internal_state
| force_heap_frags | memory
@@ -517,43 +526,38 @@ alloc_blocks_size_1([], _Type, 0) ->
undefined;
alloc_blocks_size_1([{_Type, false} | Rest], Type, Acc) ->
alloc_blocks_size_1(Rest, Type, Acc);
-alloc_blocks_size_1([{Type, Instances} | Rest], Type, Acc0) ->
- F = fun ({instance, _, L}, Acc) ->
+alloc_blocks_size_1([{_Type, Instances} | Rest], Type, Acc) ->
+ F = fun ({instance, _, L}, Acc0) ->
MBCSPool = case lists:keyfind(mbcs_pool, 1, L) of
{_, Pool} -> Pool;
false -> []
end,
{_,MBCS} = lists:keyfind(mbcs, 1, L),
{_,SBCS} = lists:keyfind(sbcs, 1, L),
- Acc +
- sum_block_sizes(MBCSPool) +
- sum_block_sizes(MBCS) +
- sum_block_sizes(SBCS)
+ Acc1 = sum_block_sizes(MBCSPool, Type, Acc0),
+ Acc2 = sum_block_sizes(MBCS, Type, Acc1),
+ sum_block_sizes(SBCS, Type, Acc2)
end,
- alloc_blocks_size_1(Rest, Type, lists:foldl(F, Acc0, Instances));
-alloc_blocks_size_1([{_Type, Instances} | Rest], Type, Acc0) ->
- F = fun ({instance, _, L}, Acc) ->
- Acc + sum_foreign_sizes(Type, L)
- end,
- alloc_blocks_size_1(Rest, Type, lists:foldl(F, Acc0, Instances));
+ alloc_blocks_size_1(Rest, Type, lists:foldl(F, Acc, Instances));
alloc_blocks_size_1([], _Type, Acc) ->
Acc.
-sum_foreign_sizes(Type, L) ->
- case lists:keyfind(mbcs_pool, 1, L) of
- {_,Pool} ->
- {_,ForeignBlocks} = lists:keyfind(foreign_blocks, 1, Pool),
- case lists:keyfind(Type, 1, ForeignBlocks) of
- {_,TypeSizes} -> sum_block_sizes(TypeSizes);
- false -> 0
- end;
- _ ->
- 0
- end.
+sum_block_sizes([{blocks, List} | Rest], Type, Acc) ->
+ sum_block_sizes(Rest, Type, sum_block_sizes_1(List, Type, Acc));
+sum_block_sizes([_ | Rest], Type, Acc) ->
+ sum_block_sizes(Rest, Type, Acc);
+sum_block_sizes([], _Type, Acc) ->
+ Acc.
+
+sum_block_sizes_1([{Type, L} | Rest], Type, Acc0) ->
+ Acc = lists:foldl(fun({size, Sz,_,_}, Sz0) -> Sz0+Sz;
+ ({size, Sz}, Sz0) -> Sz0+Sz;
+ (_, Sz) -> Sz
+ end, Acc0, L),
+ sum_block_sizes_1(Rest, Type, Acc);
+sum_block_sizes_1([_ | Rest], Type, Acc) ->
+ sum_block_sizes_1(Rest, Type, Acc);
+sum_block_sizes_1([], _Type, Acc) ->
+ Acc.
+
-sum_block_sizes(Blocks) ->
- lists:foldl(
- fun({blocks_size, Sz,_,_}, Sz0) -> Sz0+Sz;
- ({blocks_size, Sz}, Sz0) -> Sz0+Sz;
- (_, Sz) -> Sz
- end, 0, Blocks).
diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl
index 1d4e37196c..cde03ce1c4 100644
--- a/lib/kernel/src/file.erl
+++ b/lib/kernel/src/file.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. 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.
@@ -239,20 +239,30 @@ make_dir(Name) ->
del_dir(Name) ->
check_and_call(del_dir, [file_name(Name)]).
--spec read_file_info(Filename) -> {ok, FileInfo} | {error, Reason} when
- Filename :: name_all(),
+-spec read_file_info(File) -> {ok, FileInfo} | {error, Reason} when
+ File :: name_all() | io_device(),
FileInfo :: file_info(),
Reason :: posix() | badarg.
+read_file_info(IoDevice)
+ when is_pid(IoDevice); is_record(IoDevice, file_descriptor) ->
+ read_file_info(IoDevice, []);
+
read_file_info(Name) ->
check_and_call(read_file_info, [file_name(Name)]).
--spec read_file_info(Filename, Opts) -> {ok, FileInfo} | {error, Reason} when
- Filename :: name_all(),
+-spec read_file_info(File, Opts) -> {ok, FileInfo} | {error, Reason} when
+ File :: name_all() | io_device(),
Opts :: [file_info_option()],
FileInfo :: file_info(),
Reason :: posix() | badarg.
+read_file_info(IoDevice, Opts) when is_pid(IoDevice), is_list(Opts) ->
+ file_request(IoDevice, {read_handle_info, Opts});
+
+read_file_info(#file_descriptor{module = Module} = Handle, Opts) when is_list(Opts) ->
+ Module:read_handle_info(Handle, Opts);
+
read_file_info(Name, Opts) when is_list(Opts) ->
Args = [file_name(Name), Opts],
case check_args(Args) of
@@ -460,7 +470,7 @@ raw_write_file_info(Name, #file_info{} = Info) ->
-spec open(File, Modes) -> {ok, IoDevice} | {error, Reason} when
File :: Filename | iodata(),
Filename :: name_all(),
- Modes :: [mode() | ram],
+ Modes :: [mode() | ram | directory],
IoDevice :: io_device(),
Reason :: posix() | badarg | system_limit.
@@ -545,7 +555,7 @@ allocate(#file_descriptor{module = Module} = Handle, Offset, Length) ->
| {no_translation, unicode, latin1}.
read(File, Sz) when (is_pid(File) orelse is_atom(File)), is_integer(Sz), Sz >= 0 ->
- case io:request(File, {get_chars, '', Sz}) of
+ case io:request(File, {get_chars, latin1, '', Sz}) of
Data when is_list(Data); is_binary(Data) ->
{ok, Data};
Other ->
@@ -566,7 +576,7 @@ read(_, _) ->
| {no_translation, unicode, latin1}.
read_line(File) when (is_pid(File) orelse is_atom(File)) ->
- case io:request(File, {get_line, ''}) of
+ case io:request(File, {get_line, latin1, ''}) of
Data when is_list(Data); is_binary(Data) ->
{ok, Data};
Other ->
@@ -1143,7 +1153,7 @@ path_script(Path, File, Bs) ->
{ok, IoDevice, FullName} | {error, Reason} when
Path :: [Dir :: name_all()],
Filename :: name_all(),
- Modes :: [mode()],
+ Modes :: [mode() | directory],
IoDevice :: io_device(),
FullName :: filename_all(),
Reason :: posix() | badarg | system_limit.
diff --git a/lib/kernel/src/file_io_server.erl b/lib/kernel/src/file_io_server.erl
index 34d5497a4a..c03fbb548a 100644
--- a/lib/kernel/src/file_io_server.erl
+++ b/lib/kernel/src/file_io_server.erl
@@ -314,6 +314,14 @@ file_request(truncate,
Reply ->
std_reply(Reply, State)
end;
+file_request({read_handle_info, Opts},
+ #state{handle=Handle}=State) ->
+ case ?CALL_FD(Handle, read_handle_info, [Opts]) of
+ {error,Reason}=Reply ->
+ {stop,Reason,Reply,State};
+ Reply ->
+ {reply,Reply,State}
+ end;
file_request(Unknown,
#state{}=State) ->
Reason = {request, Unknown},
diff --git a/lib/kernel/src/global.erl b/lib/kernel/src/global.erl
index 3875074d74..ff6674cd08 100644
--- a/lib/kernel/src/global.erl
+++ b/lib/kernel/src/global.erl
@@ -909,7 +909,7 @@ handle_info({nodeup, Node}, S0) when S0#state.connect_all ->
end;
handle_info({whereis, Name, From}, S) ->
- do_whereis(Name, From),
+ _ = do_whereis(Name, From),
{noreply, S};
handle_info(known, S) ->
diff --git a/lib/kernel/src/group.erl b/lib/kernel/src/group.erl
index 5625ae6eb7..8410c1a4b5 100644
--- a/lib/kernel/src/group.erl
+++ b/lib/kernel/src/group.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. 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.
@@ -73,7 +73,7 @@ get_pids([], _Found, false) ->
start_shell({Mod,Func,Args}) ->
start_shell1(Mod, Func, Args);
start_shell({Node,Mod,Func,Args}) ->
- start_shell1(net, call, [Node,Mod,Func,Args]);
+ start_shell1(rpc, call, [Node,Mod,Func,Args]);
start_shell(Shell) when is_atom(Shell) ->
start_shell1(Shell, start, []);
start_shell(Shell) when is_function(Shell) ->
@@ -795,12 +795,6 @@ save_line({stack, U, _L, D}, Line) ->
{stack, U, Line, D}.
get_lines(Ls) -> get_all_lines(Ls).
-%get_lines({stack, U, {}, []}) ->
-% U;
-%get_lines({stack, U, {}, D}) ->
-% tl(lists:reverse(D, U));
-%get_lines({stack, U, L, D}) ->
-% get_lines({stack, U, {}, [L|D]}).
%% There's a funny behaviour whenever the line stack doesn't have a "\n"
%% at its end -- get_lines() seemed to work on the assumption it *will* be
diff --git a/lib/kernel/src/group_history.erl b/lib/kernel/src/group_history.erl
index 9745848992..1fab2ba14e 100644
--- a/lib/kernel/src/group_history.erl
+++ b/lib/kernel/src/group_history.erl
@@ -44,7 +44,7 @@ load() ->
wait_for_kernel_safe_sup(),
case history_status() of
enabled ->
- case open_log() of
+ try open_log() of
{ok, ?LOG_NAME} ->
read_full_log(?LOG_NAME);
{repaired, ?LOG_NAME, {recovered, Good}, {badbytes, Bad}} ->
@@ -68,6 +68,10 @@ load() ->
handle_open_error(Reason),
disable_history(),
[]
+ catch
+ % disk_log shut down abruptly, possibly because
+ % the node is shutting down. Ignore it.
+ exit:_ -> []
end;
_ ->
[]
@@ -127,11 +131,15 @@ repair_log(Name) ->
%% Return whether the shell history is enabled or not
-spec history_status() -> enabled | disabled.
history_status() ->
- case is_user() orelse application:get_env(kernel, shell_history) of
- true -> disabled; % don't run for user proc
- {ok, enabled} -> enabled;
- undefined -> ?DEFAULT_STATUS;
- _ -> disabled
+ %% Don't run for user proc or if the emulator's tearing down
+ Skip = is_user() orelse not init_running(),
+ case application:get_env(kernel, shell_history) of
+ {ok, enabled} when not Skip ->
+ enabled;
+ undefined when not Skip ->
+ ?DEFAULT_STATUS;
+ _ ->
+ disabled
end.
%% Return whether the user process is running this
@@ -142,6 +150,14 @@ is_user() ->
_ -> false
end.
+%% Return if the system is running (not stopping)
+-spec init_running() -> boolean().
+init_running() ->
+ case init:get_status() of
+ {stopping, _} -> false;
+ _ -> true
+ end.
+
%% Open a disk_log file while ensuring the required path is there.
open_log() ->
Opts = log_options(),
@@ -322,7 +338,7 @@ show_unexpected_warning({M,F,A}, Term) ->
show_unexpected_close_warning() ->
show('$#erlang-history-unexpected-close',
- "The shell log file has mysteriousy closed. Ignoring "
+ "The shell log file has mysteriously closed. Ignoring "
"currently unread history.~n", []).
show_size_warning(_Current, _New) ->
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
index 630ef5d2f7..fbccc787a2 100644
--- a/lib/kernel/src/inet_db.erl
+++ b/lib/kernel/src/inet_db.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. 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.
@@ -229,12 +229,14 @@ set_edns(Version) -> res_option(edns, Version).
set_udp_payload_size(Size) -> res_option(udp_payload_size, Size).
-set_resolv_conf(Fname) -> res_option(resolv_conf, Fname).
+set_resolv_conf(Fname) when is_list(Fname) ->
+ res_option(resolv_conf, Fname).
-set_hosts_file(Fname) -> res_option(hosts_file, Fname).
+set_hosts_file(Fname) when is_list(Fname) ->
+ res_option(hosts_file, Fname).
get_hosts_file() ->
- get_rc_hosts([], [], inet_hosts_file_byname).
+ get_rc_hosts([], [], inet_hosts_file_byaddr).
%% set socks options
set_socks_server(Server) -> call({set_socks_server, Server}).
@@ -318,7 +320,7 @@ get_rc() ->
get_rc([K | Ks], Ls) ->
case K of
- hosts -> get_rc_hosts(Ks, Ls, inet_hosts_byname);
+ hosts -> get_rc_hosts(Ks, Ls, inet_hosts_byaddr);
domain -> get_rc(domain, res_domain, "", Ks, Ls);
nameservers -> get_rc_ns(db_get(res_ns),nameservers,Ks,Ls);
alt_nameservers -> get_rc_ns(db_get(res_alt_ns),alt_nameservers,Ks,Ls);
@@ -371,18 +373,11 @@ get_rc_ns([], _Tag, Ks, Ls) ->
get_rc(Ks, Ls).
get_rc_hosts(Ks, Ls, Tab) ->
- case lists:keysort(3, ets:tab2list(Tab)) of
+ case ets:tab2list(Tab) of
[] -> get_rc(Ks, Ls);
- [{N,_,IP}|Hosts] -> get_rc_hosts(Ks, Ls, IP, Hosts, [N])
+ Hosts -> get_rc(Ks, [ [{host, IP, Names} || {{_Fam, IP}, Names} <- Hosts] | Ls])
end.
-get_rc_hosts(Ks, Ls, IP, [], Ns) ->
- get_rc(Ks, [{host,IP,lists:reverse(Ns)}|Ls]);
-get_rc_hosts(Ks, Ls, IP, [{N,_,IP}|Hosts], Ns) ->
- get_rc_hosts(Ks, Ls, IP, Hosts, [N|Ns]);
-get_rc_hosts(Ks, Ls, IP, [{N,_,NewIP}|Hosts], Ns) ->
- [{host,IP,lists:reverse(Ns)}|get_rc_hosts(Ks, Ls, NewIP, Hosts, [N])].
-
%%
%% Resolver options
%%
@@ -502,49 +497,26 @@ socks_option(noproxy) -> db_get(socks5_noproxy).
gethostname() -> db_get(hostname).
res_update_conf() ->
- res_update(res_resolv_conf, res_resolv_conf_tm, res_resolv_conf_info,
- set_resolv_conf_tm, fun set_resolv_conf/1).
+ res_update(resolv_conf, res_resolv_conf_tm).
res_update_hosts() ->
- res_update(res_hosts_file, res_hosts_file_tm, res_hosts_file_info,
- set_hosts_file_tm, fun set_hosts_file/1).
+ res_update(hosts_file, res_hosts_file_tm).
-res_update(Tag, TagTm, TagInfo, TagSetTm, SetFun) ->
+res_update(Option, TagTm) ->
case db_get(TagTm) of
undefined -> ok;
- TM ->
+ Tm ->
case times() of
- Now when Now >= TM + ?RES_FILE_UPDATE_TM ->
- case db_get(Tag) of
- undefined ->
- SetFun("");
- "" ->
- SetFun("");
- File ->
- case erl_prim_loader:read_file_info(File) of
- {ok, Finfo0} ->
- Finfo =
- Finfo0#file_info{access = undefined,
- atime = undefined},
- case db_get(TagInfo) of
- Finfo ->
- call({TagSetTm, Now});
- _ ->
- SetFun(File)
- end;
- _ ->
- call({TagSetTm, Now}),
- error
- end
- end;
+ Now when Now >= Tm + ?RES_FILE_UPDATE_TM ->
+ %% Enough time has passed - request server to update
+ res_option(Option, Tm);
_ -> ok
end
end.
db_get(Name) ->
- case ets:lookup(inet_db, Name) of
- [] -> undefined;
- [{_,Val}] -> Val
+ try ets:lookup_element(inet_db, Name, 2)
+ catch error:badarg -> undefined
end.
add_rr(RR) ->
@@ -853,12 +825,10 @@ init([]) ->
reset_db(Db),
CacheOpts = [public, bag, {keypos,#dns_rr.domain}, named_table],
Cache = ets:new(inet_cache, CacheOpts),
- BynameOpts = [protected, bag, named_table, {keypos,1}],
- ByaddrOpts = [protected, bag, named_table, {keypos,3}],
- HostsByname = ets:new(inet_hosts_byname, BynameOpts),
- HostsByaddr = ets:new(inet_hosts_byaddr, ByaddrOpts),
- HostsFileByname = ets:new(inet_hosts_file_byname, BynameOpts),
- HostsFileByaddr = ets:new(inet_hosts_file_byaddr, ByaddrOpts),
+ HostsByname = ets:new(inet_hosts_byname, [named_table]),
+ HostsByaddr = ets:new(inet_hosts_byaddr, [named_table]),
+ HostsFileByname = ets:new(inet_hosts_file_byname, [named_table]),
+ HostsFileByaddr = ets:new(inet_hosts_file_byaddr, [named_table]),
{ok, #state{db = Db,
cache = Cache,
hosts_byname = HostsByname,
@@ -868,29 +838,31 @@ init([]) ->
cache_timer = init_timer() }}.
reset_db(Db) ->
- ets:insert(Db, {hostname, []}),
- ets:insert(Db, {res_ns, []}),
- ets:insert(Db, {res_alt_ns, []}),
- ets:insert(Db, {res_search, []}),
- ets:insert(Db, {res_domain, ""}),
- ets:insert(Db, {res_lookup, []}),
- ets:insert(Db, {res_recurse, true}),
- ets:insert(Db, {res_usevc, false}),
- ets:insert(Db, {res_id, 0}),
- ets:insert(Db, {res_retry, ?RES_RETRY}),
- ets:insert(Db, {res_timeout, ?RES_TIMEOUT}),
- ets:insert(Db, {res_inet6, false}),
- ets:insert(Db, {res_edns, false}),
- ets:insert(Db, {res_udp_payload_size, ?DNS_UDP_PAYLOAD_SIZE}),
- ets:insert(Db, {cache_size, ?CACHE_LIMIT}),
- ets:insert(Db, {cache_refresh_interval,?CACHE_REFRESH}),
- ets:insert(Db, {socks5_server, ""}),
- ets:insert(Db, {socks5_port, ?IPPORT_SOCKS}),
- ets:insert(Db, {socks5_methods, [none]}),
- ets:insert(Db, {socks5_noproxy, []}),
- ets:insert(Db, {tcp_module, ?DEFAULT_TCP_MODULE}),
- ets:insert(Db, {udp_module, ?DEFAULT_UDP_MODULE}),
- ets:insert(Db, {sctp_module, ?DEFAULT_SCTP_MODULE}).
+ ets:insert(
+ Db,
+ [{hostname, []},
+ {res_ns, []},
+ {res_alt_ns, []},
+ {res_search, []},
+ {res_domain, ""},
+ {res_lookup, []},
+ {res_recurse, true},
+ {res_usevc, false},
+ {res_id, 0},
+ {res_retry, ?RES_RETRY},
+ {res_timeout, ?RES_TIMEOUT},
+ {res_inet6, false},
+ {res_edns, false},
+ {res_udp_payload_size, ?DNS_UDP_PAYLOAD_SIZE},
+ {cache_size, ?CACHE_LIMIT},
+ {cache_refresh_interval,?CACHE_REFRESH},
+ {socks5_server, ""},
+ {socks5_port, ?IPPORT_SOCKS},
+ {socks5_methods, [none]},
+ {socks5_noproxy, []},
+ {tcp_module, ?DEFAULT_TCP_MODULE},
+ {udp_module, ?DEFAULT_UDP_MODULE},
+ {sctp_module, ?DEFAULT_SCTP_MODULE}]).
%%----------------------------------------------------------------------
%% Func: handle_call/3
@@ -908,22 +880,7 @@ reset_db(Db) ->
handle_call(Request, From, #state{db=Db}=State) ->
case Request of
{load_hosts_file,IPNmAs} when is_list(IPNmAs) ->
- NIPs =
- lists:flatten(
- [ [{N,
- if tuple_size(IP) =:= 4 -> inet;
- tuple_size(IP) =:= 8 -> inet6
- end,IP} || N <- [Nm|As]]
- || {IP,Nm,As} <- IPNmAs]),
- Byname = State#state.hosts_file_byname,
- Byaddr = State#state.hosts_file_byaddr,
- ets:delete_all_objects(Byname),
- ets:delete_all_objects(Byaddr),
- %% Byname has lowercased names while Byaddr keep the name casing.
- %% This is to be able to reconstruct the original
- %% /etc/hosts entry.
- ets:insert(Byname, [{tolower(N),Type,IP} || {N,Type,IP} <- NIPs]),
- ets:insert(Byaddr, NIPs),
+ load_hosts_list(IPNmAs, State#state.hosts_file_byname, State#state.hosts_file_byaddr),
{reply, ok, State};
{add_host,{A,B,C,D}=IP,[N|As]=Names}
@@ -969,7 +926,7 @@ handle_call(Request, From, #state{db=Db}=State) ->
case res_check_option(Opt, El) of
true ->
Optname = res_optname(Opt),
- [{_,Es}] = ets:lookup(Db, Optname),
+ Es = ets:lookup_element(Db, Optname, 2),
NewEs = case Op of
ins -> [E | lists_delete(E, Es)];
add -> lists_delete(E, Es) ++ El;
@@ -1003,12 +960,12 @@ handle_call(Request, From, #state{db=Db}=State) ->
Option, Fname, res_resolv_conf_tm, res_resolv_conf_info,
undefined, From, State);
- {res_set, hosts_file=Option, Fname} ->
+ {res_set, hosts_file=Option, Fname_or_Tm} ->
handle_set_file(
- Option, Fname, res_hosts_file_tm, res_hosts_file_info,
- fun (Bin) ->
+ Option, Fname_or_Tm, res_hosts_file_tm, res_hosts_file_info,
+ fun (File, Bin) ->
case inet_parse:hosts(
- Fname, {chars,Bin}) of
+ File, {chars,Bin}) of
{ok,Opts} ->
[{load_hosts_file,Opts}];
_ -> error
@@ -1016,12 +973,12 @@ handle_call(Request, From, #state{db=Db}=State) ->
end,
From, State);
%%
- {res_set, resolv_conf=Option, Fname} ->
+ {res_set, resolv_conf=Option, Fname_or_Tm} ->
handle_set_file(
- Option, Fname, res_resolv_conf_tm, res_resolv_conf_info,
- fun (Bin) ->
+ Option, Fname_or_Tm, res_resolv_conf_tm, res_resolv_conf_info,
+ fun (File, Bin) ->
case inet_parse:resolv(
- Fname, {chars,Bin}) of
+ File, {chars,Bin}) of
{ok,Opts} ->
Search =
lists:foldl(
@@ -1075,13 +1032,13 @@ handle_call(Request, From, #state{db=Db}=State) ->
{reply, ok, State};
{add_socks_methods, Ls} ->
- [{_,As}] = ets:lookup(Db, socks5_methods),
+ As = ets:lookup_element(Db, socks5_methods, 2),
As1 = lists_subtract(As, Ls),
ets:insert(Db, {socks5_methods, As1 ++ Ls}),
{reply, ok, State};
{del_socks_methods, Ls} ->
- [{_,As}] = ets:lookup(Db, socks5_methods),
+ As = ets:lookup_element(Db, socks5_methods, 2),
As1 = lists_subtract(As, Ls),
case lists:member(none, As1) of
false -> ets:insert(Db, {socks5_methods, As1 ++ [none]});
@@ -1095,12 +1052,12 @@ handle_call(Request, From, #state{db=Db}=State) ->
{add_socks_noproxy, {{A,B,C,D},{MA,MB,MC,MD}}}
when ?ip(A,B,C,D), ?ip(MA,MB,MC,MD) ->
- [{_,As}] = ets:lookup(Db, socks5_noproxy),
+ As = ets:lookup_element(Db, socks5_noproxy, 2),
ets:insert(Db, {socks5_noproxy, As++[{{A,B,C,D},{MA,MB,MC,MD}}]}),
{reply, ok, State};
{del_socks_noproxy, {A,B,C,D}=IP} when ?ip(A,B,C,D) ->
- [{_,As}] = ets:lookup(Db, socks5_noproxy),
+ As = ets:lookup_element(Db, socks5_noproxy, 2),
ets:insert(Db, {socks5_noproxy, lists_keydelete(IP, 1, As)}),
{reply, ok, State};
@@ -1194,68 +1151,283 @@ terminate(_Reason, State) ->
%%% Internal functions
%%%----------------------------------------------------------------------
-handle_set_file(Option, Fname, TagTm, TagInfo, ParseFun, From,
- #state{db=Db}=State) ->
+handle_set_file(
+ Option, Tm, TagTm, TagInfo, ParseFun, From, #state{db=Db}=State)
+ when is_integer(Tm) ->
+ %%
+ %% Maybe update file content
+ %%
+ try ets:lookup_element(Db, TagTm, 2) of
+ Tm ->
+ %% Current update request
+ File = ets:lookup_element(Db, res_optname(Option), 2),
+ Finfo = ets:lookup_element(Db, TagInfo, 2),
+ handle_update_file(
+ Finfo, File, TagTm, TagInfo, ParseFun, From, State);
+ _ ->
+ %% Late request - ignore update
+ {reply, ok, State}
+ catch error:badarg ->
+ %% Option no longer set - ignore update
+ {reply, ok, State}
+ end;
+handle_set_file(
+ Option, Fname, TagTm, TagInfo, ParseFun, From, #state{db=Db}=State) ->
case res_check_option(Option, Fname) of
true when Fname =:= "" ->
+ %% Delete file content and monitor
ets:insert(Db, {res_optname(Option), Fname}),
ets:delete(Db, TagInfo),
ets:delete(Db, TagTm),
- handle_set_file(ParseFun, <<>>, From, State);
+ handle_set_file(ParseFun, Fname, <<>>, From, State);
true when ParseFun =:= undefined ->
+ %% Set file name and monitor
File = filename:flatten(Fname),
ets:insert(Db, {res_optname(Option), File}),
ets:insert(Db, {TagInfo, undefined}),
- TimeZero = - (?RES_FILE_UPDATE_TM + 1), % Early enough
+ TimeZero = times() - (?RES_FILE_UPDATE_TM + 1), % Early enough
ets:insert(Db, {TagTm, TimeZero}),
{reply,ok,State};
true ->
+ %% Set file name and monitor, read content
File = filename:flatten(Fname),
ets:insert(Db, {res_optname(Option), File}),
- Bin =
- case erl_prim_loader:read_file_info(File) of
- {ok, Finfo0} ->
- Finfo = Finfo0#file_info{access = undefined,
- atime = undefined},
- ets:insert(Db, {TagInfo, Finfo}),
- ets:insert(Db, {TagTm, times()}),
- case erl_prim_loader:get_file(File) of
- {ok, B, _} -> B;
- _ -> <<>>
- end;
- _ ->
- ets:insert(Db, {TagInfo, undefined}),
- TimeZero = - (?RES_FILE_UPDATE_TM + 1), % Early enough
- ets:insert(Db, {TagTm, TimeZero}),
- <<>>
- end,
- handle_set_file(ParseFun, Bin, From, State);
+ handle_update_file(
+ undefined, File, TagTm, TagInfo, ParseFun, From, State);
false -> {reply,error,State}
end.
-handle_set_file(ParseFun, Bin, From, State) ->
- case ParseFun(Bin) of
+handle_set_file(ParseFun, File, Bin, From, State) ->
+ case ParseFun(File, Bin) of
error ->
{reply,error,State};
Opts ->
handle_rc_list(Opts, From, State)
end.
+handle_update_file(
+ Finfo, File, TagTm, TagInfo, ParseFun, From, #state{db = Db} = State) ->
+ %%
+ %% Update file content if file has been updated
+ %%
+ case erl_prim_loader:read_file_info(File) of
+ {ok, Finfo} ->
+ %% No file update - we are done
+ {reply, ok, State};
+ {ok, Finfo_1} ->
+ %% File updated - read content
+ ets:insert(Db, {TagInfo, Finfo_1}),
+ ets:insert(Db, {TagTm, times()}),
+ Bin =
+ case erl_prim_loader:get_file(File) of
+ {ok, B, _} -> B;
+ _ -> <<>>
+ end,
+ handle_set_file(ParseFun, File, Bin, From, State);
+ _ ->
+ %% No file - clear content and reset monitor
+ ets:insert(Db, {TagInfo, undefined}),
+ ets:insert(Db, {TagTm, times()}),
+ handle_set_file(ParseFun, File, <<>>, From, State)
+ end.
+
%% Byname has lowercased names while Byaddr keep the name casing.
%% This is to be able to reconstruct the original /etc/hosts entry.
do_add_host(Byname, Byaddr, Names, Type, IP) ->
- do_del_host(Byname, Byaddr, IP),
- ets:insert(Byname, [{tolower(N),Type,IP} || N <- Names]),
- ets:insert(Byaddr, [{N,Type,IP} || N <- Names]),
+ Nms = [tolower(Nm) || Nm <- Names],
+ add_ip_bynms(Byname, Type, IP, Nms, Names),
+ Key = {Type, IP},
+ try ets:lookup_element(Byaddr, Key, 2) of
+ Names_0 ->
+ %% Delete IP address from byname entries
+ NmsSet = % Set of new tolower(Name)s
+ lists:foldl(
+ fun (Nm, Set) ->
+ maps:put(Nm, [], Set)
+ end, #{}, Nms),
+ del_ip_bynms(
+ Byname, Type, IP,
+ [Nm || Nm <- [tolower(Name) || Name <- Names_0],
+ not maps:is_key(Nm, NmsSet)])
+ catch error:badarg ->
+ ok
+ end,
+ %% Replace the entry in the byaddr table
+ ets:insert(Byaddr, {Key, Names}),
ok.
do_del_host(Byname, Byaddr, IP) ->
- _ =
- [ets:delete_object(Byname, {tolower(Name),Type,Addr}) ||
- {Name,Type,Addr} <- ets:lookup(Byaddr, IP)],
- ets:delete(Byaddr, IP),
+ Fam = inet_family(IP),
+ Key = {Fam, IP},
+ try ets:lookup_element(Byaddr, Key, 2) of
+ Names ->
+ %% Delete IP address from byname entries
+ del_ip_bynms(
+ Byname, Fam, IP,
+ [tolower(Name) || Name <- Names]),
+ %% Delete from byaddr table
+ true = ets:delete(Byaddr, Key),
+ ok
+ catch error:badarg ->
+ ok
+ end.
+
+
+add_ip_bynms(Byname, Fam, IP, Nms, Names) ->
+ lists:foreach(
+ fun (Nm) ->
+ Key = {Fam, Nm},
+ case ets:lookup(Byname, Key) of
+ [{_Key, [IP | _] = IPs, _Names_1}] ->
+ %% Replace names in the byname entry
+ true =
+ ets:insert(
+ Byname,
+ {Key, IPs, Names});
+ [{_Key, IPs, Names_0}] ->
+ case lists:member(IP, IPs) of
+ true ->
+ ok;
+ false ->
+ %% Add the IP address
+ true =
+ ets:insert(
+ Byname,
+ {Key, IPs ++ [IP], Names_0})
+ end;
+ [] ->
+ %% Create a new byname entry
+ true =
+ ets:insert(Byname, {Key, [IP], Names})
+ end
+ end, Nms).
+
+del_ip_bynms(Byname, Fam, IP, Nms) ->
+ lists:foreach(
+ fun (Nm) ->
+ Key = {Fam, Nm},
+ case ets:lookup(Byname, Key) of
+ [{_Key, [IP], _Names}] ->
+ %% Delete whole entry
+ true = ets:delete(Byname, Key);
+ [{_Key, IPs_0, Names_0}] ->
+ case lists:member(IP, IPs_0) of
+ true ->
+ %% Delete the IP address from list
+ IPs = lists:delete(IP, IPs_0),
+ true =
+ ets:insert(
+ Byname, {Key, IPs, Names_0});
+ false ->
+ ok
+ end;
+ [] ->
+ ok
+ end
+ end, Nms).
+
+
+inet_family(T) when tuple_size(T) =:= 4 -> inet;
+inet_family(T) when tuple_size(T) =:= 8 -> inet6.
+
+
+%% Hosts = [ {IP, Name, Aliases}, ... ]
+%% ByaddrMap = #{ {Fam, IP} := rev(Names) }
+%% BynameMap = #{ {Fam, tolower(Name)} := {rev([IP, ...]), Names}}
+
+%% Synchronises internal tables with .hosts/aliases file
+load_hosts_list(Hosts, Byname, Byaddr) ->
+ %% Create byaddr and byname maps
+ {ByaddrMap, BynameMap} = load_hosts_list(Hosts),
+ %% Insert or overwrite existing keys
+ ets:insert(
+ Byaddr,
+ [{Addr, lists:reverse(NamesR)}
+ || {Addr, NamesR} <- maps:to_list(ByaddrMap)]),
+ ets:insert(
+ Byname,
+ [{Fam_Nm, lists:reverse(IPsR), Names}
+ || {Fam_Nm, {IPsR, Names}} <- maps:to_list(BynameMap)]),
+ %% Delete no longer existing keys
+ ets_clean_map_keys(Byaddr, ByaddrMap),
+ ets_clean_map_keys(Byname, BynameMap).
+
+load_hosts_list(Hosts) ->
+ load_hosts_list_byaddr(Hosts, #{}, []).
+
+load_hosts_list_byaddr(
+ [], ByaddrMap, Addrs) ->
+ %% Now for the byname table...
+ load_hosts_list_byname(lists:reverse(Addrs), ByaddrMap, #{});
+%% Traverse hosts list, create byaddr map and insertion order list
+load_hosts_list_byaddr(
+ [{IP, Name, Aliases} | Hosts], ByaddrMap, Addrs) ->
+ Addr = {inet_family(IP), IP},
+ case ByaddrMap of
+ #{Addr := NamesR} ->
+ %% Concatenate names to existing IP address entry
+ load_hosts_list_byaddr(
+ Hosts,
+ ByaddrMap#{Addr := lists:reverse(Aliases, [Name | NamesR])},
+ Addrs);
+ #{} ->
+ %% First entry for an IP address
+ load_hosts_list_byaddr(
+ Hosts,
+ ByaddrMap#{Addr => lists:reverse(Aliases, [Name])},
+ [Addr | Addrs])
+ end.
+
+%% Traverse in insertion order from byaddr pass
+load_hosts_list_byname(
+ [], ByaddrMap, BynameMap) ->
+ {ByaddrMap, BynameMap};
+load_hosts_list_byname(
+ [{Fam, IP} = Addr | Addrs], ByaddrMap, BynameMap) ->
+ Names = lists:reverse(maps:get(Addr, ByaddrMap)),
+ %% Traverse all names for this IP address
+ load_hosts_list_byname(
+ Addrs, ByaddrMap,
+ load_hosts_list_byname(Fam, IP, BynameMap, Names, Names)).
+
+load_hosts_list_byname(_Fam, _IP, BynameMap, _Names_0, []) ->
+ BynameMap;
+load_hosts_list_byname(
+ Fam, IP, BynameMap, Names_0, [Name | Names]) ->
+ Key = {Fam, tolower(Name)},
+ case BynameMap of
+ #{Key := {IPsR, Names_1}} ->
+ %% Add IP address to existing name entry
+ load_hosts_list_byname(
+ Fam, IP,
+ BynameMap#{Key := {[IP | IPsR], Names_1}},
+ Names_0, Names);
+ #{} ->
+ %% First entry for a name
+ load_hosts_list_byname(
+ Fam, IP,
+ BynameMap#{Key => {[IP], Names_0}},
+ Names_0, Names)
+ end.
+
+ets_clean_map_keys(Tab, Map) ->
+ true = ets:safe_fixtable(Tab, true),
+ ets_clean_map_keys(Tab, Map, ets:first(Tab)),
+ true = ets:safe_fixtable(Tab, false),
ok.
+%%
+ets_clean_map_keys(_Tab, _Map, '$end_of_table') ->
+ ok;
+ets_clean_map_keys(Tab, Map, Key) ->
+ case maps:is_key(Key, Map) of
+ true ->
+ ets_clean_map_keys(Tab, Map, ets:next(Tab, Key));
+ false ->
+ true = ets:delete(Tab, Key),
+ ets_clean_map_keys(Tab, Map, ets:next(Tab, Key))
+ end.
+
%% Loop over .inetrc option list and call handle_call/3 for each
%%
@@ -1383,8 +1555,9 @@ cache_rr(_Db, Cache, RR) ->
ets:insert(Cache, RR).
times() ->
- erlang:convert_time_unit(erlang:monotonic_time() - erlang:system_info(start_time),
- native, second).
+ erlang:monotonic_time(second).
+ %% erlang:convert_time_unit(erlang:monotonic_time() - erlang:system_info(start_time),
+ %% native, second).
%% lookup and remove old entries
@@ -1518,12 +1691,12 @@ do_refresh_cache(Key, CacheDb, Now, OldestT) ->
%% -------------------------------------------------------------------
alloc_entry(Db, CacheDb, TM) ->
CurSize = ets:info(CacheDb, size),
- case ets:lookup(Db, cache_size) of
- [{cache_size, Size}] when Size =< CurSize, Size > 0 ->
+ case ets:lookup_element(Db, cache_size, 2) of
+ Size when Size =< CurSize, Size > 0 ->
alloc_entry(CacheDb, CurSize, TM, trunc(Size * 0.1) + 1);
- [{cache_size, Size}] when Size =< 0 ->
+ Size when Size =< 0 ->
false;
- _ ->
+ _Size ->
true
end.
diff --git a/lib/kernel/src/inet_hosts.erl b/lib/kernel/src/inet_hosts.erl
index fc653bf0d3..6dce48cf42 100644
--- a/lib/kernel/src/inet_hosts.erl
+++ b/lib/kernel/src/inet_hosts.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. 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.
@@ -41,12 +41,10 @@ gethostbyname(_) -> {error, formerr}.
gethostbyname(Name, Type) when is_list(Name), is_atom(Type) ->
%% Byname has lowercased names while Byaddr keep the name casing.
%% This is to be able to reconstruct the original /etc/hosts entry.
- N = inet_db:tolower(Name),
- case gethostbyname(N, Type, inet_hosts_byname, inet_hosts_byaddr) of
+ Nm = inet_db:tolower(Name),
+ case gethostbyname(Nm, Type, inet_hosts_byname) of
false ->
- case gethostbyname(N, Type,
- inet_hosts_file_byname,
- inet_hosts_file_byaddr) of
+ case gethostbyname(Nm, Type, inet_hosts_file_byname) of
false -> {error,nxdomain};
Hostent -> {ok,Hostent}
end;
@@ -56,15 +54,12 @@ gethostbyname(Name, Type) when is_atom(Name), is_atom(Type) ->
gethostbyname(atom_to_list(Name), Type);
gethostbyname(_, _) -> {error, formerr}.
-gethostbyname(Name, Type, Byname, Byaddr) ->
+gethostbyname(Nm, Type, Byname) ->
inet_db:res_update_hosts(),
- case [I || [I] <- ets:match(Byname, {Name,Type,'$1'})] of
+ case ets:lookup(Byname, {Type, Nm}) of
[] -> false;
- [IP|_]=IPs ->
- %% Use the primary IP address to generate aliases
- [Nm|As] = [N || [N] <- ets:match(Byaddr,
- {'$1',Type,IP})],
- make_hostent(Nm, IPs, As, Type)
+ [{_, IPs, [Primary | Aliases]}] ->
+ make_hostent(Primary, IPs, Aliases, Type)
end.
@@ -97,13 +92,12 @@ gethostbyaddr(IP, Type) ->
gethostbyaddr(IP, Type, Byaddr) ->
inet_db:res_update_hosts(),
- case [N || [N] <- ets:match(Byaddr, {'$1',Type,IP})] of
+ case ets:lookup(Byaddr, {Type, IP}) of
[] -> false;
- [Nm|As] -> make_hostent(Nm, [IP], As, Type)
+ [{_, [Primary | Aliases]}] -> make_hostent(Primary, [IP], Aliases, Type)
end.
-
make_hostent(Name, Addrs, Aliases, inet) ->
#hostent {
h_name = Name,
diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src
index 35c0c3f88e..f7f6a3b497 100644
--- a/lib/kernel/src/kernel.app.src
+++ b/lib/kernel/src/kernel.app.src
@@ -36,6 +36,7 @@
erl_distribution,
erl_reply,
erl_signal_handler,
+ erpc,
error_handler,
error_logger,
file,
@@ -103,6 +104,7 @@
inet_tcp,
inet_udp,
inet_sctp,
+ pg,
pg2,
raw_file_io,
raw_file_io_compressed,
@@ -143,12 +145,14 @@
ddll_server,
erl_epmd,
inet_db,
+ pg,
pg2]},
{applications, []},
{env, [{logger_level, notice},
- {logger_sasl_compatible, false}
+ {logger_sasl_compatible, false},
+ {shell_docs_ansi,auto}
]},
{mod, {kernel, []}},
- {runtime_dependencies, ["erts-10.6", "stdlib-3.5", "sasl-3.0"]}
+ {runtime_dependencies, ["erts-@OTP-15251@", "stdlib-@OTP-15251@", "sasl-3.0"]}
]
}.
diff --git a/lib/kernel/src/kernel.erl b/lib/kernel/src/kernel.erl
index 8877ceea8e..83f3fbecd5 100644
--- a/lib/kernel/src/kernel.erl
+++ b/lib/kernel/src/kernel.erl
@@ -64,7 +64,7 @@ config_change(Changed, New, Removed) ->
%%% (file,code, | erl_dist (A)| | safe_sup (1)|
%%% rpc, ...) ------------- -------------
%%% | |
-%%% (net_kernel, (disk_log, pg2,
+%%% (net_kernel, (disk_log, pg,
%%% auth, ...) ...)
%%%
%%% The rectangular boxes are supervisors. All supervisors except
@@ -180,7 +180,7 @@ init(safe) ->
Boot = start_boot_server(),
DiskLog = start_disk_log(),
- Pg2 = start_pg2(),
+ Pg = start_pg2() ++ start_pg(),
%% Run the on_load handlers for all modules that have been
%% loaded so far. Running them at this point means that
@@ -188,7 +188,7 @@ init(safe) ->
%% (and in particular call code:priv_dir/1 or code:lib_dir/1).
init:run_on_load_handlers(),
- {ok, {SupFlags, Boot ++ DiskLog ++ Pg2}}.
+ {ok, {SupFlags, Boot ++ DiskLog ++ Pg}}.
start_distribution() ->
Rpc = #{id => rex,
@@ -279,6 +279,19 @@ start_disk_log() ->
[]
end.
+start_pg() ->
+ case application:get_env(kernel, start_pg) of
+ {ok, true} ->
+ [#{id => pg,
+ start => {pg, start_link, []},
+ restart => permanent,
+ shutdown => 1000,
+ type => worker,
+ modules => [pg]}];
+ _ ->
+ []
+ end.
+
start_pg2() ->
case application:get_env(kernel, start_pg2) of
{ok, true} ->
diff --git a/lib/kernel/src/logger.erl b/lib/kernel/src/logger.erl
index fd02cf67bf..e51407f482 100644
--- a/lib/kernel/src/logger.erl
+++ b/lib/kernel/src/logger.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2019. 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.
@@ -256,9 +256,8 @@ log(Level, FunOrFormat, Args, Metadata) ->
-spec allow(Level,Module) -> boolean() when
Level :: level(),
Module :: module().
-allow(Level,Module) when ?IS_LEVEL(Level), is_atom(Module) ->
- logger_config:allow(?LOGGER_TABLE,Level,Module).
-
+allow(Level,Module) when is_atom(Module) ->
+ logger_config:allow(Level,Module).
-spec macro_log(Location,Level,StringOrReport) -> ok when
Location :: location(),
@@ -571,8 +570,8 @@ set_application_level(App,Level) ->
{error, {not_loaded, App}}
end.
--spec unset_application_level(Application) -> ok | {error, not_loaded} when
- Application :: atom().
+-spec unset_application_level(Application) ->
+ ok | {error, {not_loaded, Application}} when Application :: atom().
unset_application_level(App) ->
case application:get_key(App, modules) of
{ok, Modules} ->
@@ -595,7 +594,7 @@ get_module_level(Modules) when is_list(Modules) ->
Module :: module(),
Level :: level() | all | none.
get_module_level() ->
- logger_config:get_module_level(?LOGGER_TABLE).
+ logger_config:get_module_level().
%%%-----------------------------------------------------------------
%%% Misc
@@ -1027,14 +1026,14 @@ get_logger_env(App) ->
%%%-----------------------------------------------------------------
%%% Internal
do_log(Level,Msg,#{mfa:={Module,_,_}}=Meta) ->
- case logger_config:allow(?LOGGER_TABLE,Level,Module) of
+ case logger_config:allow(Level,Module) of
true ->
log_allowed(#{},Level,Msg,Meta);
false ->
ok
end;
do_log(Level,Msg,Meta) ->
- case logger_config:allow(?LOGGER_TABLE,Level) of
+ case logger_config:allow(Level) of
true ->
log_allowed(#{},Level,Msg,Meta);
false ->
@@ -1051,7 +1050,7 @@ do_log(Level,Msg,Meta) ->
Meta :: metadata().
log_allowed(Location,Level,{Fun,FunArgs},Meta) when is_function(Fun,1) ->
try Fun(FunArgs) of
- Msg={Format,Args} when is_list(Format), is_list(Args) ->
+ Msg={Format,Args} when ?IS_FORMAT(Format), is_list(Args) ->
log_allowed(Location,Level,Msg,Meta);
Report when ?IS_REPORT(Report) ->
log_allowed(Location,Level,Report,Meta);
@@ -1086,7 +1085,7 @@ log_allowed(Location,Level,Msg,Meta0) when is_map(Meta0) ->
do_log_allowed(Level,{Format,Args}=Msg,Meta,Tid)
when ?IS_LEVEL(Level),
- is_list(Format),
+ ?IS_FORMAT(Format),
is_list(Args),
is_map(Meta) ->
logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},Tid);
diff --git a/lib/kernel/src/logger_backend.erl b/lib/kernel/src/logger_backend.erl
index 432c671afd..7003d2268e 100644
--- a/lib/kernel/src/logger_backend.erl
+++ b/lib/kernel/src/logger_backend.erl
@@ -56,6 +56,7 @@ call_handlers(#{level:=Level}=Log,[Id|Handlers],Tid) ->
error,{removed_failing_handler,Id}),
?LOG_INTERNAL(
debug,
+ Log1,
[{logger,removed_failing_handler},
{handler,{Id,Module}},
{log_event,Log1},
@@ -68,6 +69,7 @@ call_handlers(#{level:=Level}=Log,[Id|Handlers],Tid) ->
{error,Reason} ->
?LOG_INTERNAL(
debug,
+ Log1,
[{logger,remove_handler_failed},
{reason,Reason}])
end
@@ -119,6 +121,7 @@ handle_filter_failed({Id,_}=Filter,Owner,Log,Reason) ->
ok ->
logger:internal_log(error,{removed_failing_filter,Id}),
?LOG_INTERNAL(debug,
+ Log,
[{logger,removed_failing_filter},
{filter,Filter},
{owner,Owner},
diff --git a/lib/kernel/src/logger_config.erl b/lib/kernel/src/logger_config.erl
index af8ebfd4e9..4495790c30 100644
--- a/lib/kernel/src/logger_config.erl
+++ b/lib/kernel/src/logger_config.erl
@@ -21,120 +21,142 @@
-export([new/1,delete/2,
exist/2,
- allow/2,allow/3,
+ allow/1,allow/2,
get/2, get/3,
create/3, set/3,
- set_module_level/3,unset_module_level/2,
- get_module_level/1,cache_module_level/2,
+ set_module_level/2,unset_module_level/1,
+ get_module_level/0,
level_to_int/1]).
-include("logger_internal.hrl").
+-compile({inline,[get_primary_level/0,level_to_int/1]}).
+
+-define(LEVEL_TO_CACHE(Level),Level + 16#10).
+-define(PRIMARY_TO_CACHE(Level),Level).
+-define(IS_CACHED(Level),(Level =< ?LOG_ALL)).
+-define(CACHE_TO_LEVEL(Level),if ?IS_CACHED(Level) -> Level; true -> Level - 16#10 end).
+
+-define(IS_MODULE(Module),is_atom(Module) andalso Module =/= ?PRIMARY_KEY).
+
new(Name) ->
_ = ets:new(Name,[set,protected,named_table,
{read_concurrency,true},
{write_concurrency,true}]),
ets:whereis(Name).
-delete(Tid,Id) ->
- ets:delete(Tid,table_key(Id)).
-
-allow(Tid,Level,Module) ->
- LevelInt = level_to_int(Level),
- try ets:lookup(Tid,Module) of
- [{Module,{ModLevel,cached}}] when is_integer(ModLevel),
- LevelInt =< ModLevel ->
- true;
- [{Module,ModLevel}] when is_integer(ModLevel),
- LevelInt =< ModLevel ->
- true;
- [] ->
- logger_server:cache_module_level(Module),
- allow(Tid,Level);
- _ ->
- false
- catch error:badarg ->
- true
- end.
-
-allow(Tid,Level) ->
- try ets:lookup_element(Tid,?PRIMARY_KEY,2) of
- GlobalLevelInt ->
- level_to_int(Level) =< GlobalLevelInt
- catch error:badarg ->
- true
- end.
+delete(Tid,What) ->
+ persistent_term:put({?MODULE,table_key(What)},undefined),
+ ets:delete(Tid,table_key(What)).
+
+%% Optimized for speed.
+allow(Level,Module) ->
+ ModLevel =
+ case persistent_term:get({?MODULE,Module},undefined) of
+ undefined ->
+ %% This is where the module cache takes place. We insert the module level
+ %% plus 16 into the pt and then when looking it up we need to do a check
+ %% and subtraction.
+ %% The reason why we do this dance and not just wrap it all in a tuple
+ %% is because updates of immediates (i.e. small ints in this case)
+ %% is cheap even in pt, so we cannot put any complex terms in there.
+ IntLevel = get_primary_level(),
+ persistent_term:put({?MODULE,Module},?PRIMARY_TO_CACHE(IntLevel)),
+ IntLevel;
+ IntLevel ->
+ ?CACHE_TO_LEVEL(IntLevel)
+ end,
+ less_or_equal_level(Level,ModLevel).
+
+allow(Level) ->
+ PrimaryLevelInt = get_primary_level(),
+ less_or_equal_level(Level,PrimaryLevelInt).
+
+less_or_equal_level(emergency,ModLevel) -> ?EMERGENCY =< ModLevel;
+less_or_equal_level(alert,ModLevel) -> ?ALERT =< ModLevel;
+less_or_equal_level(critical,ModLevel) -> ?CRITICAL =< ModLevel;
+less_or_equal_level(error,ModLevel) -> ?ERROR =< ModLevel;
+less_or_equal_level(warning,ModLevel) -> ?WARNING =< ModLevel;
+less_or_equal_level(notice,ModLevel) -> ?NOTICE =< ModLevel;
+less_or_equal_level(info,ModLevel) -> ?INFO =< ModLevel;
+less_or_equal_level(debug,ModLevel) -> ?DEBUG =< ModLevel.
exist(Tid,What) ->
ets:member(Tid,table_key(What)).
+get_primary_level() ->
+ persistent_term:get({?MODULE,?PRIMARY_KEY},?NOTICE).
+
get(Tid,What) ->
case ets:lookup(Tid,table_key(What)) of
- [{_,_,Config}] ->
- {ok,Config};
- [{_,Config}] when What=:=proxy ->
+ [{_,Config}] ->
{ok,Config};
[] ->
{error,{not_found,What}}
end.
get(Tid,What,Level) ->
- MS = [{{table_key(What),'$1','$2'},
- [{'>=','$1',level_to_int(Level)}],
- ['$2']}],
- case ets:select(Tid,MS) of
- [] -> error;
- [Data] -> {ok,Data}
+ TableKey = table_key(What),
+ case persistent_term:get({?MODULE,TableKey},undefined) of
+ undefined ->
+ %% The handler is not installed at the moment
+ {error,{not_found,What}};
+ ConfLevel ->
+ case less_or_equal_level(Level,ConfLevel) of
+ true ->
+ get(Tid, What);
+ false ->
+ error
+ end
end.
create(Tid,proxy,Config) ->
ets:insert(Tid,{table_key(proxy),Config});
create(Tid,What,Config) ->
LevelInt = level_to_int(maps:get(level,Config)),
- ets:insert(Tid,{table_key(What),LevelInt,Config}).
+ ok = persistent_term:put({?MODULE,table_key(What)}, LevelInt),
+ ets:insert(Tid,{table_key(What),Config}).
set(Tid,proxy,Config) ->
ets:insert(Tid,{table_key(proxy),Config}),
ok;
set(Tid,What,Config) ->
LevelInt = level_to_int(maps:get(level,Config)),
- %% Should do this only if the level has actually changed. Possibly
- %% overwrite instead of delete?
+ ok = persistent_term:put({?MODULE,table_key(What)}, LevelInt),
case What of
primary ->
- _ = ets:select_delete(Tid,[{{'_',{'$1',cached}},
- [{'=/=','$1',LevelInt}],
- [true]}]),
+ [persistent_term:put(Key,?PRIMARY_TO_CACHE(LevelInt))
+ || {{?MODULE,Module} = Key,Level} <- persistent_term:get(),
+ ?IS_MODULE(Module), ?IS_CACHED(Level)],
ok;
_ ->
ok
end,
- ets:update_element(Tid,table_key(What),[{2,LevelInt},{3,Config}]),
+ ets:insert(Tid,{table_key(What),Config}),
ok.
-set_module_level(Tid,Modules,Level) ->
+set_module_level(Modules,Level) ->
LevelInt = level_to_int(Level),
- [ets:insert(Tid,{Module,LevelInt}) || Module <- Modules],
+ [persistent_term:put({?MODULE,Module},?LEVEL_TO_CACHE(LevelInt)) || Module <- Modules],
ok.
-%% should possibly overwrite instead of delete?
-unset_module_level(Tid,all) ->
- MS = [{{'$1','$2'},[{is_atom,'$1'},{is_integer,'$2'}],[true]}],
- _ = ets:select_delete(Tid,MS),
+%% We overwrite instead of delete because that is more efficient
+%% when using persistent_term
+unset_module_level(all) ->
+ PrimaryLevel = get_primary_level(),
+ [persistent_term:put(Key, ?PRIMARY_TO_CACHE(PrimaryLevel))
+ || {{?MODULE, Module} = Key,_} <- persistent_term:get(), ?IS_MODULE(Module)],
ok;
-unset_module_level(Tid,Modules) ->
- [ets:delete(Tid,Module) || Module <- Modules],
+unset_module_level(Modules) ->
+ PrimaryLevel = get_primary_level(),
+ [persistent_term:put({?MODULE,Module}, ?PRIMARY_TO_CACHE(PrimaryLevel)) || Module <- Modules],
ok.
-get_module_level(Tid) ->
- MS = [{{'$1','$2'},[{is_atom,'$1'},{is_integer,'$2'}],[{{'$1','$2'}}]}],
- Modules = ets:select(Tid,MS),
- lists:sort([{M,int_to_level(L)} || {M,L} <- Modules]).
-
-cache_module_level(Tid,Module) ->
- GlobalLevelInt = ets:lookup_element(Tid,?PRIMARY_KEY,2),
- ets:insert_new(Tid,{Module,{GlobalLevelInt,cached}}),
- ok.
+get_module_level() ->
+ lists:sort(
+ [{Module,int_to_level(?CACHE_TO_LEVEL(Level))}
+ || {{?MODULE, Module},Level} <- persistent_term:get(),
+ ?IS_MODULE(Module), not ?IS_CACHED(Level)]).
level_to_int(none) -> ?LOG_NONE;
level_to_int(emergency) -> ?EMERGENCY;
diff --git a/lib/kernel/src/logger_formatter.erl b/lib/kernel/src/logger_formatter.erl
index 8696adbd72..0a145b16d5 100644
--- a/lib/kernel/src/logger_formatter.erl
+++ b/lib/kernel/src/logger_formatter.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2019. 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.
@@ -323,7 +323,7 @@ timestamp_to_datetimemicro(SysTime,Config) when is_integer(SysTime) ->
{Date,Time,Micro,UtcStr}.
format_mfa({M,F,A},_) when is_atom(M), is_atom(F), is_integer(A) ->
- atom_to_list(M)++":"++atom_to_list(F)++"/"++integer_to_list(A);
+ io_lib:fwrite("~tw:~tw/~w", [M, F, A]);
format_mfa({M,F,A},Config) when is_atom(M), is_atom(F), is_list(A) ->
format_mfa({M,F,length(A)},Config);
format_mfa(MFA,Config) ->
diff --git a/lib/kernel/src/logger_h_common.erl b/lib/kernel/src/logger_h_common.erl
index 16946ff97c..fb150d181b 100644
--- a/lib/kernel/src/logger_h_common.erl
+++ b/lib/kernel/src/logger_h_common.erl
@@ -392,26 +392,26 @@ do_log_to_binary(Log,Config) ->
String = try_format(Log,Formatter,FormatterConfig),
try string_to_binary(String)
catch C2:R2:S2 ->
- ?LOG_INTERNAL(debug,[{formatter_error,Formatter},
- {config,FormatterConfig},
- {log_event,Log},
- {bad_return_value,String},
- {catched,{C2,R2,S2}}]),
- <<"FORMATTER ERROR: bad return value">>
+ ?LOG_INTERNAL(debug,Log,[{formatter_error,Formatter},
+ {config,FormatterConfig},
+ {log_event,Log},
+ {bad_return_value,String},
+ {catched,{C2,R2,S2}}]),
+ <<"FORMATTER ERROR: bad return value\n">>
end.
try_format(Log,Formatter,FormatterConfig) ->
try Formatter:format(Log,FormatterConfig)
catch
C:R:S ->
- ?LOG_INTERNAL(debug,[{formatter_crashed,Formatter},
- {config,FormatterConfig},
- {log_event,Log},
- {reason,
- {C,R,logger:filter_stacktrace(?MODULE,S)}}]),
+ ?LOG_INTERNAL(debug,Log,[{formatter_crashed,Formatter},
+ {config,FormatterConfig},
+ {log_event,Log},
+ {reason,
+ {C,R,logger:filter_stacktrace(?MODULE,S)}}]),
case {?DEFAULT_FORMATTER,#{}} of
{Formatter,FormatterConfig} ->
- "DEFAULT FORMATTER CRASHED";
+ "DEFAULT FORMATTER CRASHED\n";
{DefaultFormatter,DefaultConfig} ->
try_format(Log#{msg=>{"FORMATTER CRASH: ~tp",
[maps:get(msg,Log)]}},
diff --git a/lib/kernel/src/logger_handler_watcher.erl b/lib/kernel/src/logger_handler_watcher.erl
index b75c74c643..6f622b169c 100644
--- a/lib/kernel/src/logger_handler_watcher.erl
+++ b/lib/kernel/src/logger_handler_watcher.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2019. 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.
@@ -51,42 +51,25 @@ register_handler(Id,Pid) ->
%%% gen_server callbacks
%%%===================================================================
--spec init(Args :: term()) -> {ok, State :: term()} |
- {ok, State :: term(), Timeout :: timeout()} |
- {ok, State :: term(), hibernate} |
- {stop, Reason :: term()} |
- ignore.
+-spec init(Args :: term()) -> {ok, State :: term()}.
init([]) ->
process_flag(trap_exit, true),
{ok, #state{handlers=[]}}.
-spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) ->
- {reply, Reply :: term(), NewState :: term()} |
- {reply, Reply :: term(), NewState :: term(), Timeout :: timeout()} |
- {reply, Reply :: term(), NewState :: term(), hibernate} |
- {noreply, NewState :: term()} |
- {noreply, NewState :: term(), Timeout :: timeout()} |
- {noreply, NewState :: term(), hibernate} |
- {stop, Reason :: term(), Reply :: term(), NewState :: term()} |
- {stop, Reason :: term(), NewState :: term()}.
+ {reply, ok, NewState :: term()}.
handle_call({register,Id,Pid}, _From, #state{handlers=Hs}=State) ->
Ref = erlang:monitor(process,Pid),
Hs1 = lists:keystore(Id,1,Hs,{Id,Ref}),
{reply, ok, State#state{handlers=Hs1}}.
-spec handle_cast(Request :: term(), State :: term()) ->
- {noreply, NewState :: term()} |
- {noreply, NewState :: term(), Timeout :: timeout()} |
- {noreply, NewState :: term(), hibernate} |
- {stop, Reason :: term(), NewState :: term()}.
+ {noreply, NewState :: term()}.
handle_cast(_Request, State) ->
{noreply, State}.
-spec handle_info(Info :: timeout() | term(), State :: term()) ->
- {noreply, NewState :: term()} |
- {noreply, NewState :: term(), Timeout :: timeout()} |
- {noreply, NewState :: term(), hibernate} |
- {stop, Reason :: normal | term(), NewState :: term()}.
+ {noreply, NewState :: term()}.
handle_info({'DOWN',Ref,process,_,shutdown}, #state{handlers=Hs}=State) ->
case lists:keytake(Ref,2,Hs) of
{value,{Id,Ref},Hs1} ->
@@ -108,6 +91,6 @@ handle_info(_Other,State) ->
{noreply,State}.
-spec terminate(Reason :: normal | shutdown | {shutdown, term()} | term(),
- State :: term()) -> any().
+ State :: term()) -> ok.
terminate(_Reason, _State) ->
ok.
diff --git a/lib/kernel/src/logger_internal.hrl b/lib/kernel/src/logger_internal.hrl
index c2b2d419e7..0a5c4c944f 100644
--- a/lib/kernel/src/logger_internal.hrl
+++ b/lib/kernel/src/logger_internal.hrl
@@ -41,14 +41,14 @@
-define(DEFAULT_LOGGER_CALL_TIMEOUT, infinity).
--define(LOG_INTERNAL(Level,Report),?DO_LOG_INTERNAL(Level,[Report])).
--define(LOG_INTERNAL(Level,Format,Args),?DO_LOG_INTERNAL(Level,[Format,Args])).
--define(DO_LOG_INTERNAL(Level,Data),
+-define(LOG_INTERNAL(Level,Log,Report),
+ ?DO_LOG_INTERNAL(Level,Log,[Report])).
+-define(LOG_INTERNAL(Level,Log,Format,Args),
+ ?DO_LOG_INTERNAL(Level,Log,[Format,Args])).
+-define(DO_LOG_INTERNAL(Level,Log,Data),
case logger:allow(Level,?MODULE) of
true ->
- %% Spawn this to avoid deadlocks
- _ = spawn(logger,macro_log,[?LOCATION,Level|Data]++
- [logger:add_default_metadata(#{})]),
+ _ = logger_server:do_internal_log(Level,?LOCATION,Log,Data),
ok;
false ->
ok
@@ -91,7 +91,7 @@
-define(IS_LEVEL_ALL(L),
?IS_LEVEL(L) orelse
L=:=all orelse
- L=:=none ).
+ L=:=none ).
-define(IS_MSG(Msg),
((is_tuple(Msg) andalso tuple_size(Msg)==2)
@@ -107,3 +107,6 @@
-define(IS_STRING(String),
(is_list(String) orelse is_binary(String))).
+
+-define(IS_FORMAT(Format),
+ (?IS_STRING(Format) orelse is_atom(Format))).
diff --git a/lib/kernel/src/logger_olp.erl b/lib/kernel/src/logger_olp.erl
index 8365383fe2..cf9b480341 100644
--- a/lib/kernel/src/logger_olp.erl
+++ b/lib/kernel/src/logger_olp.erl
@@ -77,7 +77,7 @@ load({_Name,Pid,ModeRef},Msg) ->
%% synchronous instead of asynchronous (slows down the tempo of a
%% process causing much load). If the process is choked, drop mode
%% is set and no message is sent.
- try ?get_mode(ModeRef) of
+ case get_mode(ModeRef) of
async ->
gen_server:cast(Pid, {'$olp_load',Msg});
sync ->
@@ -86,17 +86,11 @@ load({_Name,Pid,ModeRef},Msg) ->
ok;
_Other ->
%% dropped or {error,busy}
- ?observe(_Name,{dropped,1}),
- ok
+ ?observe(_Name,{dropped,1})
end;
drop ->
?observe(_Name,{dropped,1})
- catch
- %% if the ETS table doesn't exist (maybe because of a
- %% process restart), we can only drop the event
- _:_ -> ?observe(_Name,{dropped,1})
- end,
- ok.
+ end.
-spec info(Olp) -> map() | {error, busy} when
Olp :: atom() | pid() | olp_ref().
@@ -147,8 +141,8 @@ restart(Fun) ->
catch C:R:S ->
{error,{restart_failed,Fun,C,R,S}}
end,
- ?LOG_INTERNAL(debug,[{logger_olp,restart},
- {result,Result}]),
+ ?LOG_INTERNAL(debug,#{},[{logger_olp,restart},
+ {result,Result}]),
ok.
-spec get_ref() -> olp_ref().
@@ -174,40 +168,32 @@ init([Name,Module,Args,Options]) ->
?start_observation(Name),
- try ets:new(Name, [public]) of
- ModeRef ->
- OlpRef = {Name,self(),ModeRef},
- put(olp_ref,OlpRef),
- try Module:init(Args) of
- {ok,CBState} ->
- ?set_mode(ModeRef, async),
- T0 = ?timestamp(),
- proc_lib:init_ack({ok,self(),OlpRef}),
- %% Storing options in state to avoid copying
- %% (sending) the option data with each message
- State0 = ?merge_with_stats(
- Options#{id => Name,
- idle=> true,
- module => Module,
- mode_ref => ModeRef,
- mode => async,
- last_qlen => 0,
- last_load_ts => T0,
- burst_win_ts => T0,
- burst_msg_count => 0,
- cb_state => CBState}),
- State = reset_restart_flag(State0),
- gen_server:enter_loop(?MODULE, [], State);
- Error ->
- _ = ets:delete(ModeRef),
- unregister(Name),
- proc_lib:init_ack(Error)
- catch
- _:Error ->
- _ = ets:delete(ModeRef),
- unregister(Name),
- proc_lib:init_ack(Error)
- end
+ ModeRef = {?MODULE, Name},
+ OlpRef = {Name,self(),ModeRef},
+ put(olp_ref,OlpRef),
+ try Module:init(Args) of
+ {ok,CBState} ->
+ set_mode(ModeRef, async),
+ T0 = ?timestamp(),
+ proc_lib:init_ack({ok,self(),OlpRef}),
+ %% Storing options in state to avoid copying
+ %% (sending) the option data with each message
+ State0 = ?merge_with_stats(
+ Options#{id => Name,
+ idle=> true,
+ module => Module,
+ mode_ref => ModeRef,
+ mode => async,
+ last_qlen => 0,
+ last_load_ts => T0,
+ burst_win_ts => T0,
+ burst_msg_count => 0,
+ cb_state => CBState}),
+ State = reset_restart_flag(State0),
+ gen_server:enter_loop(?MODULE, [], State);
+ Error ->
+ unregister(Name),
+ proc_lib:init_ack(Error)
catch
_:Error ->
unregister(Name),
@@ -274,11 +260,11 @@ handle_cast(Msg, #{module:=Module, cb_state:=CBState} = State) ->
{stop, Reason, State#{cb_state=>CBState1}}
end.
-handle_info(timeout, #{mode_ref:=_ModeRef, mode:=Mode} = State) ->
+handle_info(timeout, #{mode_ref:=ModeRef} = State) ->
State1 = notify(idle,State),
State2 = maybe_notify_mode_change(async,State1),
{noreply, State2#{idle => true,
- mode => ?change_mode(_ModeRef, Mode, async),
+ mode => set_mode(ModeRef, async),
burst_msg_count => 0}};
handle_info(Msg, #{module := Module, cb_state := CBState} = State) ->
case try_callback_call(Module,handle_info,[Msg, CBState]) of
@@ -357,7 +343,7 @@ do_load(Msg, CallOrCast, State) ->
%% this function is called by do_load/3 after an overload check
%% has been performed, where QLen > FlushQLen
-flush(T1, State=#{id := _Name, mode := Mode, last_load_ts := _T0, mode_ref := ModeRef}) ->
+flush(T1, State=#{id := _Name, last_load_ts := _T0, mode_ref := ModeRef}) ->
%% flush load messages in the mailbox (a limited number in order
%% to not cause long delays)
NewFlushed = flush_load(?FLUSH_MAX_N),
@@ -378,7 +364,7 @@ flush(T1, State=#{id := _Name, mode := Mode, last_load_ts := _T0, mode_ref := Mo
State3 = ?update_max_qlen(QLen1,State2),
State4 = maybe_notify_mode_change(async,State3),
{dropped,?update_other(flushed,FLUSHED,NewFlushed,
- State4#{mode => ?change_mode(ModeRef,Mode,async),
+ State4#{mode => set_mode(ModeRef,async),
last_qlen => QLen1,
last_load_ts => T1})}.
@@ -507,11 +493,11 @@ check_load(State = #{id:=_Name, mode_ref := ModeRef, mode := Mode,
%% be dropped on the client side (never sent to
%% the olp process).
IncDrops = if Mode == drop -> 0; true -> 1 end,
- {?change_mode(ModeRef, Mode, drop), IncDrops,0};
+ {set_mode(ModeRef, drop), IncDrops,0};
QLen >= SyncModeQLen ->
- {?change_mode(ModeRef, Mode, sync), 0,0};
+ {set_mode(ModeRef, sync), 0,0};
true ->
- {?change_mode(ModeRef, Mode, async), 0,0}
+ {set_mode(ModeRef, async), 0,0}
end,
State1 = ?update_other(drops,DROPS,_NewDrops,State),
State2 = ?update_max_qlen(QLen,State1),
@@ -576,7 +562,7 @@ flush_load(N, Limit) ->
{log,_,_,_,_} ->
flush_load(N+1, Limit);
{log,_,_,_} ->
- flush_load(N+1, Limit)
+ flush_load(N+1, Limit)
after
0 -> N
end.
@@ -587,6 +573,11 @@ overload_levels_ok(Options) ->
FQL = maps:get(flush_qlen, Options, ?FLUSH_QLEN),
(DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL).
+get_mode(Ref) -> persistent_term:get(Ref, async).
+
+set_mode(Ref, M) ->
+ true = is_atom(M), persistent_term:put(Ref, M), M.
+
maybe_notify_mode_change(drop,#{mode:=Mode0}=State)
when Mode0=/=drop ->
notify({mode_change,Mode0,drop},State);
diff --git a/lib/kernel/src/logger_olp.hrl b/lib/kernel/src/logger_olp.hrl
index d68b5c048d..5ee3f4dd1c 100644
--- a/lib/kernel/src/logger_olp.hrl
+++ b/lib/kernel/src/logger_olp.hrl
@@ -72,25 +72,8 @@
-define(timestamp(), erlang:monotonic_time(microsecond)).
--define(get_mode(Tid),
- case ets:lookup(Tid, mode) of
- [{mode,M}] -> M;
- _ -> async
- end).
-
--define(set_mode(Tid, M),
- begin ets:insert(Tid, {mode,M}), M end).
-
--define(change_mode(Tid, M0, M1),
- if M0 == M1 ->
- M0;
- true ->
- ets:insert(Tid, {mode,M1}),
- M1
- end).
-
-define(max(X1, X2),
- if
+ if
X2 == undefined -> X1;
X2 > X1 -> X2;
true -> X1
diff --git a/lib/kernel/src/logger_proxy.erl b/lib/kernel/src/logger_proxy.erl
index 6ab8e3e4c5..ead2d74f37 100644
--- a/lib/kernel/src/logger_proxy.erl
+++ b/lib/kernel/src/logger_proxy.erl
@@ -151,13 +151,13 @@ notify({mode_change,Mode0,Mode1},State) ->
true ->
ok
end,
- ?LOG_INTERNAL(notice,"~w switched from ~w to ~w mode",[?MODULE,Mode0,Mode1]),
+ ?LOG_INTERNAL(notice,#{},"~w switched from ~w to ~w mode",[?MODULE,Mode0,Mode1]),
State;
notify({flushed,Flushed},State) ->
- ?LOG_INTERNAL(notice, "~w flushed ~w log events",[?MODULE,Flushed]),
+ ?LOG_INTERNAL(notice,#{},"~w flushed ~w log events",[?MODULE,Flushed]),
State;
notify(restart,State) ->
- ?LOG_INTERNAL(notice, "~w restarted", [?MODULE]),
+ ?LOG_INTERNAL(notice,#{},"~w restarted", [?MODULE]),
State;
notify(_Note,State) ->
State.
@@ -167,7 +167,7 @@ notify(_Note,State) ->
try_log(Args) ->
try apply(logger,log,Args)
catch C:R:S ->
- ?LOG_INTERNAL(debug,[{?MODULE,log_failed},
- {log,Args},
- {reason,{C,R,S}}])
+ ?LOG_INTERNAL(debug,#{},[{?MODULE,log_failed},
+ {log,Args},
+ {reason,{C,R,S}}])
end.
diff --git a/lib/kernel/src/logger_server.erl b/lib/kernel/src/logger_server.erl
index 722246e82c..fbf3b48d9b 100644
--- a/lib/kernel/src/logger_server.erl
+++ b/lib/kernel/src/logger_server.erl
@@ -25,13 +25,13 @@
-export([start_link/0, add_handler/3, remove_handler/1,
add_filter/2, remove_filter/2,
set_module_level/2, unset_module_level/0,
- unset_module_level/1, cache_module_level/1,
+ unset_module_level/1,
set_config/2, set_config/3,
update_config/2, update_config/3,
update_formatter_config/2]).
%% Helper
--export([diff_maps/2]).
+-export([diff_maps/2,do_internal_log/4]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -104,9 +104,6 @@ unset_module_level(Modules) when is_list(Modules) ->
unset_module_level(Modules) ->
{error,{not_a_list_of_modules,Modules}}.
-cache_module_level(Module) ->
- gen_server:cast(?SERVER,{cache_module_level,Module}).
-
set_config(Owner,Key,Value) ->
case sanity_check(Owner,Key,Value) of
ok ->
@@ -325,18 +322,15 @@ handle_call({update_formatter_config,HandlerId,NewFConfig},_From,
{error,{not_found,HandlerId}}
end,
{reply,Reply,State};
-handle_call({set_module_level,Modules,Level}, _From, #state{tid=Tid}=State) ->
- Reply = logger_config:set_module_level(Tid,Modules,Level),
+handle_call({set_module_level,Modules,Level}, _From, State) ->
+ Reply = logger_config:set_module_level(Modules,Level),
{reply,Reply,State};
-handle_call({unset_module_level,Modules}, _From, #state{tid=Tid}=State) ->
- Reply = logger_config:unset_module_level(Tid,Modules),
+handle_call({unset_module_level,Modules}, _From, State) ->
+ Reply = logger_config:unset_module_level(Modules),
{reply,Reply,State}.
handle_cast({async_req_reply,_Ref,_Reply} = Reply,State) ->
- call_h_reply(Reply,State);
-handle_cast({cache_module_level,Module}, #state{tid=Tid}=State) ->
- logger_config:cache_module_level(Tid,Module),
- {noreply, State}.
+ call_h_reply(Reply,State).
%% Interface for those who can't call the API - e.g. the emulator, or
%% places related to code loading.
@@ -359,12 +353,14 @@ handle_info(Unexpected,State) when element(1,Unexpected) == 'EXIT' ->
%% The simple handler will send an 'EXIT' message when it is replaced
%% We may as well ignore all 'EXIT' messages that we get
?LOG_INTERNAL(debug,
+ #{},
[{logger,got_unexpected_message},
{process,?SERVER},
{message,Unexpected}]),
{noreply,State};
handle_info(Unexpected,State) ->
?LOG_INTERNAL(info,
+ #{},
[{logger,got_unexpected_message},
{process,?SERVER},
{message,Unexpected}]),
@@ -550,6 +546,7 @@ call_h(Module, Function, Args, DefRet) ->
_ ->
ST = logger:filter_stacktrace(?MODULE,S),
?LOG_INTERNAL(error,
+ #{},
[{logger,callback_crashed},
{process,?SERVER},
{reason,{C,R,ST}}]),
@@ -592,6 +589,7 @@ call_h_reply({'DOWN',Ref,_Proc,Pid,Reason}, #state{ async_req = {Ref,_PostFun,_F
%% to the spawned process. It is only here to make sure that the logger_server does
%% not deadlock if that happens.
?LOG_INTERNAL(error,
+ #{},
[{logger,process_exited},
{process,Pid},
{reason,Reason}]),
@@ -600,6 +598,7 @@ call_h_reply({'DOWN',Ref,_Proc,Pid,Reason}, #state{ async_req = {Ref,_PostFun,_F
State);
call_h_reply(Unexpected,State) ->
?LOG_INTERNAL(info,
+ #{},
[{logger,got_unexpected_message},
{process,?SERVER},
{message,Unexpected}]),
@@ -615,3 +614,18 @@ diffs([{K,V1}|T1],[{K,V2}|T2],D1,D2) ->
diffs(T1,T2,D1#{K=>V1},D2#{K=>V2});
diffs([],[],D1,D2) ->
{D1,D2}.
+
+do_internal_log(Level,Location,Log,[Report] = Data) ->
+ do_internal_log(Level,Location,Log,Data,{report,Report});
+do_internal_log(Level,Location,Log,[Fmt,Args] = Data) ->
+ do_internal_log(Level,Location,Log,Data,{Fmt,Args}).
+do_internal_log(Level,Location,Log,Data,Msg) ->
+ Meta = logger:add_default_metadata(maps:merge(Location,maps:get(meta,Log,#{}))),
+ %% Spawn these to avoid deadlocks
+ case Log of
+ #{ meta := #{ internal_log_event := true } } ->
+ _ = spawn(logger_simple_h,log,[#{level=>Level,msg=>Msg,meta=>Meta},#{}]);
+ _ ->
+ _ = spawn(logger,macro_log,[Location,Level|Data]++
+ [Meta#{internal_log_event=>true}])
+ end.
diff --git a/lib/kernel/src/logger_simple_h.erl b/lib/kernel/src/logger_simple_h.erl
index a0d51dba25..72dfe8a899 100644
--- a/lib/kernel/src/logger_simple_h.erl
+++ b/lib/kernel/src/logger_simple_h.erl
@@ -61,15 +61,20 @@ log(#{meta:=#{error_logger:=#{tag:=info_report,type:=Type}}},_Config)
%% Skip info reports that are not 'std_info' (ref simple logger in
%% error_logger)
ok;
-log(#{msg:=_,meta:=#{time:=_}}=Log,_Config) ->
+log(#{msg:=_,meta:=#{time:=_}=M}=Log,_Config) ->
_ = case whereis(?MODULE) of
undefined ->
%% Is the node on the way down? Real emergency?
%% Log directly from client just to get it out
- do_log(
- #{level=>error,
- msg=>{report,{error,simple_handler_process_dead}},
- meta=>#{time=>logger:timestamp()}}),
+ case maps:get(internal_log_event, M, false) of
+ false ->
+ do_log(
+ #{level=>error,
+ msg=>{report,{error,simple_handler_process_dead}},
+ meta=>#{time=>logger:timestamp()}});
+ true ->
+ ok
+ end,
do_log(Log);
_ ->
?MODULE ! {log,Log}
diff --git a/lib/kernel/src/logger_std_h.erl b/lib/kernel/src/logger_std_h.erl
index 6641d99776..dbbd6fbc1f 100644
--- a/lib/kernel/src/logger_std_h.erl
+++ b/lib/kernel/src/logger_std_h.erl
@@ -330,13 +330,22 @@ open_log_file(HandlerName,#{type:=file,
end.
close_log_file(#{fd:=Fd}) ->
- _ = file:datasync(Fd),
+ _ = file:datasync(Fd), %% file:datasync may return error as it will flush the delayed_write buffer
_ = file:close(Fd),
ok;
close_log_file(_) ->
ok.
-
+%% A special close that closes the FD properly when the delayed write close failed
+delayed_write_close(#{fd:=Fd}) ->
+ case file:close(Fd) of
+ %% We got an error while closing, could be a delayed write failing
+ %% So we close again in order to make sure the file is closed.
+ {error, _} ->
+ file:close(Fd);
+ Res ->
+ Res
+ end.
%%%-----------------------------------------------------------------
%%% File control process
@@ -556,10 +565,9 @@ maybe_rotate_file(AddSize,#{rotation:=#{size:=RotSize,
maybe_rotate_file(_Bin,State) ->
State.
-rotate_file(#{fd:=Fd0,file_name:=FileName,modes:=Modes,rotation:=Rotation}=State) ->
+rotate_file(#{file_name:=FileName,modes:=Modes,rotation:=Rotation}=State) ->
State1 = sync_dev(State),
- _ = file:close(Fd0),
- _ = file:close(Fd0),
+ _ = delayed_write_close(State),
rotate_files(FileName,maps:get(count,Rotation),maps:get(compress,Rotation)),
case file:open(FileName,Modes) of
{ok,Fd} ->
diff --git a/lib/kernel/src/net.erl b/lib/kernel/src/net.erl
index fcc78d6971..aceb903bb6 100644
--- a/lib/kernel/src/net.erl
+++ b/lib/kernel/src/net.erl
@@ -69,12 +69,12 @@
]).
--deprecated({call, 4, eventually}).
--deprecated({cast, 4, eventually}).
--deprecated({broadcast, 3, eventually}).
--deprecated({ping, 1, eventually}).
--deprecated({relay, 1, eventually}).
--deprecated({sleep, 1, eventually}).
+-deprecated({call, 4, "use rpc:call/4 instead"}).
+-deprecated({cast, 4, "use rpc:cast/4 instead"}).
+-deprecated({broadcast, 3, "use rpc:eval_everywhere/3 instead"}).
+-deprecated({ping, 1, "use net_adm:ping/1 instead"}).
+-deprecated({relay, 1, "use slave:relay/1 instead"}).
+-deprecated({sleep, 1, "use 'receive after T -> ok end' instead"}).
-type ifaddrs_flag() :: up | broadcast | debug | loopback | pointopoint |
diff --git a/lib/kernel/src/pg.erl b/lib/kernel/src/pg.erl
new file mode 100644
index 0000000000..0668dd1f79
--- /dev/null
+++ b/lib/kernel/src/pg.erl
@@ -0,0 +1,507 @@
+%%
+%%
+%% Copyright WhatsApp Inc. and its affiliates. 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.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%%
+%%-------------------------------------------------------------------
+%%
+%% @author Maxim Fedorov <maximfca@gmail.com>
+%% Process Groups with eventually consistent membership.
+%%
+%% Differences (compared to pg2):
+%% * non-existent and empty group treated the same (empty list of pids),
+%% thus create/1 and delete/1 have no effect (and not implemented).
+%% which_groups() return only non-empty groups
+%% * no cluster lock required, and no dependency on global
+%% * all join/leave operations require local process (it's not possible to join
+%% a process from a different node)
+%% * multi-join: join/leave several processes with a single call
+%%
+%% Why empty groups are not supported:
+%% Unlike a process, group does not have originating node. So it's possible
+%% that during net split one node deletes the group, that still exists for
+%% another partition. pg2 will recover the group, as soon as net
+%% split converges, which is quite unexpected.
+%%
+%% Exchange protocol:
+%% * when pg process starts, it broadcasts
+%% 'discover' message to all nodes in the cluster
+%% * when pg server receives 'discover', it responds with 'sync' message
+%% containing list of groups with all local processes, and starts to
+%% monitor process that sent 'discover' message (assuming it is a part
+%% of an overlay network)
+%% * every pg process monitors 'nodeup' messages to attempt discovery for
+%% nodes that are (re)joining the cluster
+%%
+%% Leave/join operations:
+%% * processes joining the group are monitored on the local node
+%% * when process exits (without leaving groups prior to exit), local
+%% instance of pg scoped process detects this and sends 'leave' to
+%% all nodes in an overlay network (no remote monitoring done)
+%% * all leave/join operations are serialised through pg server process
+%%
+-module(pg).
+
+%% API: default scope
+-export([
+ start_link/0,
+
+ join/2,
+ leave/2,
+ get_members/1,
+ get_local_members/1,
+ which_groups/0,
+ which_local_groups/0
+]).
+
+%% Scoped API: overlay networks
+-export([
+ start/1,
+ start_link/1,
+
+ join/3,
+ leave/3,
+ get_members/2,
+ get_local_members/2,
+ which_groups/1,
+ which_local_groups/1
+]).
+
+%% gen_server callbacks
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2
+]).
+
+%% Types
+-type group() :: any().
+
+%% Default scope started by kernel app
+-define(DEFAULT_SCOPE, ?MODULE).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server and links it to calling process.
+%% Uses default scope, which is the same as as the module name.
+-spec start_link() -> {ok, pid()} | {error, any()}.
+start_link() ->
+ start_link(?DEFAULT_SCOPE).
+
+%% @doc
+%% Starts the server outside of supervision hierarchy.
+-spec start(Scope :: atom()) -> {ok, pid()} | {error, any()}.
+start(Scope) when is_atom(Scope) ->
+ gen_server:start({local, Scope}, ?MODULE, [Scope], []).
+
+%% @doc
+%% Starts the server and links it to calling process.
+%% Scope name is supplied.
+-spec start_link(Scope :: atom()) -> {ok, pid()} | {error, any()}.
+start_link(Scope) when is_atom(Scope) ->
+ gen_server:start_link({local, Scope}, ?MODULE, [Scope], []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Joins a single process
+%% Group is created automatically.
+%% Process must be local to this node.
+-spec join(Group :: group(), PidOrPids :: pid() | [pid()]) -> ok.
+join(Group, PidOrPids) ->
+ join(?DEFAULT_SCOPE, Group, PidOrPids).
+
+-spec join(Scope :: atom(), Group :: group(), PidOrPids :: pid() | [pid()]) -> ok.
+join(Scope, Group, PidOrPids) ->
+ Node = node(),
+ is_list(PidOrPids) andalso [error({nolocal, Pid}) || Pid <- PidOrPids, node(Pid) =/= Node orelse not is_pid(Pid)],
+ gen_server:call(Scope, {join_local, Group, PidOrPids}, infinity).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Single process leaving the group.
+%% Process must be local to this node.
+-spec leave(Group :: group(), PidOrPids :: pid() | [pid()]) -> ok.
+leave(Group, PidOrPids) ->
+ leave(?DEFAULT_SCOPE, Group, PidOrPids).
+
+-spec leave(Scope :: atom(), Group :: group(), Pid :: pid() | [pid()]) -> ok | not_joined.
+leave(Scope, Group, PidOrPids) ->
+ Node = node(),
+ is_list(PidOrPids) andalso [error({nolocal, Pid}) || Pid <- PidOrPids, node(Pid) =/= Node orelse not is_pid(Pid)],
+ gen_server:call(Scope, {leave_local, Group, PidOrPids}, infinity).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns all processes in a group
+-spec get_members(Group :: group()) -> [pid()].
+get_members(Group) ->
+ get_members(?DEFAULT_SCOPE, Group).
+
+-spec get_members(Scope :: atom(), Group :: group()) -> [pid()].
+get_members(Scope, Group) ->
+ try
+ ets:lookup_element(Scope, Group, 2)
+ catch
+ error:badarg ->
+ []
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns processes in a group, running on local node.
+-spec get_local_members(Group :: group()) -> [pid()].
+get_local_members(Group) ->
+ get_local_members(?DEFAULT_SCOPE, Group).
+
+-spec get_local_members(Scope :: atom(), Group :: group()) -> [pid()].
+get_local_members(Scope, Group) ->
+ try
+ ets:lookup_element(Scope, Group, 3)
+ catch
+ error:badarg ->
+ []
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns a list of all known groups.
+-spec which_groups() -> [Group :: group()].
+which_groups() ->
+ which_groups(?DEFAULT_SCOPE).
+
+-spec which_groups(Scope :: atom()) -> [Group :: group()].
+which_groups(Scope) when is_atom(Scope) ->
+ [G || [G] <- ets:match(Scope, {'$1', '_', '_'})].
+
+%%--------------------------------------------------------------------
+%% @private
+%% Returns a list of groups that have any local processes joined.
+-spec which_local_groups() -> [Group :: group()].
+which_local_groups() ->
+ which_local_groups(?DEFAULT_SCOPE).
+
+-spec which_local_groups(Scope :: atom()) -> [Group :: group()].
+which_local_groups(Scope) when is_atom(Scope) ->
+ ets:select(Scope, [{{'$1', '_', '$2'}, [{'=/=', '$2', []}], ['$1']}]).
+
+%%--------------------------------------------------------------------
+%% Internal implementation
+
+%% gen_server implementation
+-record(state, {
+ %% ETS table name, and also the registered process name (self())
+ scope :: atom(),
+ %% monitored local processes and groups they joined
+ monitors = #{} :: #{pid() => {MRef :: reference(), Groups :: [group()]}},
+ %% remote node: scope process monitor and map of groups to pids for fast sync routine
+ nodes = #{} :: #{pid() => {reference(), #{group() => [pid()]}}}
+}).
+
+-type state() :: #state{}.
+
+-spec init([Scope :: atom()]) -> {ok, state()}.
+init([Scope]) ->
+ ok = net_kernel:monitor_nodes(true),
+ %% discover all nodes in the cluster
+ broadcast([{Scope, Node} || Node <- nodes()], {discover, self()}),
+ Scope = ets:new(Scope, [set, protected, named_table, {read_concurrency, true}]),
+ {ok, #state{scope = Scope}}.
+
+-spec handle_call(Call :: {join_local, Group :: group(), Pid :: pid()}
+ | {leave_local, Group :: group(), Pid :: pid()},
+ From :: {pid(),Tag :: any()},
+ State :: state()) -> {reply, ok | not_joined, state()}.
+
+handle_call({join_local, Group, PidOrPids}, _From, #state{scope = Scope, monitors = Monitors, nodes = Nodes} = State) ->
+ NewMons = join_monitors(PidOrPids, Group, Monitors),
+ join_local_group(Scope, Group, PidOrPids),
+ broadcast(maps:keys(Nodes), {join, self(), Group, PidOrPids}),
+ {reply, ok, State#state{monitors = NewMons}};
+
+handle_call({leave_local, Group, PidOrPids}, _From, #state{scope = Scope, monitors = Monitors, nodes = Nodes} = State) ->
+ case leave_monitors(PidOrPids, Group, Monitors) of
+ Monitors ->
+ {reply, not_joined, State};
+ NewMons ->
+ leave_local_group(Scope, Group, PidOrPids),
+ broadcast(maps:keys(Nodes), {leave, self(), PidOrPids, [Group]}),
+ {reply, ok, State#state{monitors = NewMons}}
+ end;
+
+handle_call(_Request, _From, _S) ->
+ error(badarg).
+
+-spec handle_cast(
+ {sync, Peer :: pid(), Groups :: [{group(), [pid()]}]},
+ State :: state()) -> {noreply, state()}.
+
+handle_cast({sync, Peer, Groups}, #state{scope = Scope, nodes = Nodes} = State) ->
+ {noreply, State#state{nodes = handle_sync(Scope, Peer, Nodes, Groups)}};
+
+handle_cast(_, _State) ->
+ error(badarg).
+
+-spec handle_info(
+ {discover, Peer :: pid()} |
+ {join, Peer :: pid(), group(), pid() | [pid()]} |
+ {leave, Peer :: pid(), pid() | [pid()], [group()]} |
+ {'DOWN', reference(), process, pid(), term()} |
+ {nodedown, node()} | {nodeup, node()}, State :: state()) -> {noreply, state()}.
+
+%% remote pid or several pids joining the group
+handle_info({join, Peer, Group, PidOrPids}, #state{scope = Scope, nodes = Nodes} = State) ->
+ join_remote(Scope, Group, PidOrPids),
+ % store remote group => pids map for fast sync operation
+ {MRef, RemoteGroups} = maps:get(Peer, Nodes),
+ NewRemoteGroups = join_remote_map(Group, PidOrPids, RemoteGroups),
+ {noreply, State#state{nodes = Nodes#{Peer => {MRef, NewRemoteGroups}}}};
+
+%% remote pid leaving (multiple groups at once)
+handle_info({leave, Peer, PidOrPids, Groups}, #state{scope = Scope, nodes = Nodes} = State) ->
+ _ = leave_remote(Scope, PidOrPids, Groups),
+ {MRef, RemoteMap} = maps:get(Peer, Nodes),
+ NewRemoteMap = lists:foldl(
+ fun (Group, Acc) ->
+ case maps:get(Group, Acc) of
+ PidOrPids ->
+ Acc;
+ [PidOrPids] ->
+ Acc;
+ Existing when is_pid(PidOrPids) ->
+ Acc#{Group => lists:delete(PidOrPids, Existing)};
+ Existing ->
+ Acc#{Group => Existing-- PidOrPids}
+ end
+ end, RemoteMap, Groups),
+ {noreply, State#state{nodes = Nodes#{Peer => {MRef, NewRemoteMap}}}};
+
+%% we're being discovered, let's exchange!
+handle_info({discover, Peer}, #state{scope = Scope, nodes = Nodes} = State) ->
+ gen_server:cast(Peer, {sync, self(), all_local_pids(Scope)}),
+ %% do we know who is looking for us?
+ case maps:is_key(Peer, Nodes) of
+ true ->
+ {noreply, State};
+ false ->
+ MRef = monitor(process, Peer),
+ erlang:send(Peer, {discover, self()}, [noconnect]),
+ {noreply, State#state{nodes = Nodes#{Peer => {MRef, #{}}}}}
+ end;
+
+%% handle local process exit
+handle_info({'DOWN', MRef, process, Pid, _Info}, #state{scope = Scope, monitors = Monitors, nodes = Nodes} = State) when node(Pid) =:= node() ->
+ case maps:take(Pid, Monitors) of
+ error ->
+ %% this can only happen when leave request and 'DOWN' are in pg queue
+ {noreply, State};
+ {{MRef, Groups}, NewMons} ->
+ [leave_local_group(Scope, Group, Pid) || Group <- Groups],
+ %% send update to all nodes
+ broadcast(maps:keys(Nodes), {leave, self(), Pid, Groups}),
+ {noreply, State#state{monitors = NewMons}}
+ end;
+
+%% handle remote node down or leaving overlay network
+handle_info({'DOWN', MRef, process, Pid, _Info}, #state{scope = Scope, nodes = Nodes} = State) ->
+ {{MRef, RemoteMap}, NewNodes} = maps:take(Pid, Nodes),
+ _ = maps:map(fun (Group, Pids) -> leave_remote(Scope, Pids, [Group]) end, RemoteMap),
+ {noreply, State#state{nodes = NewNodes}};
+
+%% nodedown: ignore, and wait for 'DOWN' signal for monitored process
+handle_info({nodedown, _Node}, State) ->
+ {noreply, State};
+
+%% nodeup: discover if remote node participates in the overlay network
+handle_info({nodeup, Node}, #state{scope = Scope} = State) ->
+ {Scope, Node} ! {discover, self()},
+ {noreply, State};
+
+handle_info(_Info, _State) ->
+ error(badarg).
+
+-spec terminate(Reason :: any(), State :: state()) -> true.
+terminate(_Reason, #state{scope = Scope}) ->
+ true = ets:delete(Scope).
+
+%%--------------------------------------------------------------------
+%% Internal implementation
+
+%% Override all knowledge of the remote node with information it sends
+%% to local node. Current implementation must do the full table scan
+%% to remove stale pids (just as for 'nodedown').
+handle_sync(Scope, Peer, Nodes, Groups) ->
+ %% can't use maps:get() because it evaluates 'default' value first,
+ %% and in this case monitor() call has side effect.
+ {MRef, RemoteGroups} =
+ case maps:find(Peer, Nodes) of
+ error ->
+ {monitor(process, Peer), #{}};
+ {ok, MRef0} ->
+ MRef0
+ end,
+ %% sync RemoteMap and transform ETS table
+ _ = sync_groups(Scope, RemoteGroups, Groups),
+ Nodes#{Peer => {MRef, maps:from_list(Groups)}}.
+
+sync_groups(Scope, RemoteGroups, []) ->
+ %% leave all missing groups
+ [leave_remote(Scope, Pids, [Group]) || {Group, Pids} <- maps:to_list(RemoteGroups)];
+sync_groups(Scope, RemoteGroups, [{Group, Pids} | Tail]) ->
+ case maps:take(Group, RemoteGroups) of
+ {Pids, NewRemoteGroups} ->
+ sync_groups(Scope, NewRemoteGroups, Tail);
+ {OldPids, NewRemoteGroups} ->
+ [{Group, AllOldPids, LocalPids}] = ets:lookup(Scope, Group),
+ %% should be really rare...
+ AllNewPids = Pids ++ AllOldPids -- OldPids,
+ true = ets:insert(Scope, {Group, AllNewPids, LocalPids}),
+ sync_groups(Scope, NewRemoteGroups, Tail);
+ error ->
+ join_remote(Scope, Group, Pids),
+ sync_groups(Scope, RemoteGroups, Tail)
+ end.
+
+join_monitors(Pid, Group, Monitors) when is_pid(Pid) ->
+ case maps:find(Pid, Monitors) of
+ {ok, {MRef, Groups}} ->
+ maps:put(Pid, {MRef, [Group | Groups]}, Monitors);
+ error ->
+ MRef = erlang:monitor(process, Pid),
+ Monitors#{Pid => {MRef, [Group]}}
+ end;
+join_monitors([], _Group, Monitors) ->
+ Monitors;
+join_monitors([Pid | Tail], Group, Monitors) ->
+ join_monitors(Tail, Group, join_monitors(Pid, Group, Monitors)).
+
+join_local_group(Scope, Group, Pid) when is_pid(Pid) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, [Pid | All], [Pid | Local]});
+ [] ->
+ ets:insert(Scope, {Group, [Pid], [Pid]})
+ end;
+join_local_group(Scope, Group, Pids) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, Pids ++ All, Pids ++ Local});
+ [] ->
+ ets:insert(Scope, {Group, Pids, Pids})
+ end.
+
+join_remote(Scope, Group, Pid) when is_pid(Pid) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, [Pid | All], Local});
+ [] ->
+ ets:insert(Scope, {Group, [Pid], []})
+ end;
+join_remote(Scope, Group, Pids) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, Pids ++ All, Local});
+ [] ->
+ ets:insert(Scope, {Group, Pids, []})
+ end.
+
+join_remote_map(Group, Pid, RemoteGroups) when is_pid(Pid) ->
+ maps:update_with(Group, fun (List) -> [Pid | List] end, [Pid], RemoteGroups);
+join_remote_map(Group, Pids, RemoteGroups) ->
+ maps:update_with(Group, fun (List) -> Pids ++ List end, Pids, RemoteGroups).
+
+leave_monitors(Pid, Group, Monitors) when is_pid(Pid) ->
+ case maps:find(Pid, Monitors) of
+ {ok, {MRef, [Group]}} ->
+ erlang:demonitor(MRef),
+ maps:remove(Pid, Monitors);
+ {ok, {MRef, Groups}} ->
+ case lists:member(Group, Groups) of
+ true ->
+ maps:put(Pid, {MRef, lists:delete(Group, Groups)}, Monitors);
+ false ->
+ Monitors
+ end;
+ _ ->
+ Monitors
+ end;
+leave_monitors([], _Group, Monitors) ->
+ Monitors;
+leave_monitors([Pid | Tail], Group, Monitors) ->
+ leave_monitors(Tail, Group, leave_monitors(Pid, Group, Monitors)).
+
+leave_local_group(Scope, Group, Pid) when is_pid(Pid) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, [Pid], [Pid]}] ->
+ ets:delete(Scope, Group);
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, lists:delete(Pid, All), lists:delete(Pid, Local)});
+ [] ->
+ %% rare race condition when 'DOWN' from monitor stays in msg queue while process is leave-ing.
+ true
+ end;
+leave_local_group(Scope, Group, Pids) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ case All -- Pids of
+ [] ->
+ ets:delete(Scope, Group);
+ NewAll ->
+ ets:insert(Scope, {Group, NewAll, Local -- Pids})
+ end;
+ [] ->
+ true
+ end.
+
+leave_remote(Scope, Pid, Groups) when is_pid(Pid) ->
+ _ = [
+ case ets:lookup(Scope, Group) of
+ [{Group, [Pid], []}] ->
+ ets:delete(Scope, Group);
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, lists:delete(Pid, All), Local});
+ [] ->
+ true
+ end ||
+ Group <- Groups];
+leave_remote(Scope, Pids, Groups) ->
+ _ = [
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ case All -- Pids of
+ [] when Local =:= [] ->
+ ets:delete(Scope, Group);
+ NewAll ->
+ ets:insert(Scope, {Group, NewAll, Local})
+ end;
+ [] ->
+ true
+ end ||
+ Group <- Groups].
+
+all_local_pids(Scope) ->
+ %% selector: ets:fun2ms(fun({N,_,L}) when L =/=[] -> {N,L}end).
+ ets:select(Scope, [{{'$1','_','$2'},[{'=/=','$2',[]}],[{{'$1','$2'}}]}]).
+
+%% Works as gen_server:abcast(), but accepts a list of processes
+%% instead of nodes list.
+broadcast([], _Msg) ->
+ ok;
+broadcast([Dest | Tail], Msg) ->
+ %% do not use 'nosuspend', as it will lead to missing
+ %% join/leave messages when dist buffer is full
+ erlang:send(Dest, Msg, [noconnect]),
+ broadcast(Tail, Msg).
diff --git a/lib/kernel/src/pg2.erl b/lib/kernel/src/pg2.erl
index c4732f37ee..d02bbeccdf 100644
--- a/lib/kernel/src/pg2.erl
+++ b/lib/kernel/src/pg2.erl
@@ -25,6 +25,10 @@
-export([start/0,start_link/0,init/1,handle_call/3,handle_cast/2,handle_info/2,
terminate/2]).
+-deprecated([{'_','_',
+ "the 'pg2' module is deprecated and scheduled for removal "
+ "in OTP 24; use 'pg' instead."}]).
+
%%% As of R13B03 monitors are used instead of links.
%%%
diff --git a/lib/kernel/src/raw_file_io_compressed.erl b/lib/kernel/src/raw_file_io_compressed.erl
index f6ac6eaffc..1a6de3a4eb 100644
--- a/lib/kernel/src/raw_file_io_compressed.erl
+++ b/lib/kernel/src/raw_file_io_compressed.erl
@@ -21,7 +21,8 @@
-export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,
position/2, write/2, pwrite/2, pwrite/3,
- read_line/1, read/2, pread/2, pread/3]).
+ read_line/1, read/2, pread/2, pread/3,
+ read_handle_info/2]).
%% OTP internal.
-export([ipread_s32bu_p32bu/3, sendfile/8]).
@@ -118,6 +119,9 @@ ipread_s32bu_p32bu(Fd, Offset, MaxSize) ->
sendfile(_,_,_,_,_,_,_,_) ->
{error, enotsup}.
+read_handle_info(Fd, Opts) ->
+ wrap_call(Fd, [Opts]).
+
wrap_call(Fd, Command) ->
{_Owner, Pid} = get_fd_data(Fd),
try gen_statem:call(Pid, Command, infinity) of
diff --git a/lib/kernel/src/raw_file_io_delayed.erl b/lib/kernel/src/raw_file_io_delayed.erl
index 5644717aaa..f9b89270f1 100644
--- a/lib/kernel/src/raw_file_io_delayed.erl
+++ b/lib/kernel/src/raw_file_io_delayed.erl
@@ -23,7 +23,8 @@
-export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,
position/2, write/2, pwrite/2, pwrite/3,
- read_line/1, read/2, pread/2, pread/3]).
+ read_line/1, read/2, pread/2, pread/3,
+ read_handle_info/2]).
%% OTP internal.
-export([ipread_s32bu_p32bu/3, sendfile/8]).
@@ -304,6 +305,9 @@ ipread_s32bu_p32bu(Fd, Offset, MaxSize) ->
sendfile(_,_,_,_,_,_,_,_) ->
{error, enotsup}.
+read_handle_info(Fd, Opts) ->
+ wrap_call(Fd, [Opts]).
+
wrap_call(Fd, Command) ->
#{ pid := Pid } = get_fd_data(Fd),
try gen_statem:call(Pid, Command, infinity) of
diff --git a/lib/kernel/src/raw_file_io_list.erl b/lib/kernel/src/raw_file_io_list.erl
index 2e16e63f0e..e4fe434e13 100644
--- a/lib/kernel/src/raw_file_io_list.erl
+++ b/lib/kernel/src/raw_file_io_list.erl
@@ -21,7 +21,8 @@
-export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,
position/2, write/2, pwrite/2, pwrite/3,
- read_line/1, read/2, pread/2, pread/3]).
+ read_line/1, read/2, pread/2, pread/3,
+ read_handle_info/2]).
%% OTP internal.
-export([ipread_s32bu_p32bu/3, sendfile/8]).
@@ -126,3 +127,7 @@ sendfile(Fd, Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags) ->
Args = [Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags],
PrivateFd = Fd#file_descriptor.data,
?CALL_FD(PrivateFd, sendfile, Args).
+
+read_handle_info(Fd, Opts) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, read_handle_info, [Opts]).
diff --git a/lib/kernel/src/rpc.erl b/lib/kernel/src/rpc.erl
index d197de942f..22893b3468 100644
--- a/lib/kernel/src/rpc.erl
+++ b/lib/kernel/src/rpc.erl
@@ -19,6 +19,8 @@
%%
-module(rpc).
+%% -define(SERVER_SIDE_ERPC_IS_MANDATORY, yes).
+
%% General rpc, broadcast,multicall, promise and parallel evaluator
%% facility
@@ -26,6 +28,7 @@
%% a separate module.
-define(NAME, rex).
+-define(TAB_NAME, rex_nodes_observer).
-behaviour(gen_server).
@@ -61,12 +64,23 @@
-export_type([key/0]).
+%% Removed functions
+
+-removed([{safe_multi_server_call,2,"use rpc:multi_server_call/2 instead"},
+ {safe_multi_server_call,3,"use rpc:multi_server_call/3 instead"}]).
+
%%------------------------------------------------------------------------
-type state() :: map().
%%------------------------------------------------------------------------
+-define(MAX_INT_TIMEOUT, 4294967295).
+-define(TIMEOUT_TYPE, 0..?MAX_INT_TIMEOUT | 'infinity').
+-define(IS_VALID_TMO_INT(TI_), (is_integer(TI_)
+ andalso (0 =< TI_)
+ andalso (TI_ =< ?MAX_INT_TIMEOUT))).
+-define(IS_VALID_TMO(T_), ((T_ == infinity) orelse ?IS_VALID_TMO_INT(T_))).
%% The rex server may receive a huge amount of
%% messages. Make sure that they are stored off heap to
@@ -101,7 +115,7 @@ stop(Rpc) ->
init([]) ->
process_flag(trap_exit, true),
- {ok, maps:new()}.
+ {ok, #{nodes_observer => start_nodes_observer()}}.
-spec handle_call(term(), term(), state()) ->
{'noreply', state()} |
@@ -113,13 +127,23 @@ handle_call({call, Mod, Fun, Args, Gleader}, To, S) ->
handle_call({block_call, Mod, Fun, Args, Gleader}, _To, S) ->
MyGL = group_leader(),
set_group_leader(Gleader),
- Reply =
- case catch apply(Mod,Fun,Args) of
- {'EXIT', _} = Exit ->
- {badrpc, Exit};
- Other ->
- Other
- end,
+ Reply = try
+ {return, Return} = erpc:execute_call(Mod, Fun, Args),
+ Return
+ catch
+ throw:Result ->
+ Result;
+ exit:Reason ->
+ {'EXIT', Reason};
+ error:Reason:Stack ->
+ case erpc:is_arg_error(Reason, Mod, Fun, Args) of
+ true ->
+ {'EXIT', Reason};
+ false ->
+ RpcStack = erpc:trim_stack(Stack, Mod, Fun, Args),
+ {'EXIT', {Reason, RpcStack}}
+ end
+ end,
group_leader(MyGL, self()), % restore
{reply, Reply, S};
handle_call(stop, _To, S) ->
@@ -140,6 +164,8 @@ handle_cast(_, S) ->
-spec handle_info(term(), state()) -> {'noreply', state()}.
+handle_info({'DOWN', M, process, P, _}, #{nodes_observer := {P,M}} = S) ->
+ {noreply, S#{nodes_observer => start_nodes_observer()}};
handle_info({'DOWN', _, process, Caller, normal}, S) ->
{noreply, maps:remove(Caller, S)};
handle_info({'DOWN', _, process, Caller, Reason}, S) ->
@@ -169,6 +195,9 @@ handle_info({From, {send, Name, Msg}}, S) ->
handle_info({From, {call,Mod,Fun,Args,Gleader}}, S) ->
%% Special for hidden C node's, uugh ...
handle_call_call(Mod, Fun, Args, Gleader, {From,?NAME}, S);
+handle_info({From, features_request}, S) ->
+ From ! {features_reply, node(), [erpc]},
+ {noreply, S};
handle_info(_, S) ->
{noreply, S}.
@@ -254,9 +283,72 @@ proxy_user_flush() ->
end,
proxy_user_flush().
+start_nodes_observer() ->
+ Init = fun () ->
+ process_flag(priority, high),
+ process_flag(trap_exit, true),
+ Tab = ets:new(?TAB_NAME,
+ [{read_concurrency, true},
+ protected]),
+ persistent_term:put(?TAB_NAME, Tab),
+ ok = net_kernel:monitor_nodes(true),
+ lists:foreach(fun (N) ->
+ self() ! {nodeup, N}
+ end,
+ [node()|nodes()]),
+ nodes_observer_loop(Tab)
+ end,
+ spawn_monitor(Init).
+
+nodes_observer_loop(Tab) ->
+ receive
+ {nodeup, N} ->
+ {?NAME, N} ! {self(), features_request};
+ {nodedown, N} ->
+ ets:delete(Tab, N);
+ {features_reply, N, FeatureList} ->
+ try
+ SpawnRpc = lists:member(erpc, FeatureList),
+ ets:insert(Tab, {N, SpawnRpc})
+ catch
+ _:_ -> ets:insert(Tab, {N, false})
+ end;
+ _ ->
+ ignore
+ end,
+ nodes_observer_loop(Tab).
+
+-dialyzer([{nowarn_function, node_has_feature/2}, no_match]).
+
+-spec node_has_feature(Node :: atom(), Feature :: term()) -> boolean().
+
+node_has_feature(N, erpc) when N == node() ->
+ true;
+node_has_feature(N, erpc) ->
+ try
+ Tab = persistent_term:get(?TAB_NAME),
+ ets:lookup_element(Tab, N, 2)
+ catch
+ _:_ -> false
+ end;
+node_has_feature(_N, _Feature) ->
+ false.
%% THE rpc client interface
+%% Call
+
+-define(RPCIFY(ERPC_),
+ try ERPC_ of
+ {'EXIT', _} = BadRpc_ ->
+ {badrpc, BadRpc_};
+ Result_ ->
+ Result_
+ catch
+ Class_:Reason_ ->
+ callify_exception(Class_, Reason_)
+ end).
+
-spec call(Node, Module, Function, Args) -> Res | {badrpc, Reason} when
Node :: node(),
Module :: module(),
@@ -265,10 +357,8 @@ proxy_user_flush() ->
Res :: term(),
Reason :: term().
-call(N,M,F,A) when node() =:= N -> %% Optimize local call
- local_call(M, F, A);
call(N,M,F,A) ->
- do_call(N, {call,M,F,A,group_leader()}, infinity).
+ call(N,M,F,A,infinity).
-spec call(Node, Module, Function, Args, Timeout) ->
Res | {badrpc, Reason} when
@@ -278,14 +368,23 @@ call(N,M,F,A) ->
Args :: [term()],
Res :: term(),
Reason :: term(),
- Timeout :: timeout().
+ Timeout :: ?TIMEOUT_TYPE.
-call(N,M,F,A,infinity) when node() =:= N -> %% Optimize local call
- local_call(M,F,A);
call(N,M,F,A,infinity) ->
- do_call(N, {call,M,F,A,group_leader()}, infinity);
-call(N,M,F,A,Timeout) when is_integer(Timeout), Timeout >= 0 ->
- do_call(N, {call,M,F,A,group_leader()}, Timeout).
+ case ?RPCIFY(erpc:call(N, M, F, A, infinity)) of
+ {badrpc, notsup} ->
+ call_fallback(N, M, F, A, infinity, undefined);
+ Res ->
+ Res
+ end;
+call(N,M,F,A,T) when ?IS_VALID_TMO_INT(T) ->
+ Start = erlang:monotonic_time(),
+ case ?RPCIFY(erpc:call(N, M, F, A, T)) of
+ {badrpc, notsup} ->
+ call_fallback(N, M, F, A, T, Start);
+ Res ->
+ Res
+ end.
-spec block_call(Node, Module, Function, Args) -> Res | {badrpc, Reason} when
Node :: node(),
@@ -295,10 +394,8 @@ call(N,M,F,A,Timeout) when is_integer(Timeout), Timeout >= 0 ->
Res :: term(),
Reason :: term().
-block_call(N,M,F,A) when node() =:= N -> %% Optimize local call
- local_call(M,F,A);
block_call(N,M,F,A) ->
- do_call(N, {block_call,M,F,A,group_leader()}, infinity).
+ do_srv_call(N, {block_call,M,F,A,group_leader()}, infinity).
-spec block_call(Node, Module, Function, Args, Timeout) ->
Res | {badrpc, Reason} when
@@ -308,24 +405,58 @@ block_call(N,M,F,A) ->
Args :: [term()],
Res :: term(),
Reason :: term(),
- Timeout :: timeout().
-
-block_call(N,M,F,A,_Timeout) when node() =:= N -> %% Optimize local call
- local_call(M, F, A);
-block_call(N,M,F,A,infinity) ->
- do_call(N, {block_call,M,F,A,group_leader()}, infinity);
-block_call(N,M,F,A,Timeout) when is_integer(Timeout), Timeout >= 0 ->
- do_call(N, {block_call,M,F,A,group_leader()}, Timeout).
-
-local_call(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
- case catch apply(M, F, A) of
- {'EXIT',_}=V -> {badrpc, V};
- Other -> Other
+ Timeout :: ?TIMEOUT_TYPE.
+
+block_call(N,M,F,A,Timeout) when is_atom(N),
+ is_atom(M),
+ is_list(A),
+ ?IS_VALID_TMO(Timeout) ->
+ do_srv_call(N, {block_call,M,F,A,group_leader()}, Timeout).
+
+
+%% call() implementation utilizing erpc:call()...
+
+callify_exception(throw, {'EXIT', _} = BadRpc) ->
+ {badrpc, BadRpc};
+callify_exception(throw, Return) ->
+ Return;
+callify_exception(exit, {exception, Exit}) ->
+ {badrpc, {'EXIT', Exit}};
+callify_exception(exit, {signal, Reason}) ->
+ {badrpc, {'EXIT', Reason}};
+callify_exception(exit, Reason) ->
+ exit(Reason);
+callify_exception(error, {exception, Error, Stack}) ->
+ {badrpc, {'EXIT', {Error, Stack}}};
+callify_exception(error, {erpc, noconnection}) ->
+ {badrpc, nodedown};
+callify_exception(error, {erpc, timeout}) ->
+ {badrpc, timeout};
+callify_exception(error, {erpc, notsup}) ->
+ {badrpc, notsup};
+callify_exception(error, {erpc, Error}) ->
+ {badrpc, {'EXIT', Error}};
+callify_exception(error, Reason) ->
+ error(Reason).
+
+call_result(Type, ReqId, Res, Reason) ->
+ ?RPCIFY(erpc:call_result(Type, ReqId, Res, Reason)).
+
+call_fallback(N, M, F, A, infinity, _S) ->
+ do_srv_call(N, {call,M,F,A,group_leader()}, infinity);
+call_fallback(N, M, F, A, T, S) ->
+ Now = erlang:monotonic_time(),
+ Used = erlang:convert_time_unit(Now-S, native, millisecond),
+ case T - Used of
+ Timeout when Timeout =< 0 ->
+ {badrpc, timeout};
+ Timeout ->
+ do_srv_call(N, {call,M,F,A,group_leader()}, Timeout)
end.
-do_call(Node, Request, infinity) ->
+do_srv_call(Node, Request, infinity) ->
rpc_check(catch gen_server:call({?NAME,Node}, Request, infinity));
-do_call(Node, Request, Timeout) ->
+do_srv_call(Node, Request, Timeout) ->
Tag = make_ref(),
{Receiver,Mref} =
erlang:spawn_monitor(
@@ -346,6 +477,7 @@ do_call(Node, Request, Timeout) ->
end.
rpc_check_t({'EXIT', {timeout,_}}) -> {badrpc, timeout};
+rpc_check_t({'EXIT', {timeout_value,_}}) -> {badrpc, badarg};
rpc_check_t(X) -> rpc_check(X).
rpc_check({'EXIT', {{nodedown,_},_}}) ->
@@ -395,11 +527,14 @@ server_call(Node, Name, ReplyWrapper, Msg)
Function :: atom(),
Args :: [term()].
-cast(Node, Mod, Fun, Args) when Node =:= node() ->
- catch spawn(Mod, Fun, Args),
- true;
cast(Node, Mod, Fun, Args) ->
- gen_server:cast({?NAME,Node}, {cast,Mod,Fun,Args,group_leader()}),
+ _ = case node_has_feature(Node, erpc) of
+ false ->
+ gen_server:cast({?NAME,Node},
+ {cast,Mod,Fun,Args,group_leader()});
+ true ->
+ erpc:cast(Node, Mod, Fun, Args)
+ end,
true.
@@ -464,8 +599,11 @@ eval_everywhere(Mod, Fun, Args) ->
Args :: [term()].
eval_everywhere(Nodes, Mod, Fun, Args) ->
- gen_server:abcast(Nodes, ?NAME, {cast,Mod,Fun,Args,group_leader()}).
-
+ lists:foreach(fun (Node) ->
+ cast(Node, Mod, Fun, Args)
+ end,
+ Nodes),
+ abcast.
send_nodes([Node|Tail], Name, Msg, Monitors) when is_atom(Node) ->
Monitor = start_monitor(Node, Name),
@@ -489,7 +627,6 @@ start_monitor(Node, Name) ->
{Node,erlang:monitor(process, {Name, Node})}
end.
-
%% Call apply(M,F,A) on all nodes in parallel
-spec multicall(Module, Function, Args) -> {ResL, BadNodes} when
Module :: module(),
@@ -512,7 +649,7 @@ multicall(M, F, A) ->
Module :: module(),
Function :: atom(),
Args :: [term()],
- Timeout :: timeout(),
+ Timeout :: ?TIMEOUT_TYPE,
ResL :: [Res :: term() | {'badrpc', Reason :: term()}],
BadNodes :: [node()].
@@ -527,23 +664,187 @@ multicall(M, F, A, Timeout) ->
Module :: module(),
Function :: atom(),
Args :: [term()],
- Timeout :: timeout(),
+ Timeout :: ?TIMEOUT_TYPE,
ResL :: [Res :: term() | {'badrpc', Reason :: term()}],
BadNodes :: [node()].
-multicall(Nodes, M, F, A, infinity)
- when is_list(Nodes), is_atom(M), is_atom(F), is_list(A) ->
- do_multicall(Nodes, M, F, A, infinity);
-multicall(Nodes, M, F, A, Timeout)
- when is_list(Nodes), is_atom(M), is_atom(F), is_list(A), is_integer(Timeout),
- Timeout >= 0 ->
+multicall(Nodes, M, F, A, Timeout) when is_list(Nodes), is_atom(M),
+ is_atom(F), is_list(A),
+ ?IS_VALID_TMO(Timeout) ->
do_multicall(Nodes, M, F, A, Timeout).
+mc_requests(_Res, [], _M, _F, _A, ReqMap) ->
+ ReqMap;
+mc_requests(Res, [N|Ns], M, F, A, ReqMap) ->
+ ReqId = erlang:spawn_request(N, erpc, execute_call,
+ [Res, M, F, A],
+ [{reply_tag, {spawn_reply, Res, N}},
+ monitor]),
+ mc_requests(Res, Ns, M, F, A, ReqMap#{N => {spawn_request, ReqId}}).
+
+mc_spawn_replies(_Res, 0, ReqMap, _MFA, _EndTime) ->
+ ReqMap;
+mc_spawn_replies(Res, Outstanding, ReqMap, MFA, EndTime) ->
+ Timeout = mc_timeout(EndTime),
+ receive
+ {{spawn_reply, Res, _}, _, _, _} = Reply ->
+ NewReqMap = mc_handle_spawn_reply(Reply, ReqMap, MFA, EndTime),
+ mc_spawn_replies(Res, Outstanding-1, NewReqMap, MFA, EndTime)
+ after
+ Timeout ->
+ ReqMap
+ end.
+
+mc_handle_spawn_reply({{spawn_reply, _Res, Node}, ReqId, ok, Pid},
+ ReqMap, _MFA, _EndTime) ->
+ ReqMap#{Node => {spawn, ReqId, Pid}};
+mc_handle_spawn_reply({{spawn_reply, _Res, Node}, _ReqId, error, notsup},
+ ReqMap, MFA, infinity) ->
+ {M, F, A} = MFA,
+ SrvReqId = gen_server:send_request({?NAME, Node},
+ {call, M,F,A,
+ group_leader()}),
+ ReqMap#{Node => {server, SrvReqId}};
+mc_handle_spawn_reply({{spawn_reply, Res, Node}, _ReqId, error, notsup},
+ ReqMap, MFA, EndTime) ->
+ {M, F, A} = MFA,
+ {Pid, Mon} = spawn_monitor(fun () ->
+ process_flag(trap_exit, true),
+ Request = {call, M,F,A,
+ group_leader()},
+ Timeout = mc_timeout(EndTime),
+ Result = gen_server:call({?NAME, Node},
+ Request,
+ Timeout),
+ exit({Res, Result})
+ end),
+ ReqMap#{Node => {spawn_server, Mon, Pid}};
+mc_handle_spawn_reply({{spawn_reply, _Res, Node}, _ReqId, error, noconnection},
+ ReqMap, _MFA, _EndTime) ->
+ ReqMap#{Node => {error, badnode}};
+mc_handle_spawn_reply({{spawn_reply, _Res, Node}, _ReqId, error, Reason},
+ ReqMap, _MFA, _EndTime) ->
+ ReqMap#{Node => {error, {badrpc, {'EXIT', Reason}}}}.
+
+mc_timeout(infinity) ->
+ infinity;
+mc_timeout(EndTime) when is_integer(EndTime) ->
+ Now = erlang:monotonic_time(),
+ case erlang:convert_time_unit(EndTime - Now,
+ native,
+ millisecond) + 1 of
+ Ms when Ms >= 0 ->
+ Ms;
+ _ ->
+ 0
+ end.
+
+mc_results(_Res, [], OkAcc, ErrAcc, _ReqMap, _MFA, _EndTime) ->
+ {lists:reverse(OkAcc), lists:reverse(ErrAcc)};
+mc_results(Res, [N|Ns] = Nodes, OkAcc, ErrAcc, ReqMap, MFA, EndTime) ->
+ case maps:get(N, ReqMap) of
+ {error, badnode} ->
+ mc_results(Res, Ns, OkAcc, [N|ErrAcc], ReqMap, MFA, EndTime);
+ {error, BadRpc} ->
+ mc_results(Res, Ns, [BadRpc|OkAcc], ErrAcc, ReqMap, MFA, EndTime);
+ {spawn_request, ReqId} ->
+ %% We timed out waiting for spawn replies...
+ case erlang:spawn_request_abandon(ReqId) of
+ true ->
+ %% Timed out request...
+ mc_results(Res, Ns, OkAcc, [N|ErrAcc], ReqMap, MFA, EndTime);
+ false ->
+ %% Reply has been delivered now; handle it...
+ receive
+ {{spawn_reply, Res, _}, _, _, _} = Reply ->
+ NewReqMap = mc_handle_spawn_reply(Reply, ReqMap, MFA,
+ EndTime),
+ mc_results(Res, Nodes, OkAcc, ErrAcc, NewReqMap,
+ MFA, EndTime)
+ after 0 ->
+ error(internal_error)
+ end
+ end;
+ {spawn, ReqId, Pid} ->
+ Timeout = mc_timeout(EndTime),
+ receive
+ {'DOWN', ReqId, process, Pid, Reason} ->
+ case call_result(down, ReqId, Res, Reason) of
+ {badrpc, nodedown} ->
+ mc_results(Res, Ns, OkAcc, [N|ErrAcc], ReqMap,
+ MFA, EndTime);
+ CallRes ->
+ mc_results(Res, Ns, [CallRes|OkAcc], ErrAcc,
+ ReqMap, MFA, EndTime)
+ end
+ after
+ Timeout ->
+ case erlang:demonitor(ReqId, [info]) of
+ true ->
+ mc_results(Res, Ns, OkAcc, [N|ErrAcc], ReqMap,
+ MFA, EndTime);
+ false ->
+ receive
+ {'DOWN', ReqId, process, Pid, Reason} ->
+ case call_result(down, ReqId, Res, Reason) of
+ {badrpc, nodedown} ->
+ mc_results(Res, Ns, OkAcc, [N|ErrAcc],
+ ReqMap, MFA, EndTime);
+ CallRes ->
+ mc_results(Res, Ns, [CallRes|OkAcc],
+ ErrAcc, ReqMap, MFA,
+ EndTime)
+ end
+ after 0 ->
+ error(internal_error)
+ end
+ end
+ end;
+ {spawn_server, Mon, Pid} ->
+ %% Old node with timeout on the call...
+ Result = receive
+ {'DOWN', Mon, process, Pid, {Res, CallRes}} ->
+ rpc_check(CallRes);
+ {'DOWN', Mon, process, Pid, Reason} ->
+ rpc_check_t({'EXIT',Reason})
+ end,
+ case Result of
+ {badrpc, BadRpcReason} when BadRpcReason == timeout;
+ BadRpcReason == nodedown ->
+ mc_results(Res, Ns, OkAcc, [N|ErrAcc], ReqMap, MFA, EndTime);
+ _ ->
+ mc_results(Res, Ns, [Result|OkAcc], ErrAcc, ReqMap, MFA, EndTime)
+ end;
+ {server, ReqId} ->
+ %% Old node without timeout on the call...
+ case gen_server:wait_response(ReqId, infinity) of
+ {reply, Reply} ->
+ Result = rpc_check(Reply),
+ mc_results(Res, Ns, [Result|OkAcc], ErrAcc, ReqMap, MFA, EndTime);
+ {error, {noconnection, _}} ->
+ mc_results(Res, Ns, OkAcc, [N|ErrAcc], ReqMap, MFA, EndTime);
+ {error, {Reason, _}} ->
+ BadRpc = {badrpc, {'EXIT', Reason}},
+ mc_results(Res, Ns, [BadRpc|OkAcc], ErrAcc, ReqMap, MFA, EndTime)
+ end
+ end.
+
do_multicall(Nodes, M, F, A, Timeout) ->
- {Rep,Bad} = gen_server:multi_call(Nodes, ?NAME,
- {call, M,F,A, group_leader()},
- Timeout),
- {lists:map(fun({_,R}) -> R end, Rep), Bad}.
+ Res = make_ref(),
+ EndTime = if Timeout == infinity ->
+ infinity;
+ true ->
+ Now = erlang:monotonic_time(),
+ Tmo = erlang:convert_time_unit(Timeout,
+ millisecond,
+ native),
+ Now + Tmo
+ end,
+ MFA = {M, F, A},
+ ReqMap0 = mc_requests(Res, Nodes, M, F, A, #{}),
+ ReqMap1 = mc_spawn_replies(Res, maps:size(ReqMap0), ReqMap0,
+ MFA, EndTime),
+ mc_results(Res, Nodes, [], [], ReqMap1, MFA, EndTime).
%% Send Msg to Name on all nodes, and collect the answers.
@@ -603,7 +904,75 @@ rec_nodes(Name, [{N,R} | Tail], Badnodes, Replies) ->
%% rpc's towards the same node. I.e. it returns immediately and
%% it returns a Key that can be used in a subsequent yield(Key).
--opaque key() :: pid().
+-ifdef(SERVER_SIDE_ERPC_IS_MANDATORY).
+
+%%
+%% Use this more efficient implementation of async_call()
+%% when 'erpc' support can be made mandatory for server
+%% side. It should be possible to make it mandatory in
+%% OTP 25.
+%%
+
+-opaque key() :: erpc:request_id().
+
+-spec async_call(Node, Module, Function, Args) -> Key when
+ Node :: node(),
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Key :: key().
+
+async_call(Node, Mod, Fun, Args) ->
+ try
+ erpc:send_request(Node, Mod, Fun, Args)
+ catch
+ error:{erpc, badarg} ->
+ error(badarg)
+ end.
+
+-spec yield(Key) -> Res | {badrpc, Reason} when
+ Key :: key(),
+ Res :: term(),
+ Reason :: term().
+
+yield(Key) ->
+ ?RPCIFY(erpc:receive_response(Key)).
+
+-spec nb_yield(Key, Timeout) -> {value, Val} | timeout when
+ Key :: key(),
+ Timeout :: ?TIMEOUT_TYPE,
+ Val :: (Res :: term()) | {badrpc, Reason :: term()}.
+
+nb_yield(Key, Tmo) ->
+ case ?RPCIFY(erpc:wait_response(Key, Tmo)) of
+ no_response ->
+ timeout;
+ {response, {'EXIT', _} = BadRpc} ->
+ %% RPCIFY() cannot handle this case...
+ {value, BadRpc};
+ {response, R} ->
+ {value, R};
+ BadRpc ->
+ %% An exception converted by RPCIFY()...
+ {value, BadRpc}
+ end.
+
+-spec nb_yield(Key) -> {value, Val} | timeout when
+ Key :: key(),
+ Val :: (Res :: term()) | {badrpc, Reason :: term()}.
+
+nb_yield(Key) ->
+ nb_yield(Key, 0).
+
+-else.
+
+%%
+%% Currently used implementation for async_call(). When
+%% 'erpc' support can be required for async_call()
+%% replace with the implementation above...
+%%
+
+-opaque key() :: {pid(), reference()}.
-spec async_call(Node, Module, Function, Args) -> Key when
Node :: node(),
@@ -613,49 +982,59 @@ rec_nodes(Name, [{N,R} | Tail], Badnodes, Replies) ->
Key :: key().
async_call(Node, Mod, Fun, Args) ->
- ReplyTo = self(),
- spawn(
- fun() ->
- R = call(Node, Mod, Fun, Args), %% proper rpc
- ReplyTo ! {self(), {promise_reply, R}} %% self() is key
- end).
+ Caller = self(),
+ spawn_monitor(fun() ->
+ process_flag(trap_exit, true),
+ R = call(Node, Mod, Fun, Args),
+ exit({call_result, Caller, R})
+ end).
-spec yield(Key) -> Res | {badrpc, Reason} when
Key :: key(),
Res :: term(),
Reason :: term().
-yield(Key) when is_pid(Key) ->
+yield({Pid, Ref} = Key) when is_pid(Pid),
+ is_reference(Ref) ->
{value,R} = do_yield(Key, infinity),
R.
-spec nb_yield(Key, Timeout) -> {value, Val} | timeout when
Key :: key(),
- Timeout :: timeout(),
+ Timeout :: ?TIMEOUT_TYPE,
Val :: (Res :: term()) | {badrpc, Reason :: term()}.
-nb_yield(Key, infinity=Inf) when is_pid(Key) ->
+nb_yield({Pid, Ref} = Key, infinity=Inf) when is_pid(Pid),
+ is_reference(Ref) ->
do_yield(Key, Inf);
-nb_yield(Key, Timeout) when is_pid(Key), is_integer(Timeout), Timeout >= 0 ->
- do_yield(Key, Timeout).
+nb_yield({Pid, Ref} = Key, Tmo) when is_pid(Pid),
+ is_reference(Ref),
+ is_integer(Tmo),
+ Tmo >= 0 ->
+ do_yield(Key, Tmo).
-spec nb_yield(Key) -> {value, Val} | timeout when
Key :: key(),
Val :: (Res :: term()) | {badrpc, Reason :: term()}.
-nb_yield(Key) when is_pid(Key) ->
+nb_yield({Pid, Ref} = Key) when is_pid(Pid),
+ is_reference(Ref) ->
do_yield(Key, 0).
--spec do_yield(pid(), timeout()) -> {'value', _} | 'timeout'.
+-spec do_yield({pid(), reference()}, ?TIMEOUT_TYPE) -> {'value', _} | 'timeout'.
-do_yield(Key, Timeout) ->
+do_yield({Proxy, Mon}, Tmo) ->
+ Me = self(),
receive
- {Key,{promise_reply,R}} ->
- {value,R}
- after Timeout ->
+ {'DOWN', Mon, process, Proxy, {call_result, Me, R}} ->
+ {value,R};
+ {'DOWN', Mon, process, Proxy, Reason} ->
+ {value, {badrpc, {'EXIT', Reason}}}
+ after Tmo ->
timeout
end.
+-endif.
%% A parallel network evaluator
%% ArgL === [{M,F,Args},........]
diff --git a/lib/kernel/src/seq_trace.erl b/lib/kernel/src/seq_trace.erl
index f0bd1fabe9..a9f46126ef 100644
--- a/lib/kernel/src/seq_trace.erl
+++ b/lib/kernel/src/seq_trace.erl
@@ -20,12 +20,13 @@
-module(seq_trace).
--define(SEQ_TRACE_SEND, 1). %(1 << 0)
--define(SEQ_TRACE_RECEIVE, 2). %(1 << 1)
--define(SEQ_TRACE_PRINT, 4). %(1 << 2)
--define(SEQ_TRACE_NOW_TIMESTAMP, 8). %(1 << 3)
--define(SEQ_TRACE_STRICT_MON_TIMESTAMP, 16). %(1 << 4)
--define(SEQ_TRACE_MON_TIMESTAMP, 32). %(1 << 5)
+%% Don't forget to update seq_trace_SUITE after changing these.
+-define(SEQ_TRACE_SEND, 1). %(1 << 0)
+-define(SEQ_TRACE_RECEIVE, 2). %(1 << 1)
+-define(SEQ_TRACE_PRINT, 4). %(1 << 2)
+-define(SEQ_TRACE_NOW_TIMESTAMP, 8). %(1 << 3)
+-define(SEQ_TRACE_STRICT_MON_TIMESTAMP, 16). %(1 << 4)
+-define(SEQ_TRACE_MON_TIMESTAMP, 32). %(1 << 5)
-export([set_token/1,
set_token/2,
@@ -39,7 +40,8 @@
%%---------------------------------------------------------------------------
--type flag() :: 'send' | 'receive' | 'print' | 'timestamp' | 'monotonic_timestamp' | 'strict_monotonic_timestamp'.
+-type flag() :: 'send' | 'receive' | 'print' | 'timestamp' |
+ 'monotonic_timestamp' | 'strict_monotonic_timestamp'.
-type component() :: 'label' | 'serial' | flag().
-type value() :: (Label :: term())
| {Previous :: non_neg_integer(),
diff --git a/lib/kernel/src/user.erl b/lib/kernel/src/user.erl
index 5a3487a9ba..81520dd841 100644
--- a/lib/kernel/src/user.erl
+++ b/lib/kernel/src/user.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. 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.
@@ -537,54 +537,6 @@ get_line_doit(Prompt, Port, Q, Accu, Enc) ->
binrev(L, T) ->
list_to_binary(lists:reverse(L, T)).
-%% is_cr_at(Pos,Bin) ->
-%% case Bin of
-%% <<_:Pos/binary,$\r,_/binary>> ->
-%% true;
-%% _ ->
-%% false
-%% end.
-
-%% collect_line_bin_re(Bin,_Data,Stack,_) ->
-%% case re:run(Bin,<<"\n">>) of
-%% nomatch ->
-%% X = byte_size(Bin)-1,
-%% case is_cr_at(X,Bin) of
-%% true ->
-%% <<D:X/binary,_/binary>> = Bin,
-%% [<<$\r>>,D|Stack];
-%% false ->
-%% [Bin|Stack]
-%% end;
-%% {match,[{Pos,1}]} ->
-%% PosPlus = Pos + 1,
-%% case Stack of
-%% [] ->
-%% case is_cr_at(Pos - 1,Bin) of
-%% false ->
-%% <<Head:PosPlus/binary,Tail/binary>> = Bin,
-%% {stop, Head, Tail};
-%% true ->
-%% PosMinus = Pos - 1,
-%% <<Head:PosMinus/binary,_,_,Tail/binary>> = Bin,
-%% {stop, binrev([],[Head,$\n]),Tail}
-%% end;
-%% [<<$\r>>|Stack1] when Pos =:= 0 ->
-
-%% <<_:PosPlus/binary,Tail/binary>> = Bin,
-%% {stop,binrev(Stack1, [$\n]),Tail};
-%% _ ->
-%% case is_cr_at(Pos - 1,Bin) of
-%% false ->
-%% <<Head:PosPlus/binary,Tail/binary>> = Bin,
-%% {stop,binrev(Stack, [Head]),Tail};
-%% true ->
-%% PosMinus = Pos - 1,
-%% <<Head:PosMinus/binary,_,_,Tail/binary>> = Bin,
-%% {stop, binrev(Stack,[Head,$\n]),Tail}
-%% end
-%% end
-%% end.
%% get_chars(Prompt, Module, Function, XtraArg, Port, Queue, Encoding)
%% Gets characters from the input port until the applied function
%% returns {stop,Result,RestBuf}. Does not block output until input
@@ -618,9 +570,6 @@ get_chars(Prompt, M, F, Xa, Port, Q, State, Enc) ->
{Port, eof} ->
put(eof, true),
{ok, eof, queue:new()};
- %%{io_request,From,ReplyAs,Request} when is_pid(From) ->
- %% get_chars_req(Prompt, M, F, Xa, Port, queue:new(), State,
- %% Request, From, ReplyAs);
{io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) ->
do_io_request(Req, From, ReplyAs, Port,
queue:new()), %Keep Q over this call
diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile
index bd1590ee8f..57fcc74729 100644
--- a/lib/kernel/test/Makefile
+++ b/lib/kernel/test/Makefile
@@ -25,6 +25,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
MODULES= \
+ erpc_SUITE \
rpc_SUITE \
pdict_SUITE \
bif_SUITE \
@@ -85,6 +86,7 @@ MODULES= \
logger_test_lib \
net_SUITE \
os_SUITE \
+ pg_SUITE \
pg2_SUITE \
seq_trace_SUITE \
wrap_log_reader_SUITE \
diff --git a/lib/kernel/test/application_SUITE.erl b/lib/kernel/test/application_SUITE.erl
index 1ab554db7c..aebbd7735d 100644
--- a/lib/kernel/test/application_SUITE.erl
+++ b/lib/kernel/test/application_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. 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.
@@ -39,7 +39,8 @@
distr_changed_tc1/1, distr_changed_tc2/1,
ensure_started/1, ensure_all_started/1,
shutdown_func/1, do_shutdown/1, shutdown_timeout/1, shutdown_deadlock/1,
- config_relative_paths/1]).
+ config_relative_paths/1, handle_many_config_files/1,
+ format_log_1/1, format_log_2/1]).
-define(TESTCASE, testcase_name).
-define(testcase, proplists:get_value(?TESTCASE, Config)).
@@ -59,7 +60,7 @@ all() ->
set_env, set_env_persistent, set_env_errors,
{group, distr_changed}, config_change, shutdown_func, shutdown_timeout,
shutdown_deadlock, config_relative_paths,
- persistent_env].
+ persistent_env, handle_many_config_files, format_log_1, format_log_2].
groups() ->
[{reported_bugs, [],
@@ -2076,6 +2077,31 @@ persistent_env(Conf) when is_list(Conf) ->
%% Clean up
ok = application:unload(appinc).
+
+%% Test more than one config file defined by one -config parameter:
+handle_many_config_files(Conf) when is_list(Conf) ->
+
+ %% Write a config file
+ Dir = proplists:get_value(priv_dir, Conf),
+ {ok, Fd} = file:open(filename:join(Dir, "sys.config"), [write]),
+ io:format(Fd, "[].~n", []),
+ file:close(Fd),
+ NodeName = node_name(n1, Conf),
+ Config = filename:join(Dir, "sys"),
+
+ %% Node will be started with two -config
+ %% First one has one argument and second one has two arguments.
+ {ok, Node} = start_node(
+ NodeName,
+ Config,
+ " -config " ++ Config ++ " " ++ Config
+ ),
+ case rpc:call(Node, init, get_argument, [config]) of
+ {ok, [[Config], [Config, Config]]} -> ok;
+ {ok, [[Config], [Config], [Config]]} -> ok %% This happens on windows
+ end,
+ stop_node_nice(Node).
+
%%%-----------------------------------------------------------------
%%% Tests the 'shutdown_func' kernel config parameter
%%%-----------------------------------------------------------------
@@ -3008,6 +3034,188 @@ distr_changed_prep(Conf) when is_list(Conf) ->
{value, {kernel, OldKernel}} = lists:keysearch(kernel, 1, OldEnv),
{OldKernel, OldEnv, {Cp1, Cp2, Cp3}, {Ncp1, Ncp2, Ncp3}, Config2}.
+%% Test report callback for Logger handler error_logger
+format_log_1(_Config) ->
+ FD = application:get_env(kernel, error_logger_format_depth),
+ application:unset_env(kernel, error_logger_format_depth),
+ Term = lists:seq(1, 15),
+ Application = my_application,
+ Error = exit,
+ Node = Term,
+ Report = #{label=>{application_controller,Error},
+ report=>[{application,Application},
+ {exited,Term},
+ {type,Term}]},
+ {F1, A1} = application_controller:format_log(Report),
+ FExpected1 = " application: ~tp~n"
+ " exited: ~tp~n"
+ " type: ~tp~n",
+ ct:log("F1: ~ts~nA1: ~tp", [F1,A1]),
+ FExpected1 = F1,
+ [Application,Term,Term] = A1,
+
+ Progress = #{label=>{application_controller,progress},
+ report=>[{application,Application},{started_at,Node}]},
+ {PF1,PA1} = application_controller:format_log(Progress),
+ PFExpected1 = " application: ~tp~n started_at: ~tp~n",
+ ct:log("PF1: ~ts~nPA1: ~tp", [PF1,PA1]),
+ PFExpected1 = PF1,
+ [Application,Node] = PA1,
+
+ Depth = 10,
+ ok = application:set_env(kernel, error_logger_format_depth, Depth),
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ {F2,A2} = application_controller:format_log(Report),
+ FExpected2 = " application: ~tP~n"
+ " exited: ~tP~n"
+ " type: ~tP~n",
+ ct:log("F2: ~ts~nA2: ~tp", [F2,A2]),
+ FExpected2 = F2,
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ [Application,Depth,Limited,Depth,Limited,Depth] = A2,
+
+ {PF2,PA2} = application_controller:format_log(Progress),
+ PFExpected2 = " application: ~tP~n started_at: ~tP~n",
+ ct:log("PF2: ~ts~nPA2: ~tp", [PF2,PA2]),
+ PFExpected2 = PF2,
+ [Application,Depth,Limited,Depth] = PA2,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel, error_logger_format_depth);
+ _ ->
+ application:set_env(kernel, error_logger_format_depth, FD)
+ end,
+ ok.
+
+%% Test report callback for any Logger handler
+format_log_2(_Config) ->
+ Term = lists:seq(1, 15),
+ Application = my_application,
+ Error = exit,
+ Node = Term,
+ Report = #{label=>{application_controller,Error},
+ report=>[{application,Application},
+ {exited,Term},
+ {type,Term}]},
+ FormatOpts1 = #{},
+ Str1 = flatten_format_log(Report, FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = " application: my_application\n"
+ " exited: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ " type: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("Str1: ~ts", [Str1]),
+ ct:log("length(Str1): ~p", [L1]),
+ true = Expected1 =:= Str1,
+
+ Progress = #{label=>{application_controller,progress},
+ report=>[{application,Application},{started_at,Node}]},
+ PStr1 = flatten_format_log(Progress, FormatOpts1),
+ PL1 = length(PStr1),
+ PExpected1 = " application: my_application\n"
+ " started_at: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("PStr1: ~ts", [PStr1]),
+ ct:log("length(PStr1): ~p", [PL1]),
+ true = PExpected1 =:= PStr1,
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_format_log(Report, FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = " application: my_application\n"
+ " exited: [1,2,3,4,5,6,7,8,9|...]\n"
+ " type: [1,2,3,4,5,6,7,8,9|...]\n",
+ ct:log("Str2: ~ts", [Str2]),
+ ct:log("length(Str2): ~p", [L2]),
+ true = Expected2 =:= Str2,
+
+ PStr2 = flatten_format_log(Progress, FormatOpts2),
+ PL2 = length(PStr2),
+ PExpected2 = " application: my_application\n"
+ " started_at: [1,2,3,4,5,6,7,8,9|...]\n",
+ ct:log("PStr2: ~ts", [PStr2]),
+ ct:log("length(PStr2): ~p", [PL2]),
+ true = PExpected2 =:= PStr2,
+
+ FormatOpts3 = #{chars_limit=>200},
+ Str3 = flatten_format_log(Report, FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = " application: my_application\n"
+ " exited: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ " type: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("Str3: ~ts", [Str3]),
+ ct:log("length(Str3): ~p", [L3]),
+ true = Expected3 =:= Str3,
+
+ PFormatOpts3 = #{chars_limit=>80},
+ PStr3 = flatten_format_log(Progress, PFormatOpts3),
+ PL3 = length(PStr3),
+ PExpected3 = " application: my_application\n"
+ " started_at:",
+ ct:log("PStr3: ~ts", [PStr3]),
+ ct:log("length(PStr3): ~p", [PL3]),
+ true = lists:prefix(PExpected3, PStr3),
+ true = PL3 < PL1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_format_log(Report, FormatOpts4),
+ L4 = length(Str4),
+
+ Expected4 = "Application: my_application. "
+ "Exited: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "Type: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].",
+ ct:log("Str4: ~ts", [Str4]),
+ ct:log("length(Str4): ~p", [L4]),
+ true = Expected4 =:= Str4,
+
+ PStr4 = flatten_format_log(Progress, FormatOpts4),
+ PL4 = length(PStr4),
+ PExpected4 = "Application: my_application. "
+ "Started at: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].",
+ ct:log("PStr4: ~ts", [PStr4]),
+ ct:log("length(PStr4): ~p", [PL4]),
+ true = PExpected4 =:= PStr4,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_format_log(Report, FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "Application: my_application. "
+ "Exited: [1,2,3,4,5,6,7,8,9|...]. "
+ "Type: [1,2,3,4,5,6,7,8,9|...].",
+ ct:log("Str5: ~ts", [Str5]),
+ ct:log("length(Str5): ~p", [L5]),
+ true = Expected5 =:= Str5,
+
+ PStr5 = flatten_format_log(Progress, FormatOpts5),
+ PL5 = length(PStr5),
+ PExpected5 = "Application: my_application. "
+ "Started at: [1,2,3,4,5,6,7,8,9|...].",
+ ct:log("PStr5: ~ts", [PStr5]),
+ ct:log("length(PStr5): ~p", [PL5]),
+ true = PExpected5 =:= PStr5,
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>100},
+ Str6 = flatten_format_log(Report, FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "Application: my_application. Exited:",
+ ct:log("Str6: ~ts", [Str6]),
+ ct:log("length(Str6): ~p", [L6]),
+ true = lists:prefix(Expected6, Str6),
+ true = L6 < L4,
+
+ PFormatOpts6 = #{single_line=>true, chars_limit=>60},
+ PStr6 = flatten_format_log(Progress, PFormatOpts6),
+ PL6 = length(PStr6),
+ PExpected6 = "Application: my_application. Started at:",
+ ct:log("PStr6: ~ts", [PStr6]),
+ ct:log("length(PStr6): ~p", [PL6]),
+ true = lists:prefix(PExpected6, PStr6),
+ true = PL6 < PL4,
+
+ ok.
+
+flatten_format_log(Report, Format) ->
+ lists:flatten(application_controller:format_log(Report, Format)).
%%% Copied from init_SUITE.erl.
is_real_system(KernelVsn, StdlibVsn) ->
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index 131e3fed34..ed1976d912 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -26,7 +26,7 @@
-export([set_path/1, get_path/1, add_path/1, add_paths/1, del_path/1,
replace_path/1, load_file/1, load_abs/1, ensure_loaded/1,
delete/1, purge/1, purge_many_exits/0, purge_many_exits/1,
- soft_purge/1, is_loaded/1, all_loaded/1,
+ soft_purge/1, is_loaded/1, all_loaded/1, all_available/1,
load_binary/1, dir_req/1, object_code/1, set_path_file/1,
upgrade/0, upgrade/1,
sticky_dir/1, pa_pz_option/1, add_del_path/1,
@@ -61,7 +61,7 @@ all() ->
[set_path, get_path, add_path, add_paths, del_path,
replace_path, load_file, load_abs, ensure_loaded,
delete, purge, purge_many_exits, soft_purge, is_loaded, all_loaded,
- load_binary, dir_req, object_code, set_path_file,
+ all_available, load_binary, dir_req, object_code, set_path_file,
upgrade,
sticky_dir, pa_pz_option, add_del_path, dir_disappeared,
ext_mod_dep, clash, where_is_file,
@@ -507,6 +507,47 @@ all_unique([]) -> ok;
all_unique([_]) -> ok;
all_unique([{X,_}|[{Y,_}|_]=T]) when X < Y -> all_unique(T).
+all_available(Config) when is_list(Config) ->
+ case test_server:is_cover() of
+ true -> {skip,"Cover is running"};
+ false -> all_available_1(Config)
+ end.
+
+all_available_1(Config) ->
+
+ %% Add an ez dir to make sure the modules in there are found
+ DDir = proplists:get_value(data_dir,Config)++"clash/",
+ true = code:add_path(DDir++"foobar-0.1.ez/foobar-0.1/ebin"),
+
+ Available = code:all_available(),
+
+ %% Test that baz and blarg that are part of the .ez archive are found
+ {value, _} =
+ lists:search(fun({Name,_,Loaded}) -> not Loaded andalso Name =:= "baz" end, Available),
+ {value, _} =
+ lists:search(fun({Name,_,Loaded}) -> not Loaded andalso Name =:= "blarg" end, Available),
+
+ %% Test that all loaded are part of all available
+ Loaded = [{atom_to_list(M),P,true} || {M,P} <- code:all_loaded()],
+ [] = Loaded -- Available,
+
+ {value, {ModStr,_Path,false} = NotLoaded} =
+ lists:search(fun({Name,_,Loaded}) -> not is_atom(Name) end, Available),
+ ct:log("Testing with ~p",[NotLoaded]),
+
+ Mod = list_to_atom(ModStr),
+
+ %% Test that the module is actually not loaded
+ false = code:is_loaded(Mod),
+
+ %% Load it
+ Mod:module_info(),
+
+ {value, {ModStr,_Path,true}} =
+ lists:search(fun({Name,_,_}) -> Name =:= ModStr end, code:all_available()),
+
+ ok.
+
load_binary(Config) when is_list(Config) ->
TestDir = test_dir(),
File = TestDir ++ "/code_b_test" ++ code:objfile_extension(),
@@ -1883,6 +1924,11 @@ module_status() ->
loaded = code:module_status(erlang), % preloaded
loaded = code:module_status(?MODULE), % normal known loaded
+ %% module_status/0 returns status for each loaded module
+ true = (lists:sort([{M, code:module_status(M)}
+ || {M, _} <- code:all_loaded()])
+ =:= lists:sort(code:module_status())),
+
non_existing = code:which(?TESTMOD), % verify dummy name not in path
code:purge(?TESTMOD), % ensure no previous version in memory
code:delete(?TESTMOD),
@@ -1895,6 +1941,11 @@ module_status() ->
"" = code:which(?TESTMOD), % verify empty string for source file
loaded = code:module_status(?TESTMOD),
+ %% module_status/1 also accepts a list of modules
+ [] = code:module_status([]),
+ [{erlang, loaded},{?MODULE,loaded},{?TESTMOD,loaded}] =
+ code:module_status([erlang, ?MODULE, ?TESTMOD]),
+
%% deleting generated code
true = code:delete(?TESTMOD),
non_existing = code:which(?TESTMOD), % verify still not in path
diff --git a/lib/kernel/test/erl_distribution_SUITE.erl b/lib/kernel/test/erl_distribution_SUITE.erl
index c3a022df0a..70661637cc 100644
--- a/lib/kernel/test/erl_distribution_SUITE.erl
+++ b/lib/kernel/test/erl_distribution_SUITE.erl
@@ -20,6 +20,7 @@
-module(erl_distribution_SUITE).
-include_lib("common_test/include/ct.hrl").
+-include_lib("kernel/include/dist.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
@@ -41,7 +42,9 @@
monitor_nodes_combinations/1,
monitor_nodes_cleanup/1,
monitor_nodes_many/1,
- dist_ctrl_proc_smoke/1]).
+ monitor_nodes_down_up/1,
+ dist_ctrl_proc_smoke/1,
+ dist_ctrl_proc_reject/1]).
%% Performs the test at another node.
-export([get_socket_priorities/0,
@@ -53,7 +56,7 @@
-export([init_per_testcase/2, end_per_testcase/2]).
--export([dist_cntrlr_output_test/2]).
+-export([dist_cntrlr_output_test_size/2]).
-export([pinger/1]).
@@ -72,6 +75,7 @@ suite() ->
all() ->
[dist_ctrl_proc_smoke,
+ dist_ctrl_proc_reject,
tick, tick_change, nodenames, hostnames, illegal_nodenames,
connect_node,
hidden_node, setopts,
@@ -85,13 +89,16 @@ groups() ->
monitor_nodes_node_type, monitor_nodes_misc,
monitor_nodes_otp_6481, monitor_nodes_errors,
monitor_nodes_combinations, monitor_nodes_cleanup,
- monitor_nodes_many]}].
+ monitor_nodes_many,
+ monitor_nodes_down_up]}].
init_per_suite(Config) ->
+ start_gen_tcp_dist_test_type_server(),
Config.
end_per_suite(_Config) ->
[slave:stop(N) || N <- nodes()],
+ kill_gen_tcp_dist_test_type_server(),
ok.
init_per_group(_GroupName, Config) ->
@@ -275,7 +282,7 @@ test_node(Name, Illigal, ExtraArgs) ->
end,
net_kernel:monitor_nodes(true),
BinCommand = unicode:characters_to_binary(Command, utf8),
- Prt = open_port({spawn, BinCommand}, [stream,{cd,"hostnames_nodedir"}]),
+ _Prt = open_port({spawn, BinCommand}, [stream,{cd,"hostnames_nodedir"}]),
Node = list_to_atom(Name),
receive
{nodeup, Node} ->
@@ -1456,89 +1463,268 @@ monitor_nodes_many(DCfg, _Config) ->
MonNodeState = monitor_node_state(),
ok.
+%% Test order of messages nodedown and nodeup.
+monitor_nodes_down_up(Config) when is_list(Config) ->
+ [An] = get_nodenames(1, monitor_nodeup),
+ {ok, A} = ct_slave:start(An),
+
+ try
+ monitor_nodes_yoyo(A)
+ after
+ catch ct_slave:stop(A)
+ end.
+
+monitor_nodes_yoyo(A) ->
+ net_kernel:monitor_nodes(true),
+ Papa = self(),
+
+ %% Spawn lots of processes doing one erlang:monitor_node(A,true) each
+ %% just to get lots of other monitors to fire when connection goes down
+ %% and thereby give time for {nodeup,A} to race before {nodedown,A}.
+ NodeMonCnt = 10000,
+ NodeMons = [my_spawn_opt(fun F() ->
+ monitor_node = receive_any(),
+ monitor_node(A, true),
+ Papa ! ready,
+ {nodedown, A} = receive_any(),
+ F()
+ end,
+ [link, monitor, {priority, low}])
+ ||
+ _ <- lists:seq(1, NodeMonCnt)],
+
+ %% Spawn message spamming process to trigger new connection setups
+ %% as quick as possible.
+ Spammer = my_spawn_opt(fun F() ->
+ {dummy, A} ! trigger_auto_connect,
+ F()
+ end,
+ [link, monitor]),
+
+ %% Now bring connection down and verify we get {nodedown,A} before {nodeup,A}.
+ Yoyos = 20,
+ [begin
+ [P ! monitor_node || P <- NodeMons],
+ [receive ready -> ok end || _ <- NodeMons],
+
+ Owner = get_conn_owner(A),
+ exit(Owner, kill),
+
+ {nodedown, A} = receive_any(),
+ {nodeup, A} = receive_any()
+ end
+ || _ <- lists:seq(1,Yoyos)],
+
+ unlink(Spammer),
+ exit(Spammer, die),
+ receive {'DOWN',_,process,Spammer,_} -> ok end,
+
+ [begin unlink(P), exit(P, die) end || P <- NodeMons],
+ [receive {'DOWN',_,process,P,_} -> ok end || P <- NodeMons],
+
+ net_kernel:monitor_nodes(false),
+ ok.
+
+receive_any() ->
+ receive_any(infinity).
+
+receive_any(Timeout) ->
+ receive
+ M -> M
+ after
+ Timeout -> timeout
+ end.
+
+my_spawn_opt(Fun, Opts) ->
+ case spawn_opt(Fun, Opts) of
+ {Pid, _Mref} -> Pid;
+ Pid -> Pid
+ end.
+
+get_conn_owner(Node) ->
+ {ok, NodeInfo} = net_kernel:node_info(Node),
+ {value,{owner, Owner}} = lists:keysearch(owner, 1, NodeInfo),
+ Owner.
+
dist_ctrl_proc_smoke(Config) when is_list(Config) ->
+ dist_ctrl_proc_test(get_nodenames(2, ?FUNCTION_NAME)).
+
+dist_ctrl_proc_reject(Config) when is_list(Config) ->
+ ToReject = combinations(dist_util:rejectable_flags()),
+ lists:map(fun(Flags) ->
+ ct:log("Try to reject ~p",[Flags]),
+ dist_ctrl_proc_test(get_nodenames(2, ?FUNCTION_NAME),
+ "-gen_tcp_dist_reject_flags " ++ integer_to_list(Flags))
+ end, ToReject).
+
+combinations([H | T]) ->
+ lists:flatten([[(1 bsl H) bor C || C <- combinations(T)] | combinations(T)]);
+combinations([]) ->
+ [0];
+combinations(BitField) ->
+ lists:sort(combinations(bits(BitField, 0))).
+
+bits(0, _) ->
+ [];
+bits(BitField, Cnt) when BitField band 1 == 1 ->
+ [Cnt | bits(BitField bsr 1, Cnt + 1)];
+bits(BitField, Cnt) ->
+ bits(BitField bsr 1, Cnt + 1).
+
+dist_ctrl_proc_test(Nodes) ->
+ dist_ctrl_proc_test(Nodes,"").
+
+dist_ctrl_proc_test([Name1,Name2], Extra) ->
ThisNode = node(),
- [Name1, Name2] = get_nodenames(2, dist_ctrl_proc_example_smoke),
- GetSizeArg = " -gen_tcp_dist_output_loop "
- ++ atom_to_list(?MODULE) ++ " "
- ++ "dist_cntrlr_output_test",
+ GenTcpOptProlog = "-proto_dist gen_tcp "
+ "-gen_tcp_dist_output_loop " ++ atom_to_list(?MODULE) ++ " " ++
+ "dist_cntrlr_output_test_size " ++ Extra,
{ok, Node1} = start_node("", Name1, "-proto_dist gen_tcp"),
- {ok, Node2} = start_node("", Name2, "-proto_dist gen_tcp" ++ GetSizeArg),
- pong = rpc:call(Node1, net_adm, ping, [Node2]),
- NL1 = lists:sort([ThisNode, Node2]),
- NL2 = lists:sort([ThisNode, Node1]),
- NL1 = lists:sort(rpc:call(Node1, erlang, nodes, [])),
- NL2 = lists:sort(rpc:call(Node2, erlang, nodes, [])),
+ {ok, Node2} = start_node("", Name2, GenTcpOptProlog),
+ NL = lists:sort([ThisNode, Node1, Node2]),
+ wait_until(fun () ->
+ NL == lists:sort([node()|nodes()])
+ end),
+ wait_until(fun () ->
+ NL == lists:sort([rpc:call(Node1,erlang, node, [])
+ | rpc:call(Node1, erlang, nodes, [])])
+ end),
+ wait_until(fun () ->
+ NL == lists:sort([rpc:call(Node2,erlang, node, [])
+ | rpc:call(Node2, erlang, nodes, [])])
+ end),
+
+ smoke_communicate(Node1, gen_tcp_dist, dist_cntrlr_output_loop),
+ smoke_communicate(Node2, erl_distribution_SUITE, dist_cntrlr_output_loop_size),
+ stop_node(Node1),
+ stop_node(Node2),
+ ok.
+
+smoke_communicate(Node, OLoopMod, OLoopFun) ->
%% Verify that we actually are executing the distribution
%% module we expect and also massage message passing over
- %% it a bit...
- Ps1 = rpc:call(Node1, erlang, processes, []),
- try
- lists:foreach(
- fun (P) ->
- case rpc:call(Node1, erlang, process_info, [P, current_stacktrace]) of
- undefined ->
- ok;
- {current_stacktrace, StkTrace} ->
- lists:foreach(fun ({gen_tcp_dist,
- dist_cntrlr_output_loop,
- 2, _}) ->
- io:format("~p ~p~n", [P, StkTrace]),
- throw(found_it);
- (_) ->
- ok
- end, StkTrace)
- end
- end, Ps1),
- exit({missing, dist_cntrlr_output_loop})
- catch
- throw:found_it -> ok
- end,
-
- Ps2 = rpc:call(Node2, erlang, processes, []),
+ %% the connection a bit...
+ Ps = rpc:call(Node, erlang, processes, []),
try
lists:foreach(
fun (P) ->
- case rpc:call(Node2, erlang, process_info, [P, current_stacktrace]) of
+ case rpc:call(Node, erlang, process_info, [P, current_stacktrace]) of
undefined ->
ok;
{current_stacktrace, StkTrace} ->
- lists:foreach(fun ({erl_distribution_SUITE,
- dist_cntrlr_output_loop,
- 2, _}) ->
+ lists:foreach(fun ({Mod, Fun, 2, _}) when Mod == OLoopMod,
+ Fun == OLoopFun ->
io:format("~p ~p~n", [P, StkTrace]),
throw(found_it);
(_) ->
ok
end, StkTrace)
end
- end, Ps2),
- exit({missing, dist_cntrlr_output_loop})
+ end, Ps),
+ exit({missing, {OLoopMod, OLoopFun}})
catch
throw:found_it -> ok
end,
-
- stop_node(Node1),
- stop_node(Node2),
+ Bin = list_to_binary(lists:duplicate(1000,100)),
+ BitStr = <<0:7999>>,
+ List = [[Bin], atom, [BitStr|Bin], make_ref(), [[[BitStr|"hopp"]]],
+ 4711, 111122222211111111111111,"hej", fun () -> ok end, BitStr,
+ self(), fun erlang:node/1],
+ Pid = spawn_link(Node, fun () -> receive {From1, Msg1} -> From1 ! Msg1 end,
+ receive {From2, Msg2} -> From2 ! Msg2 end
+ end),
+ R = make_ref(),
+ Pid ! {self(), [R, List]},
+ receive [R, L1] -> List = L1 end,
+
+ %% Send a huge message in order to trigger message fragmentation if enabled
+ FragBin = <<0:(2*(1024*64*8))>>,
+ Pid ! {self(), [R, List, FragBin]},
+ receive [R, L2, B] -> List = L2, FragBin = B end,
+
+ unlink(Pid),
+ exit(Pid, kill),
ok.
%% Misc. functions
run_dist_configs(Func, Config) ->
- GetSizeArg = " -gen_tcp_dist_output_loop "
- ++ atom_to_list(?MODULE) ++ " "
- ++ "dist_cntrlr_output_test",
+ GetOptProlog = "-proto_dist gen_tcp -gen_tcp_dist_output_loop "
+ ++ atom_to_list(?MODULE) ++ " ",
+ GenTcpDistTest = case get_gen_tcp_dist_test_type() of
+ default ->
+ {"gen_tcp_dist", "-proto_dist gen_tcp"};
+ size ->
+ {"gen_tcp_dist (get_size)",
+ GetOptProlog ++ "dist_cntrlr_output_test_size"}
+ end,
lists:map(fun ({DCfgName, DCfg}) ->
io:format("~n~n=== Running ~s configuration ===~n~n",
[DCfgName]),
Func(DCfg, Config)
end,
- [{"default", ""},
- {"gen_tcp_dist", "-proto_dist gen_tcp"},
- {"gen_tcp_dist (get_size)", "-proto_dist gen_tcp" ++ GetSizeArg}]).
+ [{"default", ""}, GenTcpDistTest]).
+
+start_gen_tcp_dist_test_type_server() ->
+ Me = self(),
+ Go = make_ref(),
+ io:format("STARTING: gen_tcp_dist_test_type_server~n",[]),
+ {P, M} = spawn_monitor(fun () ->
+ register(gen_tcp_dist_test_type_server, self()),
+ Me ! Go,
+ gen_tcp_dist_test_type_server()
+ end),
+ receive
+ Go ->
+ ok;
+ {'DOWN', M, process, P, _} ->
+ start_gen_tcp_dist_test_type_server()
+ end.
+
+kill_gen_tcp_dist_test_type_server() ->
+ case whereis(gen_tcp_dist_test_type_server) of
+ undefined ->
+ ok;
+ Pid ->
+ exit(Pid,kill),
+ %% Sync death, before continuing...
+ false = erlang:is_process_alive(Pid)
+ end.
+
+gen_tcp_dist_test_type_server() ->
+ Type = case abs(erlang:monotonic_time(second)) rem 2 of
+ 0 -> default;
+ 1 -> size
+ end,
+ gen_tcp_dist_test_type_server(Type).
+
+gen_tcp_dist_test_type_server(Type) ->
+ receive
+ {From, Ref} ->
+ From ! {Ref, Type},
+ NewType = case Type of
+ default -> size;
+ size -> default
+ end,
+ gen_tcp_dist_test_type_server(NewType)
+ end.
+
+get_gen_tcp_dist_test_type() ->
+ Ref = make_ref(),
+ try
+ gen_tcp_dist_test_type_server ! {self(), Ref},
+ receive
+ {Ref, Type} ->
+ Type
+ end
+ catch
+ error:badarg ->
+ start_gen_tcp_dist_test_type_server(),
+ get_gen_tcp_dist_test_type()
+ end.
-dist_cntrlr_output_test(DHandle, Socket) ->
+dist_cntrlr_output_test_size(DHandle, Socket) ->
false = erlang:dist_ctrl_get_opt(DHandle, get_size),
false = erlang:dist_ctrl_set_opt(DHandle, get_size, true),
true = erlang:dist_ctrl_get_opt(DHandle, get_size),
@@ -1546,27 +1732,33 @@ dist_cntrlr_output_test(DHandle, Socket) ->
false = erlang:dist_ctrl_get_opt(DHandle, get_size),
false = erlang:dist_ctrl_set_opt(DHandle, get_size, true),
true = erlang:dist_ctrl_get_opt(DHandle, get_size),
- dist_cntrlr_output_loop(DHandle, Socket).
-
-dist_cntrlr_send_data(DHandle, Socket) ->
+ dist_cntrlr_output_loop_size(DHandle, Socket).
+
+dist_cntrlr_output_loop_size(DHandle, Socket) ->
+ receive
+ dist_data ->
+ %% Outgoing data from this node...
+ dist_cntrlr_send_data_size(DHandle, Socket);
+ _ ->
+ ok %% Drop garbage message...
+ end,
+ dist_cntrlr_output_loop_size(DHandle, Socket).
+
+dist_cntrlr_send_data_size(DHandle, Socket) ->
case erlang:dist_ctrl_get_data(DHandle) of
none ->
erlang:dist_ctrl_get_data_notification(DHandle);
{Size, Data} ->
+ ok = ensure_iovec(Data),
Size = erlang:iolist_size(Data),
ok = gen_tcp:send(Socket, Data),
- dist_cntrlr_send_data(DHandle, Socket)
+ dist_cntrlr_send_data_size(DHandle, Socket)
end.
-dist_cntrlr_output_loop(DHandle, Socket) ->
- receive
- dist_data ->
- %% Outgoing data from this node...
- dist_cntrlr_send_data(DHandle, Socket);
- _ ->
- ok %% Drop garbage message...
- end,
- dist_cntrlr_output_loop(DHandle, Socket).
+ensure_iovec([]) ->
+ ok;
+ensure_iovec([X|Y]) when is_binary(X) ->
+ ensure_iovec(Y).
monitor_node_state() ->
erts_debug:set_internal_state(available_internal_state, true),
@@ -1593,7 +1785,7 @@ print_my_messages() ->
sleep(T) -> receive after T * 1000 -> ok end.
-start_node(DCfg, Name, Param, this) ->
+start_node(_DCfg, Name, Param, this) ->
NewParam = Param ++ " -pa " ++ filename:dirname(code:which(?MODULE)),
test_server:start_node(Name, peer, [{args, NewParam}, {erl, [this]}]);
start_node(DCfg, Name, Param, "this") ->
@@ -1671,3 +1863,5 @@ block_emu(Ms) ->
Res = erts_debug:set_internal_state(block, Ms),
erts_debug:set_internal_state(available_internal_state, false),
Res.
+
+
diff --git a/lib/kernel/test/erl_distribution_wb_SUITE.erl b/lib/kernel/test/erl_distribution_wb_SUITE.erl
index 8256444bdc..ca4511a19b 100644
--- a/lib/kernel/test/erl_distribution_wb_SUITE.erl
+++ b/lib/kernel/test/erl_distribution_wb_SUITE.erl
@@ -28,15 +28,6 @@
-export([init_per_testcase/2, end_per_testcase/2, whitebox/1,
switch_options/1, missing_compulsory_dflags/1]).
-%% 1)
-%%
-%% Connections are now always set up symmetrically with respect to
-%% publication. If connecting node doesn't send DFLAG_PUBLISHED
-%% the other node wont send DFLAG_PUBLISHED. If the connecting
-%% node send DFLAG_PUBLISHED but the other node doesn't send
-%% DFLAG_PUBLISHED, the connecting node should consider its
-%% DFLAG_PUBLISHED as dropped, i.e the connecting node wont be
-%% published on the other node.
-define(to_port(Socket, Data),
case inet_tcp:send(Socket, Data) of
@@ -47,6 +38,9 @@
R
end).
+-define(DIST_VER_HIGH, 6).
+-define(DIST_VER_LOW, 5).
+
-define(DFLAG_PUBLISHED,1).
-define(DFLAG_ATOM_CACHE,2).
-define(DFLAG_EXTENDED_REFERENCES,4).
@@ -57,15 +51,19 @@
-define(DFLAG_NEW_FUN_TAGS,16#80).
-define(DFLAG_EXTENDED_PIDS_PORTS,16#100).
-define(DFLAG_UTF8_ATOMS, 16#10000).
+-define(DFLAG_BIG_CREATION, 16#40000).
+-define(DFLAG_HANDSHAKE_23, 16#01000000).
%% From R9 and forward extended references is compulsory
%% From R10 and forward extended pids and ports are compulsory
%% From R20 and forward UTF8 atoms are compulsory
%% From R21 and forward NEW_FUN_TAGS is compulsory (no more tuple fallback {fun, ...})
+%% From R23 and forward BIG_CREATION is compulsory
-define(COMPULSORY_DFLAGS, (?DFLAG_EXTENDED_REFERENCES bor
?DFLAG_EXTENDED_PIDS_PORTS bor
?DFLAG_UTF8_ATOMS bor
- ?DFLAG_NEW_FUN_TAGS)).
+ ?DFLAG_NEW_FUN_TAGS bor
+ ?DFLAG_BIG_CREATION)).
-define(PASS_THROUGH, $p).
@@ -131,9 +129,18 @@ whitebox(Config) when is_list(Config) ->
{ok, Node} = start_node(?MODULE,""),
Cookie = erlang:get_cookie(),
{_,Host} = split(node()),
- ok = pending_up_md5(Node, join(ccc,Host), Cookie),
- ok = simultaneous_md5(Node, join('A',Host), Cookie),
- ok = simultaneous_md5(Node, join(zzzzzzzzzzzzzz,Host), Cookie),
+ [begin
+ io:format("Test OurVersion=~p and TrustEpmd=~p\n",
+ [OurVersion, TrustEpmd]),
+ ok = pending_up_md5(Node, join(ccc,Host), OurVersion,
+ TrustEpmd, Cookie),
+ ok = simultaneous_md5(Node, join('A',Host), OurVersion,
+ TrustEpmd, Cookie),
+ ok = simultaneous_md5(Node, join(zzzzzzzzzzzzzz,Host),
+ OurVersion, TrustEpmd, Cookie)
+ end
+ || OurVersion <- lists:seq(?DIST_VER_LOW, ?DIST_VER_HIGH),
+ TrustEpmd <- [true, false]],
stop_node(Node),
ok.
@@ -202,17 +209,22 @@ test_switch_active_and_packet() ->
%%
%% Handshake tests
%%
-pending_up_md5(Node,OurName,Cookie) ->
+pending_up_md5(Node,OurName,OurVersion,TrustEpmd,Cookie) ->
{NA,NB} = split(Node),
- {port,PortNo,_} = erl_epmd:port_please(NA,NB),
+ {port,PortNo,EpmdSaysVersion} = erl_epmd:port_please(NA,NB),
{ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
[{active,false},
{packet,2}]),
- send_name(SocketA,OurName,5),
+ AssumedVersion = case TrustEpmd of
+ true -> EpmdSaysVersion;
+ false -> ?DIST_VER_LOW
+ end,
+ SentNameMsg = send_name(SocketA,OurName, OurVersion, AssumedVersion),
ok = recv_status(SocketA),
- {hidden,Node,5,HisChallengeA} = recv_challenge(SocketA), % See 1)
+ {Node,ChallengeMsg,HisChallengeA} = recv_challenge(SocketA,OurVersion),
OurChallengeA = gen_challenge(),
OurDigestA = gen_digest(HisChallengeA, Cookie),
+ send_complement(SocketA, SentNameMsg, ChallengeMsg, OurVersion),
send_challenge_reply(SocketA, OurChallengeA, OurDigestA),
ok = recv_challenge_ack(SocketA, OurChallengeA, Cookie),
%%%
@@ -224,13 +236,14 @@ pending_up_md5(Node,OurName,Cookie) ->
{ok, SocketB} = gen_tcp:connect(atom_to_list(NB),PortNo,
[{active,false},
{packet,2}]),
- send_name(SocketB,OurName,5),
+ SentNameMsg = send_name(SocketB,OurName, OurVersion, AssumedVersion),
alive = recv_status(SocketB),
send_status(SocketB, true),
gen_tcp:close(SocketA),
- {hidden,Node,5,HisChallengeB} = recv_challenge(SocketB), % See 1)
+ {Node,ChallengeMsg,HisChallengeB} = recv_challenge(SocketB,OurVersion),
OurChallengeB = gen_challenge(),
OurDigestB = gen_digest(HisChallengeB, Cookie),
+ send_complement(SocketB, SentNameMsg, ChallengeMsg, OurVersion),
send_challenge_reply(SocketB, OurChallengeB, OurDigestB),
ok = recv_challenge_ack(SocketB, OurChallengeB, Cookie),
%%%
@@ -246,7 +259,7 @@ pending_up_md5(Node,OurName,Cookie) ->
gen_tcp:close(SocketB),
ok.
-simultaneous_md5(Node, OurName, Cookie) when OurName < Node ->
+simultaneous_md5(Node, OurName, OurVersion, TrustEpmd, Cookie) when OurName < Node ->
pong = net_adm:ping(Node),
LSocket = case gen_tcp:listen(0, [{active, false}, {packet,2}]) of
{ok, Socket} ->
@@ -254,15 +267,19 @@ simultaneous_md5(Node, OurName, Cookie) when OurName < Node ->
Else ->
exit(Else)
end,
- EpmdSocket = register(OurName, LSocket, 1, 5),
+ EpmdSocket = register_node(OurName, LSocket, ?DIST_VER_LOW, ?DIST_VER_LOW),
{NA, NB} = split(Node),
rpc:cast(Node, net_adm, ping, [OurName]),
receive after 1000 -> ok end,
- {port, PortNo, _} = erl_epmd:port_please(NA,NB),
+ {port, PortNo, EpmdSaysVersion} = erl_epmd:port_please(NA,NB),
{ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
[{active,false},
{packet,2}]),
- send_name(SocketA,OurName,5),
+ AssumedVersion = case TrustEpmd of
+ true -> EpmdSaysVersion;
+ false -> ?DIST_VER_LOW
+ end,
+ send_name(SocketA,OurName, OurVersion, AssumedVersion),
%% We are still not marked up on the other side, as our first message
%% is not sent.
SocketB = case gen_tcp:accept(LSocket) of
@@ -275,11 +292,13 @@ simultaneous_md5(Node, OurName, Cookie) when OurName < Node ->
%% Now we are expected to close A
gen_tcp:close(SocketA),
%% But still Socket B will continue
- {normal,Node,5} = recv_name(SocketB), % See 1)
+ {Node,GotNameMsg,GotFlags} = recv_name(SocketB),
+ true = (GotFlags band ?DFLAG_HANDSHAKE_23) =/= 0,
send_status(SocketB, ok_simultaneous),
MyChallengeB = gen_challenge(),
- send_challenge(SocketB, OurName, MyChallengeB,5),
- HisChallengeB = recv_challenge_reply(SocketB, MyChallengeB, Cookie),
+ send_challenge(SocketB, OurName, MyChallengeB, OurVersion, GotFlags),
+ recv_complement(SocketB, GotNameMsg, OurVersion),
+ {ok,HisChallengeB} = recv_challenge_reply(SocketB, MyChallengeB, Cookie),
DigestB = gen_digest(HisChallengeB,Cookie),
send_challenge_ack(SocketB, DigestB),
inet:setopts(SocketB, [{active, false},
@@ -293,7 +312,7 @@ simultaneous_md5(Node, OurName, Cookie) when OurName < Node ->
gen_tcp:close(EpmdSocket),
ok;
-simultaneous_md5(Node, OurName, Cookie) when OurName > Node ->
+simultaneous_md5(Node, OurName, OurVersion, TrustEpmd, Cookie) when OurName > Node ->
pong = net_adm:ping(Node),
LSocket = case gen_tcp:listen(0, [{active, false}, {packet,2}]) of
{ok, Socket} ->
@@ -301,11 +320,12 @@ simultaneous_md5(Node, OurName, Cookie) when OurName > Node ->
Else ->
exit(Else)
end,
- EpmdSocket = register(OurName, LSocket, 1, 5),
+ EpmdSocket = register_node(OurName, LSocket,
+ ?DIST_VER_LOW, ?DIST_VER_LOW),
{NA, NB} = split(Node),
rpc:cast(Node, net_adm, ping, [OurName]),
receive after 1000 -> ok end,
- {port, PortNo, _} = erl_epmd:port_please(NA,NB),
+ {port, PortNo, EpmdSaysVersion} = erl_epmd:port_please(NA,NB),
{ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
[{active,false},
{packet,2}]),
@@ -315,16 +335,22 @@ simultaneous_md5(Node, OurName, Cookie) when OurName > Node ->
Else2 ->
exit(Else2)
end,
- send_name(SocketA,OurName,5),
+ AssumedVersion = case TrustEpmd of
+ true -> EpmdSaysVersion;
+ false -> ?DIST_VER_LOW
+ end,
+ SentNameMsg = send_name(SocketA,OurName, OurVersion, AssumedVersion),
ok_simultaneous = recv_status(SocketA),
%% Socket B should die during this
case catch begin
- {normal,Node,5} = recv_name(SocketB), % See 1)
+ {Node,GotNameMsg,GotFlagsB} = recv_name(SocketB),
+ true = (GotFlagsB band ?DFLAG_HANDSHAKE_23) =/= 0,
send_status(SocketB, ok_simultaneous),
MyChallengeB = gen_challenge(),
send_challenge(SocketB, OurName, MyChallengeB,
- 5),
- HisChallengeB = recv_challenge_reply(
+ OurVersion, GotFlagsB),
+ recv_complement(SocketB, GotNameMsg, OurVersion),
+ {ok,HisChallengeB} = recv_challenge_reply(
SocketB,
MyChallengeB,
Cookie),
@@ -346,9 +372,10 @@ simultaneous_md5(Node, OurName, Cookie) when OurName > Node ->
end,
gen_tcp:close(SocketB),
%% But still Socket A will continue
- {hidden,Node,5,HisChallengeA} = recv_challenge(SocketA), % See 1)
+ {Node,ChallengeMsg,HisChallengeA} = recv_challenge(SocketA,OurVersion),
OurChallengeA = gen_challenge(),
OurDigestA = gen_digest(HisChallengeA, Cookie),
+ send_complement(SocketA, SentNameMsg, ChallengeMsg, OurVersion),
send_challenge_reply(SocketA, OurChallengeA, OurDigestA),
ok = recv_challenge_ack(SocketA, OurChallengeA, Cookie),
@@ -368,13 +395,16 @@ missing_compulsory_dflags(Config) when is_list(Config) ->
{ok, Node} = start_node(Name1,""),
{NA,NB} = split(Node),
{port,PortNo,_} = erl_epmd:port_please(NA,NB),
- {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
- [{active,false},
- {packet,2}]),
- BadNode = list_to_atom(atom_to_list(Name2)++"@"++atom_to_list(NB)),
- send_name(SocketA,BadNode,5,0),
- not_allowed = recv_status(SocketA),
- gen_tcp:close(SocketA),
+ [begin
+ {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
+ [{active,false},
+ {packet,2}]),
+ BadNode = list_to_atom(atom_to_list(Name2)++"@"++atom_to_list(NB)),
+ send_name(SocketA,BadNode, Version, Version, 0),
+ not_allowed = recv_status(SocketA),
+ gen_tcp:close(SocketA)
+ end
+ || Version <- lists:seq(?DIST_VER_LOW, ?DIST_VER_HIGH)],
stop_node(Node),
ok.
@@ -486,46 +516,91 @@ recv_status(Socket) ->
exit(Bad)
end.
-send_challenge(Socket, Node, Challenge, Version) ->
- send_challenge(Socket, Node, Challenge, Version, ?COMPULSORY_DFLAGS).
-send_challenge(Socket, Node, Challenge, Version, Flags) ->
- {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
- ?to_port(Socket, [$n,?int16(Version),?int32(Flags),
- ?int32(Challenge), atom_to_list(Node)]).
+send_challenge(Socket, Node, Challenge, Version, GotFlags) ->
+ send_challenge(Socket, Node, Challenge, Version, GotFlags, ?COMPULSORY_DFLAGS).
-recv_challenge(Socket) ->
- case gen_tcp:recv(Socket, 0) of
- {ok,[$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
+send_challenge(Socket, Node, Challenge, ?DIST_VER_LOW, _GotFlags, Flags) ->
+ {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
+ ?to_port(Socket, [$n,<<?DIST_VER_LOW:16>>,<<Flags:32>>,
+ <<Challenge:32>>, atom_to_list(Node)]);
+send_challenge(Socket, Node, Challenge, ?DIST_VER_HIGH, GotFlags, Flags) ->
+ true = (GotFlags band ?DFLAG_HANDSHAKE_23) =/= 0,
+ {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
+ NodeName = atom_to_list(Node),
+ Nlen = length(NodeName),
+ Creation = erts_internal:get_creation(),
+ ?to_port(Socket, [$N, <<(Flags bor ?DFLAG_HANDSHAKE_23):64>>,
+ <<Challenge:32>>, <<Creation:32>>,
+ <<Nlen:16>>, NodeName
+ ]).
+
+recv_challenge(Socket, OurVersion) ->
+ {ok, Msg} = gen_tcp:recv(Socket, 0),
+ %%io:format("recv_challenge Msg=~p\n", [Msg]),
+ case {OurVersion, Msg} of
+ {?DIST_VER_LOW,
+ [$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
Flags = ?u32(Fl1,Fl2,Fl3,Fl4),
- Type = case Flags band ?DFLAG_PUBLISHED of
- 0 ->
- hidden;
- _ ->
- normal
- end,
+ true = (Flags band ?COMPULSORY_DFLAGS) =:= ?COMPULSORY_DFLAGS,
Node =list_to_atom(Ns),
- Version = ?u16(V1,V0),
+ ?DIST_VER_LOW = ?u16(V1,V0),
+ Challenge = ?u32(CA3,CA2,CA1,CA0),
+ {Node,$n,Challenge};
+
+ {?DIST_VER_HIGH,
+ [$N, F7,F6,F5,F4,F3,F2,F1,F0, CA3,CA2,CA1,CA0,
+ Cr3,Cr2,Cr1,Cr0, NL1,NL0 | Ns]} ->
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ true = (Flags band ?COMPULSORY_DFLAGS) =:= ?COMPULSORY_DFLAGS,
+ <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
+ true = (Creation =/= 0),
+ <<NameLen:16>> = <<NL1,NL0>>,
+ NameLen = length(Ns),
+ Node = list_to_atom(Ns),
Challenge = ?u32(CA3,CA2,CA1,CA0),
- {Type,Node,Version,Challenge};
+ {Node,$N,Challenge};
+
_ ->
?shutdown(no_node)
end.
+send_complement(Socket, SentNameMsg, ChallengeMsg, OurVersion) ->
+ case {SentNameMsg,ChallengeMsg} of
+ {$n,$N} ->
+ FlagsHigh = our_flags(?COMPULSORY_DFLAGS, OurVersion) bsr 32,
+ ?to_port(Socket, [$c,
+ <<FlagsHigh:32>>,
+ ?int32(erts_internal:get_creation())]);
+ {Same,Same} ->
+ ok
+ end.
+
+recv_complement(Socket, $n, OurVersion) when OurVersion > ?DIST_VER_LOW ->
+ case gen_tcp:recv(Socket, 0) of
+ {ok,[$c,Cr3,Cr2,Cr1,Cr0]} ->
+ Creation = ?u32(Cr3,Cr2,Cr1,Cr0),
+ true = (Creation =/= 0);
+ Err ->
+ {error,Err}
+ end;
+recv_complement(_, _ , _) ->
+ ok.
+
send_challenge_reply(Socket, Challenge, Digest) ->
?to_port(Socket, [$r,?int32(Challenge),Digest]).
recv_challenge_reply(Socket, ChallengeA, Cookie) ->
case gen_tcp:recv(Socket, 0) of
- {ok,[$r,CB3,CB2,CB1,CB0 | SumB]} when length(SumB) == 16 ->
+ {ok,[$r,CB3,CB2,CB1,CB0 | SumB]=Data} when length(SumB) == 16 ->
SumA = gen_digest(ChallengeA, Cookie),
ChallengeB = ?u32(CB3,CB2,CB1,CB0),
if SumB == SumA ->
- ChallengeB;
+ {ok,ChallengeB};
true ->
- ?shutdown(bad_challenge_reply)
+ {error,Data}
end;
- _ ->
- ?shutdown(no_node)
+ Err ->
+ {error,Err}
end.
send_challenge_ack(Socket, Digest) ->
@@ -539,20 +614,34 @@ recv_challenge_ack(Socket, ChallengeB, CookieA) ->
ok;
true ->
?shutdown(bad_challenge_ack)
- end;
- _ ->
- ?shutdown(bad_challenge_ack)
+ end
end.
-send_name(Socket, MyNode0, Version) ->
- send_name(Socket, MyNode0, Version, ?COMPULSORY_DFLAGS).
-send_name(Socket, MyNode0, Version, Flags) ->
+send_name(Socket, MyNode0, OurVersion, AssumedVersion) ->
+ send_name(Socket, MyNode0, OurVersion, AssumedVersion, ?COMPULSORY_DFLAGS).
+
+send_name(Socket, MyNode0, OurVersion, AssumedVersion, Flags) ->
MyNode = atom_to_list(MyNode0),
- ok = ?to_port(Socket, [<<$n,Version:16,Flags:32>>|MyNode]).
+ if (OurVersion =:= ?DIST_VER_LOW) or
+ (AssumedVersion =:= ?DIST_VER_LOW) ->
+ OurFlags = our_flags(Flags,OurVersion),
+ ok = ?to_port(Socket, [<<$n,OurVersion:16,OurFlags:32>>|MyNode]),
+ $n;
+
+ (OurVersion > ?DIST_VER_LOW) and
+ (AssumedVersion > ?DIST_VER_LOW) ->
+ Creation = erts_internal:get_creation(),
+ NameLen = length(MyNode),
+ ok = ?to_port(Socket, [<<$N, (Flags bor ?DFLAG_HANDSHAKE_23):64,
+ Creation:32,NameLen:16>>|MyNode]),
+ $N
+ end.
+
+our_flags(Flags, ?DIST_VER_LOW) ->
+ Flags;
+our_flags(Flags, OurVersion) when OurVersion > ?DIST_VER_LOW ->
+ Flags bor ?DFLAG_HANDSHAKE_23.
-%%
-%% recv_name is common for both old and new handshake.
-%%
recv_name(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok,Data} ->
@@ -561,19 +650,18 @@ recv_name(Socket) ->
?shutdown({no_node,Res})
end.
-get_name([$m,VersionA,VersionB,_Ip1,_Ip2,_Ip3,_Ip4|OtherNode]) ->
- {normal, list_to_atom(OtherNode), ?u16(VersionA,VersionB)};
-get_name([$h,VersionA,VersionB,_Ip1,_Ip2,_Ip3,_Ip4|OtherNode]) ->
- {hidden, list_to_atom(OtherNode), ?u16(VersionA,VersionB)};
-get_name([$n,VersionA, VersionB, Flag1, Flag2, Flag3, Flag4 | OtherNode]) ->
- Type = case ?u32(Flag1, Flag2, Flag3, Flag4) band ?DFLAG_PUBLISHED of
- 0 ->
- hidden;
- _ ->
- normal
- end,
- {Type, list_to_atom(OtherNode),
- ?u16(VersionA,VersionB)};
+get_name([$n, V1,V0, F3,F2,F1,F0 | OtherNode]) ->
+ <<Version:16>> = <<V1,V0>>,
+ 5 = Version,
+ <<Flags:32>> = <<F3,F2,F1,F0>>,
+ {list_to_atom(OtherNode), $n, Flags};
+get_name([$N, F7,F6,F5,F4,F3,F2,F1,F0,
+ _C3,_C2,_C1,_C0, NLen1,NLen2 | OtherNode]) ->
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ true = (Flags band ?DFLAG_HANDSHAKE_23) =/= 0,
+ <<NameLen:16>> = <<NLen1,NLen2>>,
+ NameLen = length(OtherNode),
+ {list_to_atom(OtherNode), $N, Flags};
get_name(Data) ->
?shutdown(Data).
@@ -620,6 +708,13 @@ wait_for_reg_reply(Socket, SoFar) ->
receive
{tcp, Socket, Data0} ->
case SoFar ++ Data0 of
+ [$v, Result, A, B, C, D] ->
+ case Result of
+ 0 ->
+ {alive, Socket, ?u32(A, B, C, D)};
+ _ ->
+ {error, duplicate_name}
+ end;
[$y, Result, A, B] ->
case Result of
0 ->
@@ -640,7 +735,7 @@ wait_for_reg_reply(Socket, SoFar) ->
end.
-register(NodeName, ListenSocket, VLow, VHigh) ->
+register_node(NodeName, ListenSocket, VLow, VHigh) ->
{ok,{_,TcpPort}} = inet:sockname(ListenSocket),
case do_register_node(NodeName, TcpPort, VLow, VHigh) of
{alive, Socket, _Creation} ->
diff --git a/lib/kernel/test/erpc_SUITE.erl b/lib/kernel/test/erpc_SUITE.erl
new file mode 100644
index 0000000000..3106bba879
--- /dev/null
+++ b/lib/kernel/test/erpc_SUITE.erl
@@ -0,0 +1,796 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. 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.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erpc_SUITE).
+
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2]).
+-export([call/1, call_reqtmo/1, call_against_old_node/1, cast/1,
+ send_request/1, send_request_receive_reqtmo/1,
+ send_request_wait_reqtmo/1,
+ send_request_check_reqtmo/1,
+ send_request_against_old_node/1,
+ multicall/1, multicall_reqtmo/1,
+ timeout_limit/1]).
+-export([init_per_testcase/2, end_per_testcase/2]).
+
+-export([call_func1/1, call_func2/1, call_func4/4]).
+
+-export([f/0, f2/0]).
+
+-include_lib("common_test/include/ct.hrl").
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,2}}].
+
+all() ->
+ [call, call_reqtmo, call_against_old_node, cast,
+ send_request, send_request_receive_reqtmo,
+ send_request_wait_reqtmo, send_request_check_reqtmo,
+ send_request_against_old_node,
+ multicall, multicall_reqtmo,
+ timeout_limit].
+
+groups() ->
+ [].
+
+init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
+ [{testcase, Func}|Config].
+
+end_per_testcase(_Func, _Config) ->
+ ok.
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+call(Config) when is_list(Config) ->
+ call_test(Config).
+
+call_test(Config) ->
+ call_test(node(), 10000),
+ call_test(node(), infinity),
+ try
+ erpc:call(node(), erlang, node, [], 0),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} ->
+ ok
+ end,
+ {ok, Node} = start_node(Config),
+ call_test(Node, 10000),
+ call_test(Node, infinity),
+ try
+ erpc:call(Node, erlang, node, [], 0),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, halt, []),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, node, []),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} ->
+ ok
+ end.
+
+call_test(Node, Timeout) ->
+ io:format("call_test(~p, ~p)~n", [Node, Timeout]),
+ Node = erpc:call(Node, erlang, node, [], Timeout),
+ try
+ erpc:call(Node, erlang, error, [oops|invalid], Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, error, [oops|invalid], Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, error, [oops], Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{exception, oops, [{erlang,error,[oops],_}]} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, exit, [oops], Timeout),
+ ct:fail(unexpected)
+ catch
+ exit:{exception, oops} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, throw, [oops], Timeout),
+ ct:fail(unexpected)
+ catch
+ throw:oops ->
+ ok
+ end,
+ case {node() == Node, Timeout == infinity} of
+ {true, true} ->
+ %% This would kill the test since local calls
+ %% without timeout is optimized to execute in
+ %% calling process itself...
+ ok;
+ _ ->
+ ExitSignal = fun () ->
+ exit(self(), oops),
+ receive after infinity -> ok end
+ end,
+ try
+ erpc:call(Node, erlang, apply, [ExitSignal, []], Timeout),
+ ct:fail(unexpected)
+ catch
+ exit:{signal, oops} ->
+ ok
+ end
+ end,
+ try
+ erpc:call(Node, ?MODULE, call_func1, [boom], Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{exception,
+ {exception,
+ boom,
+ [{?MODULE, call_func3, A2, _},
+ {?MODULE, call_func2, 1, _}]},
+ [{erpc, call, A1, _},
+ {?MODULE, call_func1, 1, _}]}
+ when ((A1 == 5)
+ orelse (A1 == [Node, ?MODULE, call_func2, [boom]]))
+ andalso ((A2 == 1)
+ orelse (A2 == [boom])) ->
+ ok
+ end,
+ try
+ call_func4(Node, node(), 10, Timeout),
+ ct:fail(unexpected)
+ catch
+ error:Error1 ->
+%%% io:format("Error1=~p~n", [Error1]),
+ check_call_func4_error(Error1, 10)
+ end,
+ try
+ call_func4(node(), Node, 5, Timeout),
+ ct:fail(unexpected)
+ catch
+ error:Error2 ->
+%%% io:format("Error2=~p~n", [Error2]),
+ check_call_func4_error(Error2, 5)
+ end,
+ ok.
+
+check_call_func4_error({exception,
+ badarg,
+ [{?MODULE, call_func5, _, _},
+ {?MODULE, call_func4, _, _}]},
+ 0) ->
+ ok;
+check_call_func4_error({exception,
+ Exception,
+ [{erpc, call, _, _},
+ {?MODULE, call_func5, _, _},
+ {?MODULE, call_func4, _, _}]},
+ N) ->
+ check_call_func4_error(Exception, N-1).
+
+call_func1(X) ->
+ erpc:call(node(), ?MODULE, call_func2, [X]),
+ ok.
+
+call_func2(X) ->
+ call_func3(X),
+ ok.
+
+call_func3(X) ->
+ erlang:error(X, [X]).
+
+call_func4(A, B, N, T) ->
+ call_func5(A, B, N, T),
+ ok.
+
+call_func5(A, B, N, T) when N >= 0 ->
+ erpc:call(A, ?MODULE, call_func4, [B, A, N-1, T], T),
+ ok;
+call_func5(_A, _B, _N, _T) ->
+ erlang:error(badarg).
+
+call_against_old_node(Config) ->
+ case start_22_node(Config) of
+ {ok, Node22} ->
+ try
+ erpc:call(Node22, erlang, node, []),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, notsup} ->
+ ok
+ end,
+ stop_node(Node22),
+ ok;
+ _ ->
+ {skipped, "No OTP 22 available"}
+ end.
+
+call_reqtmo(Config) when is_list(Config) ->
+ Fun = fun (Node, SendMe, Timeout) ->
+ try
+ erpc:call(Node, erlang, send,
+ [self(), SendMe], Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} -> ok
+ end
+ end,
+ reqtmo_test(Config, Fun).
+
+reqtmo_test(Config, Test) ->
+ %% Tests that we time out in time also when the request itself
+ %% does not get through. A typical issue we have had
+ %% in the past, is that the timeout has not triggered until
+ %% the request has gotten through...
+
+ Timeout = 500,
+ WaitBlock = 100,
+ BlockTime = 1000,
+
+ {ok, Node} = start_node(Config),
+
+ erpc:call(Node, erts_debug, set_internal_state, [available_internal_state,
+ true]),
+
+ SendMe = make_ref(),
+
+ erpc:cast(Node, erts_debug, set_internal_state, [block, BlockTime]),
+ receive after WaitBlock -> ok end,
+
+ Start = erlang:monotonic_time(),
+
+ Test(Node, SendMe, Timeout),
+
+ Stop = erlang:monotonic_time(),
+ Time = erlang:convert_time_unit(Stop-Start, native, millisecond),
+ io:format("Actual time: ~p ms~n", [Time]),
+ true = Time >= Timeout,
+ true = Time =< Timeout + 200,
+
+ receive SendMe -> ok end,
+
+ receive UnexpectedMsg -> ct:fail({unexpected_message, UnexpectedMsg})
+ after 0 -> ok
+ end,
+
+ stop_node(Node),
+
+ {comment,
+ "Timeout = " ++ integer_to_list(Timeout)
+ ++ " Actual = " ++ integer_to_list(Time)}.
+
+cast(Config) when is_list(Config) ->
+ try
+ erpc:cast(node, erlang, send, [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:cast(node(), "erlang", send, [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:cast(node(), erlang, make_ref(), [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:cast(node(), erlang, send, [hej|hopp]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ Me = self(),
+ Ok1 = make_ref(),
+ ok = erpc:cast(node(), erlang, send, [Me, Ok1]),
+ receive
+ Ok1 -> ok
+ end,
+ {ok, Node} = start_node(Config),
+ Ok2 = make_ref(),
+ ok = erpc:cast(Node, erlang, send, [Me, Ok2]),
+ receive
+ Ok2 -> ok
+ end,
+ stop_node(Node),
+ case start_22_node(Config) of
+ {ok, Node22} ->
+ ok = erpc:cast(Node, erlang, send, [Me, wont_reach_me]),
+ receive
+ Msg -> ct:fail({unexpected_message, Msg})
+ after
+ 2000 -> ok
+ end,
+ stop_node(Node22),
+ {comment, "Tested against OTP 22 as well"};
+ _ ->
+ {comment, "No tested against OTP 22"}
+ end.
+
+send_request(Config) when is_list(Config) ->
+ %% Note: First part of nodename sets response delay in seconds.
+ PA = filename:dirname(code:which(?MODULE)),
+ NodeArgs = [{args,"-pa "++ PA}],
+ {ok,Node1} = test_server:start_node('1_erpc_SUITE_call', slave, NodeArgs),
+ {ok,Node2} = test_server:start_node('10_erpc_SUITE_call', slave, NodeArgs),
+ {ok,Node3} = test_server:start_node('20_erpc_SUITE_call', slave, NodeArgs),
+ ReqId1 = erpc:send_request(Node1, ?MODULE, f, []),
+ ReqId2 = erpc:send_request(Node2, ?MODULE, f, []),
+ ReqId3 = erpc:send_request(Node3, ?MODULE, f, []),
+ ReqId4 = erpc:send_request(Node1, erlang, error, [bang]),
+ ReqId5 = erpc:send_request(Node1, ?MODULE, f, []),
+
+ try
+ erpc:receive_response(ReqId4, 10)
+ catch
+ error:{exception, bang, [{erlang,error,[bang],_}]} ->
+ ok
+ end,
+ try
+ erpc:receive_response(ReqId5, 10)
+ catch
+ error:{erpc, timeout} ->
+ ok
+ end,
+
+ %% Test fast timeouts.
+ no_response = erpc:wait_response(ReqId2),
+ no_response = erpc:wait_response(ReqId2, 10),
+
+ %% Let Node1 finish its work before yielding.
+ ct:sleep({seconds,2}),
+ {hej,_,Node1} = erpc:receive_response(ReqId1),
+
+ %% Wait for the Node2 and Node3.
+ {response,{hej,_,Node2}} = erpc:wait_response(ReqId2, infinity),
+ {hej,_,Node3} = erpc:receive_response(ReqId3),
+
+ try
+ erpc:receive_response(ReqId5, 10),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+
+ receive
+ Msg0 -> ct:fail(Msg0)
+ after 0 -> ok
+ end,
+
+ {ok, Node4} = start_node(Config),
+
+ ReqId6 = erpc:send_request(Node4, erlang, node, []),
+
+ no_response = erpc:check_response({response, Node1}, ReqId6),
+ no_response = erpc:check_response(ReqId6, ReqId6),
+ receive
+ Msg1 ->
+ {response, Node4} = erpc:check_response(Msg1, ReqId6)
+ end,
+
+ ReqId7 = erpc:send_request(Node4, erlang, halt, []),
+ try
+ erpc:receive_response(ReqId7),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} -> ok
+ end,
+
+ ReqId8 = erpc:send_request(Node4, erlang, node, []),
+ receive
+ Msg2 ->
+ try
+ erpc:check_response(Msg2, ReqId8),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} ->
+ ok
+ end
+ end,
+
+ case start_22_node(Config) of
+ {ok, Node5} ->
+ ReqId9 = erpc:send_request(Node5, erlang, node, []),
+ try
+ erpc:receive_response(ReqId9),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, notsup} -> ok
+ end,
+
+ stop_node(Node5),
+ ok;
+ _ ->
+ {comment, "No test against OTP 22 node performed"}
+ end.
+
+send_request_receive_reqtmo(Config) when is_list(Config) ->
+ Fun = fun (Node, SendMe, Timeout) ->
+ RID = erpc:send_request(Node, erlang, send,
+ [self(), SendMe]),
+ try
+ erpc:receive_response(RID, Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} -> ok
+ end
+ end,
+ reqtmo_test(Config, Fun).
+
+send_request_wait_reqtmo(Config) when is_list(Config) ->
+ Fun = fun (Node, SendMe, Timeout) ->
+ RID = erpc:send_request(Node, erlang, send,
+ [self(), SendMe]),
+ no_response = erpc:wait_response(RID, 0),
+ no_response = erpc:wait_response(RID, Timeout),
+ %% Cleanup...
+ try
+ erpc:receive_response(RID, 0),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} -> ok
+ end
+ end,
+ reqtmo_test(Config, Fun).
+
+send_request_check_reqtmo(Config) when is_list(Config) ->
+ Fun = fun (Node, SendMe, Timeout) ->
+ RID = erpc:send_request(Node, erlang, send,
+ [self(), SendMe]),
+ receive Msg -> erpc:check_response(Msg, RID)
+ after Timeout -> ok
+ end,
+ %% Cleanup...
+ try
+ erpc:receive_response(RID, 0),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} -> ok
+ end
+ end,
+ reqtmo_test(Config, Fun).
+
+send_request_against_old_node(Config) when is_list(Config) ->
+ case start_22_node(Config) of
+ {ok, Node22} ->
+ RID1 = erpc:send_request(Node22, erlang, node, []),
+ RID2 = erpc:send_request(Node22, erlang, node, []),
+ RID3 = erpc:send_request(Node22, erlang, node, []),
+ try
+ erpc:receive_response(RID1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, notsup} ->
+ ok
+ end,
+ try
+ erpc:wait_response(RID2, infinity),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, notsup} ->
+ ok
+ end,
+ try
+ receive
+ Msg ->
+ erpc:check_response(Msg, RID3),
+ ct:fail(unexpected)
+ end
+ catch
+ error:{erpc, notsup} ->
+ ok
+ end,
+ stop_node(Node22),
+ ok;
+ _ ->
+ {skipped, "No OTP 22 available"}
+ end.
+
+multicall(Config) ->
+ {ok, Node1} = start_node(Config),
+ {ok, Node2} = start_node(Config),
+ {Node3, Node3Res} = case start_22_node(Config) of
+ {ok, N3} ->
+ {N3, {error, {erpc, notsup}}};
+ _ ->
+ {ok, N3} = start_node(Config),
+ stop_node(N3),
+ {N3, {error, {erpc, noconnection}}}
+ end,
+ {ok, Node4} = start_node(Config),
+ {ok, Node5} = start_node(Config),
+ stop_node(Node2),
+
+ ThisNode = node(),
+ Nodes = [ThisNode, Node1, Node2, Node3, Node4, Node5],
+
+ [{ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5}]
+ = erpc:multicall(Nodes, erlang, node, []),
+
+ [BlingError,
+ BlingError,
+ {error, {erpc, noconnection}},
+ Node3Res,
+ BlingError,
+ BlingError]
+ = erpc:multicall(Nodes, ?MODULE, call_func2, [bling]),
+
+ {error, {exception,
+ bling,
+ [{?MODULE, call_func3, A, _},
+ {?MODULE, call_func2, 1, _}]}} = BlingError,
+ true = (A == 1) orelse (A == [bling]),
+
+ [{error, {exception, blong, [{erlang, error, [blong], _}]}},
+ {error, {exception, blong, [{erlang, error, [blong], _}]}},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {error, {exception, blong, [{erlang, error, [blong], _}]}},
+ {error, {exception, blong, [{erlang, error, [blong], _}]}}]
+ = erpc:multicall(Nodes, erlang, error, [blong]),
+
+ SlowNode4 = fun () ->
+ case node() of
+ Node4 ->
+ receive after 1000 -> ok end,
+ slow;
+ ThisNode ->
+ throw(fast);
+ _ ->
+ fast
+ end
+ end,
+
+ Start1 = erlang:monotonic_time(),
+ [{throw, fast},
+ {ok, fast},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {error, {erpc, timeout}},
+ {ok, fast}]
+ = erpc:multicall(Nodes, erlang, apply, [SlowNode4, []], 500),
+
+ End1 = erlang:monotonic_time(),
+
+ Time1 = erlang:convert_time_unit(End1-Start1, native, millisecond),
+ io:format("Time1 = ~p~n",[Time1]),
+ true = Time1 >= 500,
+ true = Time1 =< 1000,
+
+ SlowThisNode = fun () ->
+ case node() of
+ ThisNode ->
+ receive after 1000 -> ok end,
+ slow;
+ Node4 ->
+ throw(fast);
+ Node5 ->
+ exit(fast);
+ _ ->
+ fast
+ end
+ end,
+
+ Start2 = erlang:monotonic_time(),
+ [{error, {erpc, timeout}},
+ {ok, fast},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {throw, fast},
+ {exit, {exception, fast}}]
+ = erpc:multicall(Nodes, erlang, apply, [SlowThisNode, []], 500),
+
+ End2 = erlang:monotonic_time(),
+
+ Time2 = erlang:convert_time_unit(End2-Start2, native, millisecond),
+ io:format("Time2 = ~p~n",[Time2]),
+ true = Time2 >= 500,
+ true = Time2 =< 1000,
+
+ %% We should not get any stray messages due to timed out operations...
+ receive Msg -> ct:fail({unexpected, Msg})
+ after 1000 -> ok
+ end,
+
+ [{error, {erpc, noconnection}},
+ Node3Res,
+ {error, {erpc, noconnection}},
+ {error, {erpc, noconnection}}]
+ = erpc:multicall([Node2, Node3, Node4, Node5], erlang, halt, []),
+
+ stop_node(Node3),
+ case Node3Res of
+ {error, {erpc, notsup}} ->
+ {comment, "Tested against an OTP 22 node as well"};
+ _ ->
+ {comment, "No OTP 22 node available; i.e., only testing against current release"}
+ end.
+
+multicall_reqtmo(Config) when is_list(Config) ->
+ {ok, QuickNode1} = start_node(Config),
+ {ok, QuickNode2} = start_node(Config),
+ Fun = fun (Node, SendMe, Timeout) ->
+ Me = self(),
+ SlowSend = fun () ->
+ if node() == Node ->
+ Me ! SendMe,
+ done;
+ true ->
+ done
+ end
+ end,
+ [{ok, done},{error,{erpc,timeout}},{ok, done}]
+ = erpc:multicall([QuickNode1, Node, QuickNode2],
+ erlang, apply, [SlowSend, []],
+ Timeout)
+ end,
+ Res = reqtmo_test(Config, Fun),
+ stop_node(QuickNode1),
+ stop_node(QuickNode2),
+ Res.
+
+timeout_limit(Config) when is_list(Config) ->
+ Node = node(),
+ MaxTmo = (1 bsl 32) - 1,
+ erlang:send_after(100, self(), dummy_message),
+ try
+ receive
+ M ->
+ M
+ after MaxTmo + 1 ->
+ ok
+ end,
+ ct:fail("The ?MAX_INT_TIMEOUT define in erpc.erl needs "
+ "to be updated to reflect max timeout value "
+ "in a receive/after...")
+ catch
+ error:timeout_value ->
+ ok
+ end,
+ Node = erpc:call(Node, erlang, node, [], MaxTmo),
+ try
+ erpc:call(node(), erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ [{ok,Node}] = erpc:multicall([Node], erlang, node, [], MaxTmo),
+ try
+ erpc:multicall([Node], erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ ReqId1 = erpc:send_request(Node, erlang, node, []),
+ Node = erpc:receive_response(ReqId1, MaxTmo),
+ ReqId2 = erpc:send_request(Node, erlang, node, []),
+ try
+ erpc:receive_response(ReqId2, MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ ReqId3 = erpc:send_request(Node, erlang, node, []),
+ Node = erpc:receive_response(ReqId3, MaxTmo),
+ ReqId4 = erpc:send_request(Node, erlang, node, []),
+ try
+ erpc:receive_response(ReqId4, MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ ok.
+
+
+%%%
+%%% Utility functions.
+%%%
+
+start_node(Config) ->
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-" ++ integer_to_list(erlang:system_time(second))
+ ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))),
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{args, "-pa " ++ Pa}]).
+
+start_22_node(Config) ->
+ Rel = "22_latest",
+ case test_server:is_release_available(Rel) of
+ false ->
+ notsup;
+ true ->
+ Cookie = atom_to_list(erlang:get_cookie()),
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-" ++ integer_to_list(erlang:system_time(second))
+ ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))),
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name,
+ peer,
+ [{args, "-pa " ++ Pa ++ " -setcookie "++Cookie},
+ {erl, [{release, Rel}]}])
+ end.
+
+stop_node(Node) ->
+ test_server:stop_node(Node).
+
+flush(L) ->
+ receive
+ M ->
+ flush([M|L])
+ after 0 ->
+ L
+ end.
+
+t() ->
+ [N | _] = string:tokens(atom_to_list(node()), "_"),
+ 1000*list_to_integer(N).
+
+f() ->
+ timer:sleep(T=t()),
+ spawn(?MODULE, f2, []),
+ {hej,T,node()}.
+
+f2() ->
+ timer:sleep(500),
+ halt().
diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl
index 1c1b35abc1..f432eec708 100644
--- a/lib/kernel/test/file_SUITE.erl
+++ b/lib/kernel/test/file_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. 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.
@@ -58,6 +58,8 @@
-export([ file_info_basic_file/1, file_info_basic_directory/1,
file_info_bad/1, file_info_times/1, file_write_file_info/1,
file_wfi_helpers/1]).
+-export([ file_handle_info_basic_file/1, file_handle_info_basic_directory/1,
+ file_handle_info_times/1]).
-export([rename/1, access/1, truncate/1, datasync/1, sync/1,
read_write/1, pread_write/1, append/1, exclusive/1]).
-export([ e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
@@ -155,7 +157,10 @@ groups() ->
{pos, [], [pos1, pos2, pos3]},
{file_info, [],
[file_info_basic_file, file_info_basic_directory,
- file_info_bad, file_info_times, file_write_file_info,
+ file_info_bad, file_info_times,
+ file_handle_info_basic_file, file_handle_info_basic_directory,
+ file_handle_info_times,
+ file_write_file_info,
file_wfi_helpers]},
{consult, [], [consult1, path_consult]},
{eval, [], [eval1, path_eval]},
@@ -275,11 +280,11 @@ mini_server(Parent) ->
Parent ! {io_request,From,To,{put_chars,Data}},
From ! {io_reply, To, ok},
mini_server(Parent);
- {io_request,From,To,{get_chars,'',N}} ->
+ {io_request,From,To,{get_chars,_Encoding,'',N}} ->
Parent ! {io_request,From,To,{get_chars,'',N}},
From ! {io_reply, To, {ok, lists:duplicate(N,$a)}},
mini_server(Parent);
- {io_request,From,To,{get_line,''}} ->
+ {io_request,From,To,{get_line,_Encoding,''}} ->
Parent ! {io_request,From,To,{get_line,''}},
From ! {io_reply, To, {ok, "hej\n"}},
mini_server(Parent)
@@ -989,6 +994,14 @@ new_modes(Config) when is_list(Config) ->
ok
end,
+ % open directory
+ {ok, Fd9} = ?FILE_MODULE:open(NewDir, [directory]),
+ ok = ?FILE_MODULE:close(Fd9),
+
+ % open raw directory
+ {ok, Fd10} = ?FILE_MODULE:open(NewDir, [raw, directory]),
+ ok = ?FILE_MODULE:close(Fd10),
+
[] = flush(),
ok.
@@ -1238,6 +1251,9 @@ open_errors(Config) when is_list(Config) ->
{error, E4} = ?FILE_MODULE:open(DataDirSlash, [write]),
{eisdir,eisdir,eisdir,eisdir} = {E1,E2,E3,E4},
+ Real = filename:join(DataDir, "realmen.html"),
+ {error, enotdir} = ?FILE_MODULE:open(Real, [directory]),
+
[] = flush(),
ok.
@@ -1408,7 +1424,8 @@ file_info_basic_directory(Config) when is_list(Config) ->
{win32, _} ->
test_directory("/", read_write),
test_directory("c:/", read_write),
- test_directory("c:\\", read_write);
+ test_directory("c:\\", read_write),
+ test_directory("\\\\localhost\\c$", read_write);
_ ->
test_directory("/", read)
end,
@@ -1540,6 +1557,180 @@ filter_atime(Atime, Config) ->
Atime
end.
+%% Test read_file_info on I/O devices.
+
+file_handle_info_basic_file(Config) when is_list(Config) ->
+ RootDir = proplists:get_value(priv_dir, Config),
+
+ %% Create a short file.
+ Name = filename:join(RootDir,
+ atom_to_list(?MODULE)
+ ++"_basic_test.fil"),
+ {ok,Fd1} = ?FILE_MODULE:open(Name, write),
+ io:put_chars(Fd1, "foo bar"),
+ ok = ?FILE_MODULE:close(Fd1),
+
+ %% Test that the file has the expected attributes.
+ %% The times are tricky, so we will save them to a separate test case.
+
+ {ok, Fd} = ?FILE_MODULE:open(Name, read),
+ {ok,FileInfo} = ?FILE_MODULE:read_file_info(Fd),
+ ok = ?FILE_MODULE:close(Fd),
+
+ {ok, FdRaw} = ?FILE_MODULE:open(Name, [read, raw]),
+ {ok,FileInfoRaw} = ?FILE_MODULE:read_file_info(FdRaw),
+ ok = ?FILE_MODULE:close(FdRaw),
+
+ #file_info{size=Size,type=Type,access=Access,
+ atime=AccessTime,mtime=ModifyTime} = FileInfo = FileInfoRaw,
+ io:format("Access ~p, Modify ~p", [AccessTime, ModifyTime]),
+ Size = 7,
+ Type = regular,
+ read_write = Access,
+ true = abs(time_dist(filter_atime(AccessTime, Config),
+ filter_atime(ModifyTime,
+ Config))) < 5,
+ all_integers(tuple_to_list(AccessTime) ++ tuple_to_list(ModifyTime)),
+
+ [] = flush(),
+ ok.
+
+file_handle_info_basic_directory(Config) when is_list(Config) ->
+ %% Note: filename:join/1 removes any trailing slash,
+ %% which is essential for ?FILE_MODULE:file_info/1 to work on
+ %% platforms such as Windows95.
+ RootDir = filename:join([proplists:get_value(priv_dir, Config)]),
+
+ %% Test that the RootDir directory has the expected attributes.
+ test_directory_handle(RootDir, read_write),
+
+ %% Note that on Windows file systems,
+ %% "/" or "c:/" are *NOT* directories.
+ %% Therefore, test that ?FILE_MODULE:file_info/1 behaves as if they were
+ %% directories.
+ case os:type() of
+ {win32, _} ->
+ test_directory_handle("/", read_write),
+ test_directory_handle("c:/", read_write),
+ test_directory_handle("c:\\", read_write),
+ test_directory_handle("\\\\localhost\\c$", read_write);
+ _ ->
+ test_directory_handle("/", read)
+ end,
+ ok.
+
+test_directory_handle(Name, ExpectedAccess) ->
+ {ok, DirFd} = file:open(Name, [read, directory]),
+ try
+ {ok,FileInfo} = ?FILE_MODULE:read_file_info(DirFd),
+ {ok,FileInfo} = ?FILE_MODULE:read_file_info(DirFd, [raw]),
+ #file_info{size=Size,type=Type,access=Access,
+ atime=AccessTime,mtime=ModifyTime} = FileInfo,
+ io:format("Testing directory ~s", [Name]),
+ io:format("Directory size is ~p", [Size]),
+ io:format("Access ~p", [Access]),
+ io:format("Access time ~p; Modify time~p",
+ [AccessTime, ModifyTime]),
+ Type = directory,
+ Access = ExpectedAccess,
+ all_integers(tuple_to_list(AccessTime) ++ tuple_to_list(ModifyTime)),
+ [] = flush(),
+ ok
+ after
+ file:close(DirFd)
+ end.
+
+%% Test that the file times behave as they should.
+
+file_handle_info_times(Config) when is_list(Config) ->
+ %% We have to try this twice, since if the test runs across the change
+ %% of a month the time diff calculations will fail. But it won't happen
+ %% if you run it twice in succession.
+ test_server:m_out_of_n(
+ 1,2,
+ fun() -> file_handle_info_int(Config) end),
+ ok.
+
+file_handle_info_int(Config) ->
+ %% Note: filename:join/1 removes any trailing slash,
+ %% which is essential for ?FILE_MODULE:file_info/1 to work on
+ %% platforms such as Windows95.
+
+ RootDir = filename:join([proplists:get_value(priv_dir, Config)]),
+ io:format("RootDir = ~p", [RootDir]),
+
+ Name = filename:join(RootDir,
+ atom_to_list(?MODULE)
+ ++"_file_info.fil"),
+ {ok,Fd1} = ?FILE_MODULE:open(Name, write),
+ io:put_chars(Fd1,"foo"),
+ {ok,FileInfo1} = ?FILE_MODULE:read_file_info(Fd1),
+ ok = ?FILE_MODULE:close(Fd1),
+
+ {ok,Fd1Raw} = ?FILE_MODULE:open(Name, [read, raw]),
+ {ok,FileInfo1Raw} = ?FILE_MODULE:read_file_info(Fd1Raw),
+ ok = ?FILE_MODULE:close(Fd1Raw),
+
+ %% We assert that everything but the size is the same, on some OSs the
+ %% size may not have been flushed to disc and we do not want to do a
+ %% sync to force it.
+ FileInfo1Raw = FileInfo1#file_info{ size = FileInfo1Raw#file_info.size },
+
+ #file_info{type=regular,atime=AccTime1,mtime=ModTime1} = FileInfo1,
+
+ Now = erlang:localtime(), %???
+ io:format("Now ~p",[Now]),
+ io:format("Open file Acc ~p Mod ~p",[AccTime1,ModTime1]),
+ true = abs(time_dist(filter_atime(Now, Config),
+ filter_atime(AccTime1,
+ Config))) < 8,
+ true = abs(time_dist(Now,ModTime1)) < 8,
+
+ %% Sleep until we can be sure the seconds value has changed.
+ %% Note: FAT-based filesystem (like on Windows 95) have
+ %% a resolution of 2 seconds.
+ timer:sleep(2200),
+
+ %% close the file, and watch the modify date change
+
+ {ok,Fd2} = ?FILE_MODULE:open(Name, read),
+ {ok,FileInfo2} = ?FILE_MODULE:read_file_info(Fd2),
+ ok = ?FILE_MODULE:close(Fd2),
+
+ {ok,Fd2Raw} = ?FILE_MODULE:open(Name, [read, raw]),
+ {ok,FileInfo2Raw} = ?FILE_MODULE:read_file_info(Fd2Raw),
+ ok = ?FILE_MODULE:close(Fd2Raw),
+
+ #file_info{size=Size,type=regular,access=Access,
+ atime=AccTime2,mtime=ModTime2} = FileInfo2 = FileInfo2Raw,
+ io:format("Closed file Acc ~p Mod ~p",[AccTime2,ModTime2]),
+ true = time_dist(ModTime1,ModTime2) >= 0,
+
+ %% this file is supposed to be binary, so it'd better keep it's size
+ Size = 3,
+ Access = read_write,
+
+ %% Do some directory checking
+
+ {ok,Fd3} = ?FILE_MODULE:open(RootDir, [read, directory]),
+ {ok,FileInfo3} = ?FILE_MODULE:read_file_info(Fd3),
+ ok = ?FILE_MODULE:close(Fd3),
+
+ {ok,Fd3Raw} = ?FILE_MODULE:open(RootDir, [read, directory, raw]),
+ {ok,FileInfo3Raw} = ?FILE_MODULE:read_file_info(Fd3Raw),
+ ok = ?FILE_MODULE:close(Fd3Raw),
+
+ #file_info{size=DSize,type=directory,access=DAccess,
+ atime=AccTime3,mtime=ModTime3} = FileInfo3 = FileInfo3Raw,
+ %% this dir was modified only a few secs ago
+ io:format("Dir Acc ~p; Mod ~p; Now ~p", [AccTime3, ModTime3, Now]),
+ true = abs(time_dist(Now,ModTime3)) < 5,
+ DAccess = read_write,
+ io:format("Dir size is ~p",[DSize]),
+
+ [] = flush(),
+ ok.
+
%% Test the write_file_info/2 function.
file_write_file_info(Config) when is_list(Config) ->
@@ -2042,24 +2233,33 @@ allocate_and_assert(Fd, Offset, Length) ->
allocate_file_size(Config) when is_list(Config) ->
case os:type() of
{unix, darwin} ->
- PrivDir = proplists:get_value(priv_dir, Config),
- Allocate = filename:join(PrivDir, atom_to_list(?MODULE)++"_allocate_file"),
-
- {ok, Fd} = ?FILE_MODULE:open(Allocate, [write]),
- ok = ?FILE_MODULE:allocate(Fd, 0, 1024),
- {ok, 1024} = ?FILE_MODULE:position(Fd, eof),
- ok = ?FILE_MODULE:close(Fd),
-
- [] = flush(),
- ok;
+ do_allocate_file_size(Config);
{unix, linux} ->
- {skip, "file:allocate/3 on Linux does not change file size"};
+ do_allocate_file_size(Config);
{win32, _} ->
{skip, "Windows does not support file:allocate/3"};
_ ->
{skip, "Support for allocate/3 is spotty in our test platform at the moment."}
end.
+do_allocate_file_size(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Allocate = filename:join(PrivDir, atom_to_list(?MODULE)++"_allocate_file"),
+
+ {ok, Fd} = ?FILE_MODULE:open(Allocate, [write]),
+ Result =
+ case ?FILE_MODULE:allocate(Fd, 0, 1024) of
+ ok ->
+ {ok, 1024} = ?FILE_MODULE:position(Fd, eof),
+ ok;
+ {error, enotsup} ->
+ {skip, "Filesystem does not support file:allocate/3"}
+ end,
+ ok = ?FILE_MODULE:close(Fd),
+
+ [] = flush(),
+ Result.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
delete(Config) when is_list(Config) ->
diff --git a/lib/kernel/test/gen_tcp_api_SUITE.erl b/lib/kernel/test/gen_tcp_api_SUITE.erl
index 1be016444f..00c9dc5ed5 100644
--- a/lib/kernel/test/gen_tcp_api_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_api_SUITE.erl
@@ -594,10 +594,13 @@ unused_ip() ->
io:format("we = ~p, unused_ip = ~p~n", [Hent, IP]),
IP.
-unused_ip(_, _, _, 255) -> error;
+unused_ip(255, 255, 255, 255) -> error;
+unused_ip(255, B, C, D) -> unused_ip(1, B + 1, C, D);
+unused_ip(A, 255, C, D) -> unused_ip(A, 1, C + 1, D);
+unused_ip(A, B, 255, D) -> unused_ip(A, B, 1, D + 1);
unused_ip(A, B, C, D) ->
case inet:gethostbyaddr({A, B, C, D}) of
- {ok, _} -> unused_ip(A, B, C, D+1);
+ {ok, _} -> unused_ip(A + 1, B, C, D);
{error, _} -> {ok, {A, B, C, D}}
end.
diff --git a/lib/kernel/test/global_SUITE.erl b/lib/kernel/test/global_SUITE.erl
index 5bff9cc292..3c4654b44c 100644
--- a/lib/kernel/test/global_SUITE.erl
+++ b/lib/kernel/test/global_SUITE.erl
@@ -43,7 +43,7 @@
-export([global_load/3, lock_global/2, lock_global2/2]).
-export([]).
--export([mass_spawn/1]).
+-export([init_mass_spawn/1]).
-export([start_tracer/0, stop_tracer/0, get_trace/0]).
@@ -3887,7 +3887,7 @@ mass_death(Config) when is_list(Config) ->
io:format("Nodes: ~p~n", [Nodes]),
Ns = lists:seq(1, 40),
%% Start processes with globally registered names on the nodes
- {Pids,[]} = rpc:multicall(Nodes, ?MODULE, mass_spawn, [Ns]),
+ {Pids,[]} = rpc:multicall(Nodes, ?MODULE, init_mass_spawn, [Ns]),
io:format("Pids: ~p~n", [Pids]),
%% Wait...
ct:sleep(10000),
@@ -3924,6 +3924,11 @@ wait_mass_death(Nodes, OrigNames, Then, Config) ->
wait_mass_death(Nodes, OrigNames, Then, Config)
end.
+init_mass_spawn(N) ->
+ Pid = mass_spawn(N),
+ unlink(Pid),
+ Pid.
+
mass_spawn([]) ->
ok;
mass_spawn([N|T]) ->
@@ -3937,7 +3942,10 @@ mass_spawn([N|T]) ->
Parent ! self(),
loop()
end),
- receive Pid -> Pid end.
+ receive
+ Pid ->
+ Pid
+ end.
mass_names([], _) ->
[];
diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl
index 44ec7e7076..efe48e7e99 100644
--- a/lib/kernel/test/inet_SUITE.erl
+++ b/lib/kernel/test/inet_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. 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.
@@ -35,7 +35,8 @@
ipv4_to_ipv6/0, ipv4_to_ipv6/1,
host_and_addr/0, host_and_addr/1,
t_gethostnative/1,
- gethostnative_parallell/1, cname_loop/1, missing_hosts_reload/1,
+ gethostnative_parallell/1, cname_loop/1,
+ missing_hosts_reload/1, hosts_file_quirks/1,
gethostnative_soft_restart/0, gethostnative_soft_restart/1,
gethostnative_debug_level/0, gethostnative_debug_level/1,
lookup_bad_search_option/1,
@@ -43,6 +44,7 @@
getif_ifr_name_overflow/1,getservbyname_overflow/1, getifaddrs/1,
parse_strict_address/1, ipv4_mapped_ipv6_address/1,
simple_netns/1, simple_netns_open/1,
+ add_del_host/1, add_del_host_v6/1,
simple_bind_to_device/1, simple_bind_to_device_open/1]).
-export([get_hosts/1, get_ipv6_hosts/1, parse_hosts/1, parse_address/1,
@@ -57,11 +59,13 @@ all() ->
[t_gethostbyaddr, t_gethostbyname, t_getaddr,
t_gethostbyaddr_v6, t_gethostbyname_v6, t_getaddr_v6,
ipv4_to_ipv6, host_and_addr, {group, parse},
- t_gethostnative, gethostnative_parallell, cname_loop, missing_hosts_reload,
+ t_gethostnative, gethostnative_parallell, cname_loop,
+ missing_hosts_reload, hosts_file_quirks,
gethostnative_debug_level, gethostnative_soft_restart,
lookup_bad_search_option,
getif, getif_ifr_name_overflow, getservbyname_overflow,
getifaddrs, parse_strict_address, simple_netns, simple_netns_open,
+ add_del_host, add_del_host_v6,
simple_bind_to_device, simple_bind_to_device_open].
groups() ->
@@ -698,6 +702,8 @@ t_gethostnative(Config) when is_list(Config) ->
{error,notfound} ->
ok;
{error,no_data} ->
+ ok;
+ {error,try_again} ->
ok
end.
@@ -868,6 +874,176 @@ missing_hosts_reload(Config) when is_list(Config) ->
% cleanup
true = test_server:stop_node(TestNode).
+
+%% The /etc/hosts file format and limitations is quite undocumented.
+%%
+%% Our implementation of the hosts file resolver tries to
+%% do the right thing. Here is an attempt to define "the right thing",
+%% and this test case tries to check most of these rules:
+%%
+%% * A hosts file consists of entries with one IP address,
+%% and a list of host names. The IP address is IPv4 or IPv6.
+%% The first host name is the primary host name
+%% and the others are aliases.
+%%
+%% * A lookup for an IP address should return one #hostent{} record
+%% with the one IP address from the query, and the host names
+%% from all entries with the same IP address concatenated.
+%% The first host name from the first hosts file entry
+%% with the requested IP address will be the primary host name
+%% and all others are aliases. All host names are returned
+%% as in the hosts file entries i.e character case is preserved.
+%%
+%% * A lookup for a host name is character case insensitive.
+%%
+%% * A lookup for a host name should return one #hostent{} record
+%% with the host name list from the first hosts file entry
+%% with an IP address of the requested address family
+%% that has a matching host name, as it is in the hosts file
+%% i.e character case is preserved. The IP addresses in the
+%% returned #hostent{} record should be the first from the
+%% same matching hosts file entry, followed by all others
+%% for which there is a matching host name and address family.
+%% There should be no duplicates among the returned IP addresses.
+%%
+%% * These rules are of the opinion that if duplicate host names
+%% with the same character casing occurs for the same IP
+%% address, it is a configuration error, so it is not tested for
+%% and there is no preferred behaviour.
+hosts_file_quirks(Config) when is_list(Config) ->
+ Records = [R1, R2, R3, R4, R5] =
+ [#hostent{
+ h_name = h_ex(Name),
+ h_aliases = [h_ex(Alias) || Alias <- Aliases],
+ h_addrtype = Fam,
+ h_length =
+ case Fam of
+ inet -> 4;
+ inet6 -> 16
+ end,
+ h_addr_list =
+ [case Fam of
+ inet -> inet_ex(N);
+ inet6 -> inet6_ex(N)
+ end]}
+ || {{Fam,N}, Name, Aliases} <-
+ [{{inet,1}, "a", ["B"]},
+ {{inet,2}, "D", []},
+ {{inet6,3}, "A", ["c"]},
+ {{inet,1}, "c", []},
+ {{inet,5}, "A", []}]],
+ true = R1#hostent.h_addr_list =:= R4#hostent.h_addr_list,
+ R14 =
+ R1#hostent{
+ h_aliases =
+ R1#hostent.h_aliases ++
+ [R4#hostent.h_name | R4#hostent.h_aliases]},
+ R145 =
+ R14#hostent{
+ h_addr_list =
+ R1#hostent.h_addr_list ++ R5#hostent.h_addr_list},
+ %%
+ RootDir = proplists:get_value(priv_dir,Config),
+ HostsFile = filename:join(RootDir, atom_to_list(?MODULE) ++ "-quirks.hosts"),
+ InetRc = filename:join(RootDir, "quirks.inetrc"),
+ ok = file:write_file(HostsFile, hostents_to_list(Records)),
+ ok = file:write_file(InetRc, "{hosts_file, \"" ++ HostsFile ++ "\"}.\n"),
+ %%
+ %% start a node
+ Pa = filename:dirname(code:which(?MODULE)),
+ {ok, TestNode} = test_server:start_node(?MODULE, slave,
+ [{args, "-pa " ++ Pa ++ " -kernel inetrc '\"" ++ InetRc ++ "\"'"}]),
+ %% ensure it has our RC
+ Rc = rpc:call(TestNode, inet_db, get_rc, []),
+ {hosts_file, HostsFile} = lists:keyfind(hosts_file, 1, Rc),
+ %%
+ %% check entries
+ io:format("Check hosts file contents~n", []),
+ V1 =
+ [{R14, inet_ex(1)},
+ {R2, inet_ex(2)},
+ {R3, inet6_ex(3)},
+ {R5, inet_ex(5)},
+ {R145, h_ex("a"), inet},
+ {R14, h_ex("b"), inet},
+ {R14, h_ex("c"), inet},
+ {R2, h_ex("d"), inet},
+ {R3, h_ex("a"), inet6},
+ {R3, h_ex("c"), inet6}
+ ],
+ hosts_file_quirks_verify(TestNode, V1),
+ %%
+ %% test add and del
+ ok =
+ rpc:call(
+ TestNode, inet_db, add_host,
+ [inet_ex(1), [h_ex("a"), h_ex("B")]]),
+ io:format("Check after add host~n", []),
+ hosts_file_quirks_verify(
+ TestNode,
+ [{R1, inet_ex(1)},
+ {R2, inet_ex(2)},
+ {R3, inet6_ex(3)},
+ {R5, inet_ex(5)},
+ {R1, h_ex("a"), inet},
+ {R1, h_ex("b"), inet},
+ {R14, h_ex("c"), inet},
+ {R2, h_ex("d"), inet},
+ {R3, h_ex("a"), inet6},
+ {R3, h_ex("c"), inet6}
+ ]),
+ ok = rpc:call(TestNode, inet_db, del_host, [inet_ex(1)]),
+ io:format("Check after del host~n", []),
+ hosts_file_quirks_verify(TestNode, V1),
+ %%
+ %% cleanup
+ true = test_server:stop_node(TestNode).
+
+hosts_file_quirks_verify(_TestNode, Vs) ->
+ hosts_file_quirks_verify(_TestNode, Vs, true).
+%%
+hosts_file_quirks_verify(_TestNode, [], Ok) ->
+ case Ok of
+ true -> ok;
+ false -> error(verify_failed)
+ end;
+hosts_file_quirks_verify(TestNode, [V | Vs], Ok) ->
+ case
+ case V of
+ {R, Addr} ->
+ {R, rpc:call(TestNode, inet_hosts, gethostbyaddr, [Addr])};
+ {R, Host, Fam} ->
+ {R, rpc:call(TestNode, inet_hosts, gethostbyname, [Host, Fam])}
+ end
+ of
+ {nxdomain, {error, nxdomain}} ->
+ hosts_file_quirks_verify(TestNode, Vs, Ok);
+ {_R_1, {error, nxdomain}} ->
+ io:format("Verify failed ~p: nxdomain~n", [V]),
+ hosts_file_quirks_verify(TestNode, Vs, false);
+ {R_1, {ok, R_1}} ->
+ hosts_file_quirks_verify(TestNode, Vs, Ok);
+ {_R_1, {ok, R_2}} ->
+ io:format("Verify failed ~p: ~p~n", [V, R_2]),
+ hosts_file_quirks_verify(TestNode, Vs, false)
+ end.
+
+%% Expand entry
+h_ex(H) -> H ++ ".example.com".
+inet_ex(N) -> {127,17,17,N}.
+inet6_ex(N) -> {0,0,0,0,17,17,17,N}.
+
+hostents_to_list([]) -> [];
+hostents_to_list([R | Rs]) ->
+ #hostent{
+ h_name = Name,
+ h_aliases = Aliases,
+ h_addr_list = [IP]} = R,
+ [inet:ntoa(IP), $\t,
+ lists:join($\s, [Name | Aliases]), $\r, $\n
+ | hostents_to_list(Rs)].
+
+
%% These must be run in the whole suite since they need
%% the host list and require inet_gethost_native to be started.
%%
@@ -1413,3 +1589,46 @@ jog_bind_to_device_opt(S) ->
ok = inet:setopts(S, [{bind_to_device,<<"lo">>}]),
{ok,[{bind_to_device,<<"lo">>}]} = inet:getopts(S, [bind_to_device]),
ok.
+
+add_del_host(_Config) ->
+ Name = "foo.com",
+ Alias = "bar.org",
+ Ip = {69,89,31,226},
+ HostEnt = #hostent{
+ h_name = Name,
+ h_aliases = [Alias],
+ h_addrtype = inet,
+ h_length = 4,
+ h_addr_list = [Ip]
+ },
+ {error, nxdomain} = inet_hosts:gethostbyname(Name, inet),
+ ok = inet_db:add_host(Ip, [Name, Alias]),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Name, inet),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Alias, inet),
+ ok = inet_db:del_host(Ip),
+ {error, nxdomain} = inet_hosts:gethostbyname(Name, inet),
+ {error, nxdomain} = inet_hosts:gethostbyname(Alias, inet),
+ ok = inet_db:add_host(Ip, [Name, Alias]),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Name, inet).
+
+add_del_host_v6(_Config) ->
+ Name = "foo.com",
+ Alias = "bar.org",
+ Ip = {32,1,219,8,10,11,18,240},
+ HostEnt = #hostent{
+ h_name = Name,
+ h_aliases = [Alias],
+ h_addrtype = inet6,
+ h_length = 16,
+ h_addr_list = [Ip]
+ },
+ {error, nxdomain} = inet_hosts:gethostbyname(Name, inet6),
+ ok = inet_db:add_host(Ip, [Name, Alias]),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Name, inet6),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Alias, inet6),
+ ok = inet_db:del_host(Ip),
+ {error, nxdomain} = inet_hosts:gethostbyname(Name, inet6),
+ {error, nxdomain} = inet_hosts:gethostbyname(Alias, inet6),
+ ok = inet_db:add_host(Ip, [Name, Alias]),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Name, inet6).
+
diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl
index 9d35611d93..ceee387289 100644
--- a/lib/kernel/test/init_SUITE.erl
+++ b/lib/kernel/test/init_SUITE.erl
@@ -25,7 +25,7 @@
init_per_group/2,end_per_group/2]).
-export([get_arguments/1, get_argument/1, boot_var/1, restart/1,
- many_restarts/0, many_restarts/1,
+ many_restarts/0, many_restarts/1, restart_with_mode/1,
get_plain_arguments/1,
reboot/1, stop_status/1, stop/1, get_status/1, script_id/1,
find_system_processes/0]).
@@ -48,7 +48,7 @@ suite() ->
all() ->
[get_arguments, get_argument, boot_var,
- many_restarts,
+ many_restarts, restart_with_mode,
get_plain_arguments, restart, stop_status, get_status, script_id,
{group, boot}].
@@ -348,6 +348,22 @@ wait_for(N,Node,EHPid) ->
wait_for(N-1,Node,EHPid)
end.
+restart_with_mode(Config) when is_list(Config) ->
+ %% We cannot use loose_node because it doesn't run in
+ %% embedded mode so we quickly start one that exits after restarting
+ {ok,[[Erl]]} = init:get_argument(progname),
+ ModPath = filename:dirname(code:which(?MODULE)),
+
+ Eval1 = "'Mode=code:get_mode(), io:fwrite(Mode), case Mode of interactive -> init:restart([{mode,embedded}]); embedded -> erlang:halt() end'",
+ Cmd1 = Erl ++ " -mode interactive -noshell -eval " ++ Eval1,
+ "interactiveembedded" = os:cmd(Cmd1),
+
+ Eval2 = "'Mode=code:get_mode(), io:fwrite(Mode), case Mode of embedded -> init:restart([{mode,interactive}]); interactive -> erlang:halt() end'",
+ Cmd2 = Erl ++ " -mode embedded -noshell -eval " ++ Eval2,
+ "embeddedinteractive" = os:cmd(Cmd2),
+
+ ok.
+
%% ------------------------------------------------
%% Slave executes erlang:halt() on master nodedown.
%% Therefore the slave process must be killed
diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl
index 173e25c520..883abda067 100644
--- a/lib/kernel/test/interactive_shell_SUITE.erl
+++ b/lib/kernel/test/interactive_shell_SUITE.erl
@@ -22,7 +22,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
get_columns_and_rows/1, exit_initial/1, job_control_local/1,
- job_control_remote/1,
+ job_control_remote/1,stop_during_init/1,
job_control_remote_noshell/1,ctrl_keys/1,
get_columns_and_rows_escript/1]).
@@ -44,7 +44,7 @@ all() ->
[get_columns_and_rows_escript,get_columns_and_rows,
exit_initial, job_control_local,
job_control_remote, job_control_remote_noshell,
- ctrl_keys].
+ ctrl_keys, stop_during_init].
groups() ->
[].
@@ -205,6 +205,22 @@ exit_initial(Config) when is_list(Config) ->
{getline_re,"35"}],[])
end.
+stop_during_init(Config) when is_list(Config) ->
+ case get_progs() of
+ {error,_Reason} ->
+ {skip,"No runerl present"};
+ {RunErl,_ToErl,Erl} ->
+ case create_tempdir() of
+ {error, Reason2} ->
+ {skip, Reason2};
+ Tempdir ->
+ XArg = " -kernel shell_history true -s init stop",
+ start_runerl_command(RunErl, Tempdir, "\\\""++Erl++"\\\""++XArg),
+ {ok, Binary} = file:read_file(filename:join(Tempdir, "erlang.log.1")),
+ nomatch = binary:match(Binary, <<"*** ERROR: Shell process terminated! ***">>)
+ end
+ end.
+
%% Tests that local shell can be started by means of job control.
job_control_local(Config) when is_list(Config) ->
case proplists:get_value(default_shell,Config) of
@@ -656,10 +672,10 @@ start_runerl_node(RunErl,Erl,Tempdir,Nodename) ->
end)++
" -setcookie "++atom_to_list(erlang:get_cookie())
end,
- spawn(fun() ->
- os:cmd("\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++" \""++
- Erl++XArg++"\"")
- end).
+ spawn(fun() -> start_runerl_command(RunErl, Tempdir, Erl++XArg) end).
+
+start_runerl_command(RunErl, Tempdir, Cmd) ->
+ os:cmd("\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++" \""++Cmd++"\"").
start_toerl_server(ToErl,Tempdir) ->
Pid = spawn(?MODULE,toerl_server,[self(),ToErl,Tempdir]),
diff --git a/lib/kernel/test/kernel_SUITE.erl b/lib/kernel/test/kernel_SUITE.erl
index 3e5ed855b5..1a88c70963 100644
--- a/lib/kernel/test/kernel_SUITE.erl
+++ b/lib/kernel/test/kernel_SUITE.erl
@@ -23,6 +23,7 @@
-module(kernel_SUITE).
-include_lib("common_test/include/ct.hrl").
+-compile(r21).
%% Test server specific exports
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
@@ -106,7 +107,7 @@ appup_tests(App,{OkVsns0,NokVsns}) ->
create_test_vsns(App) ->
ThisMajor = erlang:system_info(otp_release),
FirstMajor = previous_major(ThisMajor),
- SecondMajor = previous_major(FirstMajor),
+ SecondMajor = previous_major(previous_major(FirstMajor)),
Ok = app_vsn(App,[ThisMajor,FirstMajor]),
Nok0 = app_vsn(App,[SecondMajor]),
Nok = case Ok of
diff --git a/lib/kernel/test/logger_SUITE.erl b/lib/kernel/test/logger_SUITE.erl
index f8f3d27778..f9dce3b61e 100644
--- a/lib/kernel/test/logger_SUITE.erl
+++ b/lib/kernel/test/logger_SUITE.erl
@@ -276,7 +276,7 @@ change_config(_Config) ->
logger:get_primary_config(),
3 = maps:size(PC1),
%% Check that internal 'handlers' field has not been changed
- MS = [{{{?HANDLER_KEY,'$1'},'_','_'},[],['$1']}],
+ MS = [{{{?HANDLER_KEY,'$1'},'_'},[],['$1']}],
HIds1 = lists:sort(ets:select(?LOGGER_TABLE,MS)), % dirty, internal data
HIds2 = lists:sort(logger:get_handler_ids()),
HIds1 = HIds2,
@@ -478,14 +478,15 @@ set_application_level(cleanup,_Config) ->
ok.
cache_module_level(_Config) ->
- ok = logger:unset_module_level(?MODULE),
- [] = ets:lookup(?LOGGER_TABLE,?MODULE), %dirty - add API in logger_config?
+
+ %% This test does a lot of whitebox tests so be prepared for that
+ persistent_term:erase({logger_config,?MODULE}),
+
+ primary = persistent_term:get({logger_config,?MODULE}, primary),
?LOG_NOTICE(?map_rep),
- %% Caching is done asynchronously, so wait a bit for the update
- timer:sleep(100),
- [_] = ets:lookup(?LOGGER_TABLE,?MODULE), %dirty - add API in logger_config?
- ok = logger:unset_module_level(?MODULE),
- [] = ets:lookup(?LOGGER_TABLE,?MODULE), %dirty - add API in logger_config?
+ 5 = persistent_term:get({logger_config,?MODULE}, primary),
+ logger:set_primary_config(level, info),
+ 6 = persistent_term:get({logger_config,?MODULE}, primary),
ok.
cache_module_level(cleanup,_Config) ->
@@ -569,6 +570,7 @@ filter_failed(cleanup,_Config) ->
ok.
handler_failed(_Config) ->
+ logger:set_primary_config(level,all),
register(callback_receiver,self()),
{error,{invalid_id,1}} = logger:add_handler(1,?MODULE,#{}),
{error,{invalid_module,"nomodule"}} = logger:add_handler(h1,"nomodule",#{}),
@@ -612,7 +614,7 @@ handler_failed(_Config) ->
logger:add_handler(h1,?MODULE,#{add_call=>KillHandler}),
check_no_log(),
- ok = logger:add_handler(h1,?MODULE,#{}),
+ ok = logger:add_handler(h1,?MODULE,#{tc_proc=>self()}),
{error,{attempting_syncronous_call_to_self,_}} =
logger:set_handler_config(h1,#{conf_call=>CallAddHandler}),
{error,{callback_crashed,_}} =
@@ -628,7 +630,8 @@ handler_failed(_Config) ->
logger:set_handler_config(h1,conf_call,KillHandler),
ok = logger:remove_handler(h1),
- [add,remove] = test_server:messages_get(),
+ [add,{#{level:=error},_},{#{level:=error},_},
+ {#{level:=error},_},{#{level:=error},_},remove] = test_server:messages_get(),
check_no_log(),
ok = logger:add_handler(h1,?MODULE,#{rem_call=>CallAddHandler}),
@@ -644,6 +647,7 @@ handler_failed(_Config) ->
handler_failed(cleanup,_Config) ->
logger:remove_handler(h1),
logger:remove_handler(h2),
+ logger:set_primary_config(level,info),
ok.
config_sanity_check(_Config) ->
@@ -1146,7 +1150,7 @@ kernel_config(Config) ->
ok.
-pretty_print(Config) ->
+pretty_print(_Config) ->
ok = logger:add_handler(?FUNCTION_NAME,logger_std_h,#{}),
ok = logger:set_module_level([module1,module2],debug),
@@ -1280,11 +1284,21 @@ test_api(Level) ->
ok = check_logged(Level,#{Level=>rep},#{my=>meta}),
logger:Level("~w: ~w",[Level,fa]),
ok = check_logged(Level,"~w: ~w",[Level,fa],#{}),
+ logger:Level('~w: ~w',[Level,fa]),
+ ok = check_logged(Level,'~w: ~w',[Level,fa],#{}),
+ logger:Level(<<"~w: ~w">>,[Level,fa]),
+ ok = check_logged(Level,<<"~w: ~w">>,[Level,fa],#{}),
logger:Level("~w: ~w ~w",[Level,fa,meta],#{my=>meta}),
ok = check_logged(Level,"~w: ~w ~w",[Level,fa,meta],#{my=>meta}),
logger:Level(fun(x) -> {"~w: ~w ~w",[Level,fun_to_fa,meta]} end,x,
#{my=>meta}),
ok = check_logged(Level,"~w: ~w ~w",[Level,fun_to_fa,meta],#{my=>meta}),
+ logger:Level(fun(x) -> {<<"~w: ~w ~w">>,[Level,fun_to_fa,meta]} end,x,
+ #{my=>meta}),
+ ok = check_logged(Level,<<"~w: ~w ~w">>,[Level,fun_to_fa,meta],#{my=>meta}),
+ logger:Level(fun(x) -> {'~w: ~w ~w',[Level,fun_to_fa,meta]} end,x,
+ #{my=>meta}),
+ ok = check_logged(Level,'~w: ~w ~w',[Level,fun_to_fa,meta],#{my=>meta}),
logger:Level(fun(x) -> #{Level=>fun_to_r,meta=>true} end,x,
#{my=>meta}),
ok = check_logged(Level,#{Level=>fun_to_r,meta=>true},#{my=>meta}),
@@ -1310,6 +1324,12 @@ test_log_function(Level) ->
logger:log(Level,fun(x) -> {"~w: ~w ~w",[Level,fun_to_fa,meta]} end,
x, #{my=>meta}),
ok = check_logged(Level,"~w: ~w ~w",[Level,fun_to_fa,meta],#{my=>meta}),
+ logger:log(Level,fun(x) -> {<<"~w: ~w ~w">>,[Level,fun_to_fa,meta]} end,
+ x, #{my=>meta}),
+ ok = check_logged(Level,<<"~w: ~w ~w">>,[Level,fun_to_fa,meta],#{my=>meta}),
+ logger:log(Level,fun(x) -> {'~w: ~w ~w',[Level,fun_to_fa,meta]} end,
+ x, #{my=>meta}),
+ ok = check_logged(Level,'~w: ~w ~w',[Level,fun_to_fa,meta],#{my=>meta}),
logger:log(Level,fun(x) -> #{Level=>fun_to_r,meta=>true} end,
x, #{my=>meta}),
ok = check_logged(Level,#{Level=>fun_to_r,meta=>true},#{my=>meta}),
diff --git a/lib/kernel/test/logger_formatter_SUITE.erl b/lib/kernel/test/logger_formatter_SUITE.erl
index 83e3e6c40a..5217f76cbc 100644
--- a/lib/kernel/test/logger_formatter_SUITE.erl
+++ b/lib/kernel/test/logger_formatter_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2019. 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.
@@ -568,6 +568,11 @@ format_mfa(_Config) ->
ct:log(String4),
"othermfa" = String4,
+ Meta5 = #{mfa=>{'m o d','a\x{281}b',[' ']}},
+ String5 = format(info,{"~p",[term]},Meta5,#{template=>Template}),
+ ct:log(String5),
+ "'m o d':'a\x{281}b'/1" = String5,
+
ok.
format_time(_Config) ->
diff --git a/lib/kernel/test/logger_legacy_SUITE.erl b/lib/kernel/test/logger_legacy_SUITE.erl
index c3cab07d81..0e46ec3ee3 100644
--- a/lib/kernel/test/logger_legacy_SUITE.erl
+++ b/lib/kernel/test/logger_legacy_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2019. 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.
@@ -137,9 +137,9 @@ gen_event(_Config) ->
ok = gen_event:add_handler(Pid,?MODULE,gen_event),
Msg = fun() -> erlang:error({badmatch,b}) end,
Pid ! Msg,
- ?check({warning_msg,"** Undefined handle_info in ~tp"++_,[?MODULE,Msg]}),
+ ?check({warning_msg,"** Undefined handle_info in ~p"++_,[?MODULE,Msg]}),
gen_event:notify(Pid,Msg),
- ?check({error,"** gen_event handler ~p crashed."++_,
+ ?check({error,"** gen_event handler ~tp crashed."++_,
[?MODULE,Pid,Msg,gen_event,{{badmatch,b},_}]}).
gen_fsm(_Config) ->
diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl
index 602a79c78b..ec0e5122e8 100644
--- a/lib/kernel/test/logger_std_h_SUITE.erl
+++ b/lib/kernel/test/logger_std_h_SUITE.erl
@@ -305,6 +305,8 @@ formatter_fail(Config) ->
Dir = ?config(priv_dir,Config),
Log = filename:join(Dir,?FUNCTION_NAME),
+ logger:set_primary_config(level,all),
+
%% no formatter
ok = logger:add_handler(?MODULE,
logger_std_h,
@@ -346,6 +348,7 @@ formatter_fail(Config) ->
ok.
formatter_fail(cleanup,_Config) ->
+ logger:set_primary_config(level,info),
logger:remove_handler(?MODULE).
config_fail(_Config) ->
@@ -2200,10 +2203,14 @@ check_tracer(T,TimeoutFun) ->
TimeoutFun()
end.
-escape([$+|Rest]) ->
- [$\\,$+|escape(Rest)];
-escape([H|T]) ->
- [H|escape(T)];
+escape([C|Rest]) ->
+ %% The characters that have to be escaped in a regex
+ case lists:member(C,"[-[\]{}()*+?.,\\^$|#\s]") of
+ true ->
+ [$\\,C|escape(Rest)];
+ false ->
+ [C|escape(Rest)]
+ end;
escape([]) ->
[].
diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl
index 710b9b115c..e952dec625 100644
--- a/lib/kernel/test/os_SUITE.erl
+++ b/lib/kernel/test/os_SUITE.erl
@@ -324,14 +324,18 @@ close_stdin(Config) ->
"-1" = os:cmd(Fds).
max_size_command(_Config) ->
+ WSL = case os:getenv("WSLENV") of
+ false -> "";
+ _ -> "wsl "
+ end,
- Res20 = os:cmd("cat /dev/zero", #{ max_size => 20 }),
+ Res20 = os:cmd(WSL ++ "cat /dev/zero", #{ max_size => 20 }),
20 = length(Res20),
- Res0 = os:cmd("cat /dev/zero", #{ max_size => 0 }),
+ Res0 = os:cmd(WSL ++ "cat /dev/zero", #{ max_size => 0 }),
0 = length(Res0),
- Res32768 = os:cmd("cat /dev/zero", #{ max_size => 32768 }),
+ Res32768 = os:cmd(WSL ++ "cat /dev/zero", #{ max_size => 32768 }),
32768 = length(Res32768),
ResHello = string:trim(os:cmd("echo hello", #{ max_size => 20 })),
diff --git a/lib/kernel/test/pg_SUITE.erl b/lib/kernel/test/pg_SUITE.erl
new file mode 100644
index 0000000000..bdb7abe99d
--- /dev/null
+++ b/lib/kernel/test/pg_SUITE.erl
@@ -0,0 +1,619 @@
+%%
+%%
+%% Copyright WhatsApp Inc. and its affiliates. 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.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%%-------------------------------------------------------------------
+%% @author Maxim Fedorov <maximfca@gmail.com>
+%% Process Groups smoke test.
+-module(pg_SUITE).
+-author("maximfca@gmail.com").
+
+%% Test server callbacks
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ stop_proc/1
+]).
+
+%% Test cases exports
+-export([
+ pg/0, pg/1,
+ errors/0, errors/1,
+ leave_exit_race/0, leave_exit_race/1,
+ single/0, single/1,
+ two/1,
+ thundering_herd/0, thundering_herd/1,
+ initial/1,
+ netsplit/1,
+ trisplit/1,
+ foursplit/1,
+ exchange/1,
+ nolocal/1,
+ double/1,
+ scope_restart/1,
+ missing_scope_join/1,
+ disconnected_start/1,
+ forced_sync/0, forced_sync/1,
+ group_leave/1
+]).
+
+-export([
+ control/1,
+ controller/3
+]).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
+
+suite() ->
+ [{timetrap, {seconds, 10}}].
+
+init_per_suite(Config) ->
+ case erlang:is_alive() of
+ false ->
+ %% verify epmd running (otherwise next call fails)
+ (erl_epmd:names("localhost") =:= {error, address}) andalso ([] = os:cmd("epmd -daemon")),
+ %% start a random node name
+ NodeName = list_to_atom(lists:concat([atom_to_list(?MODULE), "_", os:getpid()])),
+ {ok, Pid} = net_kernel:start([NodeName, shortnames]),
+ [{distribution, Pid} | Config];
+ true ->
+ Config
+ end.
+
+end_per_suite(Config) ->
+ is_pid(proplists:get_value(distribution, Config)) andalso net_kernel:stop().
+
+init_per_testcase(TestCase, Config) ->
+ {ok, _Pid} = pg:start_link(TestCase),
+ Config.
+
+end_per_testcase(TestCase, _Config) ->
+ gen_server:stop(TestCase),
+ ok.
+
+all() ->
+ [{group, basic}, {group, cluster}, {group, performance}].
+
+groups() ->
+ [
+ {basic, [parallel], [errors, pg, leave_exit_race, single]},
+ {performance, [sequential], [thundering_herd]},
+ {cluster, [parallel], [two, initial, netsplit, trisplit, foursplit,
+ exchange, nolocal, double, scope_restart, missing_scope_join,
+ disconnected_start, forced_sync, group_leave]}
+ ].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+
+pg() ->
+ [{doc, "This test must be names pg, to stay inline with default scope"}].
+
+pg(_Config) ->
+ ?assertNotEqual(undefined, whereis(?FUNCTION_NAME)), %% ensure scope was started
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, self())),
+ ?assertEqual([self()], pg:get_local_members(?FUNCTION_NAME)),
+ ?assertEqual([?FUNCTION_NAME], pg:which_groups()),
+ ?assertEqual([?FUNCTION_NAME], pg:which_local_groups()),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, self())),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME)),
+ ?assertEqual([], pg:which_groups(?FUNCTION_NAME)),
+ ?assertEqual([], pg:which_local_groups(?FUNCTION_NAME)).
+
+errors() ->
+ [{doc, "Tests that errors are handled as expected, for example pg server crashes when it needs to"}].
+
+errors(_Config) ->
+ %% kill with 'info' and 'cast'
+ ?assertException(error, badarg, pg:handle_info(garbage, garbage)),
+ ?assertException(error, badarg, pg:handle_cast(garbage, garbage)),
+ %% kill with call
+ {ok, _Pid} = pg:start(second),
+ ?assertException(exit, {{badarg, _}, _}, gen_server:call(second, garbage, 100)).
+
+leave_exit_race() ->
+ [{doc, "Tests that pg correctly handles situation when leave and 'DOWN' messages are both in pg queue"}].
+
+leave_exit_race(Config) when is_list(Config) ->
+ process_flag(priority, high),
+ [
+ begin
+ Pid = spawn(fun () -> ok end),
+ pg:join(leave_exit_race, test, Pid),
+ pg:leave(leave_exit_race, test, Pid)
+ end
+ || _ <- lists:seq(1, 100)].
+
+single() ->
+ [{doc, "Tests single node groups"}, {timetrap, {seconds, 5}}].
+
+single(Config) when is_list(Config) ->
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, self())),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [self(), self()])),
+ ?assertEqual([self(), self(), self()], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual([self(), self(), self()], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual(not_joined, pg:leave(?FUNCTION_NAME, '$missing$', self())),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, ?FUNCTION_NAME, [self(), self()])),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, ?FUNCTION_NAME, self())),
+ ?assertEqual([], pg:which_groups(?FUNCTION_NAME)),
+ ?assertEqual([], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ %% double
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, self())),
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, Pid)),
+ Expected = lists:sort([Pid, self()]),
+ ?assertEqual(Expected, lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ ?assertEqual(Expected, lists:sort(pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+
+ stop_proc(Pid),
+ sync(?FUNCTION_NAME),
+ ?assertEqual([self()], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, ?FUNCTION_NAME, self())),
+ ok.
+
+two(Config) when is_list(Config) ->
+ {TwoPeer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, Pid)),
+ ?assertEqual([Pid], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ %% first RPC must be serialised
+ sync({?FUNCTION_NAME, TwoPeer}),
+ ?assertEqual([Pid], rpc:call(TwoPeer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ ?assertEqual([], rpc:call(TwoPeer, pg, get_local_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ stop_proc(Pid),
+ %% again, must be serialised
+ sync(?FUNCTION_NAME),
+ ?assertEqual([], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual([], rpc:call(TwoPeer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+
+ Pid2 = erlang:spawn(TwoPeer, forever()),
+ Pid3 = erlang:spawn(TwoPeer, forever()),
+ ?assertEqual(ok, rpc:call(TwoPeer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, Pid2])),
+ ?assertEqual(ok, rpc:call(TwoPeer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, Pid3])),
+ %% serialise through the *other* node
+ sync({?FUNCTION_NAME, TwoPeer}),
+ ?assertEqual(lists:sort([Pid2, Pid3]),
+ lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ %% stop the peer
+ stop_node(TwoPeer, Socket),
+ %% hope that 'nodedown' comes before we route our request
+ sync(?FUNCTION_NAME),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ok.
+
+thundering_herd() ->
+ [{doc, "Thousands of overlay network nodes sending sync to us, and we time out!"}, {timetrap, {seconds, 5}}].
+
+thundering_herd(Config) when is_list(Config) ->
+ GroupCount = 10000,
+ SyncCount = 2000,
+ %% make up a large amount of groups
+ [pg:join(?FUNCTION_NAME, {group, Seq}, self()) || Seq <- lists:seq(1, GroupCount)],
+ %% initiate a few syncs - and those are really slow...
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ PeerPid = erlang:spawn(Peer, forever()),
+ PeerPg = rpc:call(Peer, erlang, whereis, [?FUNCTION_NAME], 1000),
+ %% WARNING: code below acts for white-box! %% WARNING
+ FakeSync = [{{group, 1}, [PeerPid, PeerPid]}],
+ [gen_server:cast(?FUNCTION_NAME, {sync, PeerPg, FakeSync}) || _ <- lists:seq(1, SyncCount)],
+ %% next call must not timetrap, otherwise test fails
+ pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, self()),
+ stop_node(Peer, Socket).
+
+initial(Config) when is_list(Config) ->
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, Pid)),
+ ?assertEqual([Pid], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ %% first RPC must be serialised
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual([Pid], rpc:call(Peer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+
+ ?assertEqual([], rpc:call(Peer, pg, get_local_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ stop_proc(Pid),
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual([], rpc:call(Peer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ stop_node(Peer, Socket),
+ ok.
+
+netsplit(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ ?assertEqual(Peer, rpc(Socket, erlang, node, [])), %% just to test RPC
+ RemoteOldPid = erlang:spawn(Peer, forever()),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, '$invisible', RemoteOldPid])),
+ %% hohoho, partition!
+ net_kernel:disconnect(Peer),
+ ?assertEqual(Peer, rpc(Socket, erlang, node, [])), %% just to ensure RPC still works
+ RemotePid = rpc(Socket, erlang, spawn, [forever()]),
+ ?assertEqual([], rpc(Socket, erlang, nodes, [])),
+ ?assertEqual(ok, rpc(Socket, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])), %% join - in a partition!
+
+ ?assertEqual(ok, rpc(Socket, pg, leave, [?FUNCTION_NAME, '$invisible', RemoteOldPid])),
+ ?assertEqual(ok, rpc(Socket, pg, join, [?FUNCTION_NAME, '$visible', RemoteOldPid])),
+ ?assertEqual([RemoteOldPid], rpc(Socket, pg, get_local_members, [?FUNCTION_NAME, '$visible'])),
+ %% join locally too
+ LocalPid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, LocalPid)),
+
+ ?assertNot(lists:member(Peer, nodes())), %% should be no nodes in the cluster
+
+ pong = net_adm:ping(Peer),
+ %% now ensure sync happened
+ Pids = lists:sort([RemotePid, LocalPid]),
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual(Pids, lists:sort(rpc:call(Peer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME]))),
+ ?assertEqual([RemoteOldPid], pg:get_members(?FUNCTION_NAME, '$visible')),
+ stop_node(Peer, Socket),
+ ok.
+
+trisplit(Config) when is_list(Config) ->
+ {Peer, Socket1} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ _PeerPid1 = erlang:spawn(Peer, forever()),
+ PeerPid2 = erlang:spawn(Peer, forever()),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, three, PeerPid2])),
+ net_kernel:disconnect(Peer),
+ ?assertEqual(true, net_kernel:connect_node(Peer)),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, one, PeerPid2])),
+ %% now ensure sync happened
+ {Peer2, Socket2} = spawn_node(?FUNCTION_NAME, trisplit_second),
+ ?assertEqual(true, rpc:call(Peer2, net_kernel, connect_node, [Peer])),
+ ?assertEqual(lists:sort([node(), Peer]), lists:sort(rpc:call(Peer2, erlang, nodes, []))),
+ sync({?FUNCTION_NAME, Peer2}),
+ ?assertEqual([PeerPid2], rpc:call(Peer2, pg, get_members, [?FUNCTION_NAME, one])),
+ stop_node(Peer, Socket1),
+ stop_node(Peer2, Socket2),
+ ok.
+
+foursplit(Config) when is_list(Config) ->
+ Pid = erlang:spawn(forever()),
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, one, Pid)),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, two, Pid)),
+ PeerPid1 = spawn(Peer, forever()),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, one, Pid)),
+ ?assertEqual(not_joined, pg:leave(?FUNCTION_NAME, three, Pid)),
+ net_kernel:disconnect(Peer),
+ ?assertEqual(ok, rpc(Socket, ?MODULE, stop_proc, [PeerPid1])),
+ ?assertEqual(not_joined, pg:leave(?FUNCTION_NAME, three, Pid)),
+ ?assertEqual(true, net_kernel:connect_node(Peer)),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME, one)),
+ ?assertEqual([], rpc(Socket, pg, get_members, [?FUNCTION_NAME, one])),
+ stop_node(Peer, Socket),
+ ok.
+
+exchange(Config) when is_list(Config) ->
+ {Peer1, Socket1} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ {Peer2, Socket2} = spawn_node(?FUNCTION_NAME, exchange_second),
+ Pids10 = [rpc(Socket1, erlang, spawn, [forever()]) || _ <- lists:seq(1, 10)],
+ Pids2 = [rpc(Socket2, erlang, spawn, [forever()]) || _ <- lists:seq(1, 10)],
+ Pids11 = [rpc(Socket1, erlang, spawn, [forever()]) || _ <- lists:seq(1, 10)],
+ %% kill first 3 pids from node1
+ {PidsToKill, Pids1} = lists:split(3, Pids10),
+
+ ?assertEqual(ok, rpc(Socket1, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, Pids10])),
+ sync({?FUNCTION_NAME, Peer1}),
+ ?assertEqual(lists:sort(Pids10), lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ [rpc(Socket1, ?MODULE, stop_proc, [Pid]) || Pid <- PidsToKill],
+ sync(?FUNCTION_NAME),
+ sync({?FUNCTION_NAME, Peer1}),
+
+ Pids = lists:sort(Pids1 ++ Pids2 ++ Pids11),
+ ?assert(lists:all(fun erlang:is_pid/1, Pids)),
+
+ net_kernel:disconnect(Peer1),
+ net_kernel:disconnect(Peer2),
+
+ sync(?FUNCTION_NAME),
+ ?assertEqual([], lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+
+ [?assertEqual(ok, rpc(Socket2, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, Pid])) || Pid <- Pids2],
+ [?assertEqual(ok, rpc(Socket1, pg, join, [?FUNCTION_NAME, second, Pid])) || Pid <- Pids11],
+ ?assertEqual(ok, rpc(Socket1, pg, join, [?FUNCTION_NAME, third, Pids11])),
+ %% rejoin
+ ?assertEqual(true, net_kernel:connect_node(Peer1)),
+ ?assertEqual(true, net_kernel:connect_node(Peer2)),
+ %% need to sleep longer to ensure both nodes made the exchange
+ sync(?FUNCTION_NAME),
+ sync({?FUNCTION_NAME, Peer1}),
+ sync({?FUNCTION_NAME, Peer2}),
+ ?assertEqual(Pids, lists:sort(pg:get_members(?FUNCTION_NAME, second) ++ pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ ?assertEqual(lists:sort(Pids11), lists:sort(pg:get_members(?FUNCTION_NAME, third))),
+
+ {Left, Stay} = lists:split(3, Pids11),
+ ?assertEqual(ok, rpc(Socket1, pg, leave, [?FUNCTION_NAME, third, Left])),
+ sync({?FUNCTION_NAME, Peer1}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual(lists:sort(Stay), lists:sort(pg:get_members(?FUNCTION_NAME, third))),
+ ?assertEqual(not_joined, rpc(Socket1, pg, leave, [?FUNCTION_NAME, left, Stay])),
+ ?assertEqual(ok, rpc(Socket1, pg, leave, [?FUNCTION_NAME, third, Stay])),
+ sync({?FUNCTION_NAME, Peer1}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual([], lists:sort(pg:get_members(?FUNCTION_NAME, third))),
+ sync({?FUNCTION_NAME, Peer1}),
+ sync(?FUNCTION_NAME),
+
+ stop_node(Peer1, Socket1),
+ stop_node(Peer2, Socket2),
+ ok.
+
+nolocal(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ RemotePid = spawn(Peer, forever()),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ ?assertEqual([], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ stop_node(Peer, Socket),
+ ok.
+
+double(Config) when is_list(Config) ->
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, Pid)),
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [Pid])),
+ ?assertEqual([Pid, Pid], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ sync(?FUNCTION_NAME),
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual([Pid, Pid], rpc:call(Peer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ stop_node(Peer, Socket),
+ ok.
+
+scope_restart(Config) when is_list(Config) ->
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [Pid, Pid])),
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ RemotePid = spawn(Peer, forever()),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual(lists:sort([RemotePid, Pid, Pid]), lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ %% stop scope locally, and restart
+ gen_server:stop(?FUNCTION_NAME),
+ pg:start(?FUNCTION_NAME),
+ %% ensure remote pids joined, local are missing
+ sync(?FUNCTION_NAME),
+ sync({?FUNCTION_NAME, Peer}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual([RemotePid], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ stop_node(Peer, Socket),
+ ok.
+
+missing_scope_join(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ ?assertEqual(ok, rpc:call(Peer, gen_server, stop, [?FUNCTION_NAME])),
+ RemotePid = spawn(Peer, forever()),
+ ?assertMatch({badrpc, {'EXIT', {noproc, _}}}, rpc:call(Peer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ ?assertMatch({badrpc, {'EXIT', {noproc, _}}}, rpc:call(Peer, pg, leave, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ stop_node(Peer, Socket),
+ ok.
+
+disconnected_start(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ net_kernel:disconnect(Peer),
+ ?assertEqual(ok, rpc(Socket, gen_server, stop, [?FUNCTION_NAME])),
+ ?assertMatch({ok, _Pid}, rpc(Socket, pg, start,[?FUNCTION_NAME])),
+ ?assertEqual(ok, rpc(Socket, gen_server, stop, [?FUNCTION_NAME])),
+ RemotePid = rpc(Socket, erlang, spawn, [forever()]),
+ ?assert(is_pid(RemotePid)),
+ stop_node(Peer, Socket),
+ ok.
+
+forced_sync() ->
+ [{doc, "This test was added when lookup_element was erroneously used instead of lookup, crashing pg with badmatch, and it tests rare out-of-order sync operations"}].
+
+forced_sync(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ Pid = erlang:spawn(forever()),
+ RemotePid = spawn(Peer, forever()),
+ Expected = lists:sort([Pid, RemotePid]),
+ pg:join(?FUNCTION_NAME, one, Pid),
+
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, one, RemotePid])),
+ RemoteScopePid = rpc:call(Peer, erlang, whereis, [?FUNCTION_NAME]),
+ ?assert(is_pid(RemoteScopePid)),
+ %% hohoho, partition!
+ net_kernel:disconnect(Peer),
+ ?assertEqual(true, net_kernel:connect_node(Peer)),
+ %% now ensure sync happened
+ sync({?FUNCTION_NAME, Peer}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual(Expected, lists:sort(pg:get_members(?FUNCTION_NAME, one))),
+ %% WARNING: this code uses pg as white-box, exploiting internals,
+ %% only to simulate broken 'sync'
+ %% Fake Groups: one should disappear, one should be replaced, one stays
+ %% This tests handle_sync function.
+ FakeGroups = [{one, [RemotePid, RemotePid]}, {?FUNCTION_NAME, [RemotePid, RemotePid]}],
+ gen_server:cast(?FUNCTION_NAME, {sync, RemoteScopePid, FakeGroups}),
+ %% ensure it is broken well enough
+ sync(?FUNCTION_NAME),
+ ?assertEqual(lists:sort([RemotePid, RemotePid]), lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ ?assertEqual(lists:sort([RemotePid, RemotePid, Pid]), lists:sort(pg:get_members(?FUNCTION_NAME, one))),
+ %% simulate force-sync via 'discover' - ask peer to send sync to us
+ {?FUNCTION_NAME, Peer} ! {discover, whereis(?FUNCTION_NAME)},
+ sync({?FUNCTION_NAME, Peer}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual(Expected, lists:sort(pg:get_members(?FUNCTION_NAME, one))),
+ ?assertEqual([], lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ %% and simulate extra sync
+ sync({?FUNCTION_NAME, Peer}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual(Expected, lists:sort(pg:get_members(?FUNCTION_NAME, one))),
+
+ stop_node(Peer, Socket),
+ ok.
+
+group_leave(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ RemotePid = erlang:spawn(Peer, forever()),
+ Total = lists:duplicate(16, RemotePid),
+ {Left, Remain} = lists:split(4, Total),
+ %% join 16 times!
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, two, Total])),
+ ?assertEqual(ok, rpc:call(Peer, pg, leave, [?FUNCTION_NAME, two, Left])),
+
+ sync({?FUNCTION_NAME, Peer}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual(Remain, pg:get_members(?FUNCTION_NAME, two)),
+ stop_node(Peer, Socket),
+ sync(?FUNCTION_NAME),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME, two)),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Test Helpers - start/stop additional Erlang nodes
+
+sync(GS) ->
+ _ = sys:log(GS, get).
+
+-define (LOCALHOST, {127, 0, 0, 1}).
+
+%% @doc Kills process Pid and waits for it to exit using monitor,
+%% and yields after (for 1 ms).
+-spec stop_proc(pid()) -> ok.
+stop_proc(Pid) ->
+ monitor(process, Pid),
+ erlang:exit(Pid, kill),
+ receive
+ {'DOWN', _MRef, process, Pid, _Info} ->
+ timer:sleep(1)
+ end.
+
+%% @doc Executes remote call on the node via TCP socket
+%% Used when dist connection is not available, or
+%% when it's undesirable to use one.
+-spec rpc(gen_tcp:socket(), module(), atom(), [term()]) -> term().
+rpc(Sock, M, F, A) ->
+ ok = gen_tcp:send(Sock, term_to_binary({call, M, F, A})),
+ inet:setopts(Sock, [{active, once}]),
+ receive
+ {tcp, Sock, Data} ->
+ case binary_to_term(Data) of
+ {ok, Val} ->
+ Val;
+ {error, Error} ->
+ {badrpc, Error}
+ end;
+ {tcp_closed, Sock} ->
+ error(closed)
+ end.
+
+%% @doc starts peer node on this host.
+%% Returns spawned node name, and a gen_tcp socket to talk to it using ?MODULE:rpc.
+-spec spawn_node(Scope :: atom(), Node :: atom()) -> {node(), gen_tcp:socket()}.
+spawn_node(Scope, Name) ->
+ Self = self(),
+ Controller = erlang:spawn(?MODULE, controller, [Name, Scope, Self]),
+ receive
+ {'$node_started', Node, Port} ->
+ {ok, Socket} = gen_tcp:connect(?LOCALHOST, Port, [{active, false}, {mode, binary}, {packet, 4}]),
+ Controller ! {socket, Socket},
+ {Node, Socket};
+ Other ->
+ error({start_node, Name, Other})
+ after 60000 ->
+ error({start_node, Name, timeout})
+ end.
+
+%% @private
+-spec controller(atom(), atom(), pid()) -> ok.
+controller(Name, Scope, Self) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ Pa2 = filename:dirname(code:which(pg)),
+ Args = lists:concat(["-setcookie ", erlang:get_cookie(),
+ "-connect_all false -kernel dist_auto_connect never -noshell -pa ", Pa, " -pa ", Pa2]),
+ {ok, Node} = test_server:start_node(Name, peer, [{args, Args}]),
+ case rpc:call(Node, ?MODULE, control, [Scope], 5000) of
+ {badrpc, nodedown} ->
+ Self ! {badrpc, Node},
+ ok;
+ {Port, _PgPid} ->
+ Self ! {'$node_started', Node, Port},
+ controller_wait()
+ end.
+
+controller_wait() ->
+ Port =
+ receive
+ {socket, Port0} ->
+ Port0
+ end,
+ MRef = monitor(port, Port),
+ receive
+ {'DOWN', MRef, port, Port, _Info} ->
+ ok
+ end.
+
+%% @doc Stops the node previously started with spawn_node,
+%% and also closes the RPC socket.
+-spec stop_node(node(), gen_tcp:socket()) -> true.
+stop_node(Node, Socket) when Node =/= node() ->
+ true = test_server:stop_node(Node),
+ Socket =/= undefined andalso gen_tcp:close(Socket),
+ true.
+
+forever() ->
+ fun() -> receive after infinity -> ok end end.
+
+
+-spec control(Scope :: atom()) -> {Port :: integer(), pid()}.
+control(Scope) ->
+ Control = self(),
+ erlang:spawn(fun () -> server(Control, Scope) end),
+ receive
+ {port, Port, PgPid} ->
+ {Port, PgPid};
+ Other ->
+ error({error, Other})
+ end.
+
+server(Control, Scope) ->
+ try
+ {ok, Pid} = if Scope =:= undefined -> {ok, undefined}; true -> pg:start(Scope) end,
+ {ok, Listen} = gen_tcp:listen(0, [{mode, binary}, {packet, 4}, {ip, ?LOCALHOST}]),
+ {ok, Port} = inet:port(Listen),
+ Control ! {port, Port, Pid},
+ {ok, Sock} = gen_tcp:accept(Listen),
+ server_loop(Sock)
+ catch
+ Class:Reason:Stack ->
+ Control ! {error, {Class, Reason, Stack}}
+ end.
+
+server_loop(Sock) ->
+ inet:setopts(Sock, [{active, once}]),
+ receive
+ {tcp, Sock, Data} ->
+ {call, M, F, A} = binary_to_term(Data),
+ Ret =
+ try
+ erlang:apply(M, F, A) of
+ Res ->
+ {ok, Res}
+ catch
+ exit:Reason ->
+ {error, {'EXIT', Reason}};
+ error:Reason ->
+ {error, {'EXIT', Reason}}
+ end,
+ ok = gen_tcp:send(Sock, term_to_binary(Ret)),
+ server_loop(Sock);
+ {tcp_closed, Sock} ->
+ erlang:halt(1)
+ end.
diff --git a/lib/kernel/test/rpc_SUITE.erl b/lib/kernel/test/rpc_SUITE.erl
index a89a7600a2..d05db46837 100644
--- a/lib/kernel/test/rpc_SUITE.erl
+++ b/lib/kernel/test/rpc_SUITE.erl
@@ -25,7 +25,13 @@
call/1, block_call/1, multicall/1, multicall_timeout/1,
multicall_dies/1, multicall_node_dies/1,
called_dies/1, called_node_dies/1,
- called_throws/1, call_benchmark/1, async_call/1]).
+ called_throws/1, call_benchmark/1, async_call/1,
+ call_against_old_node/1,
+ multicall_mix/1,
+ timeout_limit/1]).
+-export([init_per_testcase/2, end_per_testcase/2]).
+
+-export([call_func1/1]).
-export([suicide/2, suicide/3, f/0, f2/0]).
@@ -39,11 +45,17 @@ all() ->
[off_heap, call, block_call, multicall, multicall_timeout,
multicall_dies, multicall_node_dies, called_dies,
called_node_dies, called_throws, call_benchmark,
- async_call].
+ async_call, call_against_old_node, multicall_mix, timeout_limit].
groups() ->
[].
+init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
+ [{testcase, Func}|Config].
+
+end_per_testcase(_Func, _Config) ->
+ ok.
+
init_per_suite(Config) ->
Config.
@@ -224,7 +236,6 @@ do_multicall_2_nodes_dies(Mod, Func, Args) ->
ok.
-
%% OTP-3766.
called_dies(Config) when is_list(Config) ->
PA = filename:dirname(code:which(?MODULE)),
@@ -254,20 +265,23 @@ called_dies(Config) when is_list(Config) ->
%%
TrapExit = process_flag(trap_exit, true),
%%
- rep(fun (Tag, Call, Args=[Node|_]) when Node == node() ->
+ rep(fun (Tag, call, Args=[Node|_]) when Node == node() ->
{Tag,timeout} =
- {Tag,apply(rpc, Call, Args)},
+ {Tag,apply(rpc, call, Args)},
{Tag,flush,[{'EXIT',_,normal}]} =
{Tag,flush,flush([])};
(Tag, Call, Args) ->
{Tag,timeout} =
{Tag,apply(rpc, Call, Args)}
end, N, ?MODULE, suicide, [link,normal]),
- rep(fun (Tag, Call, Args=[Node|_]) when Node == node() ->
+ rep(fun (Tag, call, Args=[Node|_]) when Node == node() ->
{Tag,timeout} =
- {Tag,apply(rpc, Call, Args)},
+ {Tag,apply(rpc, call, Args)},
{Tag,flush,[{'EXIT',_,abnormal}]} =
{Tag,flush,flush([])};
+ (Tag, Call, Args=[Node|_]) when Node == node() ->
+ {Tag,timeout} =
+ {Tag,apply(rpc, Call, Args)};
(Tag, block_call, Args) ->
{Tag,timeout} =
{Tag,apply(rpc, block_call, Args)};
@@ -275,20 +289,23 @@ called_dies(Config) when is_list(Config) ->
{Tag,{badrpc,{'EXIT',abnormal}}} =
{Tag,apply(rpc, Call, Args)}
end, N, ?MODULE, suicide, [link,abnormal]),
- rep(fun (Tag, Call, Args=[Node|_]) when Node == node() ->
+ rep(fun (Tag, call, Args=[Node|_]) when Node == node() ->
{Tag,timeout} =
- {Tag,apply(rpc, Call, Args)},
+ {Tag,apply(rpc, call, Args)},
{Tag,flush,[{'EXIT',_,normal}]} =
{Tag,flush,flush([])};
(Tag, Call, Args) ->
{Tag,timeout} =
{Tag,apply(rpc, Call, Args)}
end, N, ?MODULE, suicide, [exit,normal]),
- rep(fun (Tag, Call, Args=[Node|_]) when Node == node() ->
+ rep(fun (Tag, call, Args=[Node|_]) when Node == node() ->
{Tag,timeout} =
- {Tag,apply(rpc, Call, Args)},
+ {Tag,apply(rpc, call, Args)},
{Tag,flush,[{'EXIT',_,abnormal}]} =
{Tag,flush,flush([])};
+ (Tag, Call, Args=[Node|_]) when Node == node() ->
+ {Tag,timeout} =
+ {Tag,apply(rpc, Call, Args)};
(Tag, block_call, Args) ->
{Tag,timeout} =
{Tag,apply(rpc, block_call, Args)};
@@ -370,8 +387,10 @@ called_node_dies(Config) when is_list(Config) ->
PA, ?MODULE, suicide, [init,stop,[]]),
node_rep(
- fun (Call, Args=[_|_]) ->
- {badrpc,{'EXIT',{killed,_}}} = apply(rpc, Call, Args)
+ fun (block_call, Args=[_|_]) ->
+ {badrpc,{'EXIT',{killed,_}}} = apply(rpc, block_call, Args);
+ (call, Args=[_|_]) ->
+ {badrpc,nodedown} = apply(rpc, call, Args)
end, "rpc_SUITE_called_node_dies_3",
PA, ?MODULE, suicide, [erlang,exit,[rex,kill]]),
@@ -380,7 +399,7 @@ called_node_dies(Config) when is_list(Config) ->
%% Cannot block call rpc - will hang
ok;
(Call, Args=[_|_]) ->
- {badrpc,{'EXIT',{normal,_}}} = apply(rpc, Call, Args)
+ {badrpc,nodedown} = apply(rpc, Call, Args)
end, "rpc_SUITE_called_node_dies_4",
PA, ?MODULE, suicide, [rpc,stop,[]]),
@@ -474,10 +493,153 @@ async_call(Config) when is_list(Config) ->
ok.
+call_against_old_node(Config) ->
+ case start_22_node(Config) of
+ {ok, Node22} ->
+ Node22 = rpc:call(Node22, erlang, node, []),
+ stop_node(Node22),
+ ok;
+ _ ->
+ {skipped, "No OTP 22 available"}
+ end.
+
+multicall_mix(Config) ->
+ {ok, Node1} = start_node(Config),
+ {ok, Node2} = start_node(Config),
+ {Node3, OldNodeTest} = case start_22_node(Config) of
+ {ok, N3} ->
+ {N3, true};
+ _ ->
+ {ok, N3} = start_node(Config),
+ {N3, false}
+ end,
+ {ok, Node4} = start_node(Config),
+ {ok, Node5} = start_node(Config),
+ stop_node(Node2),
+
+ ThisNode = node(),
+ Nodes = [ThisNode, Node1, Node2, Node3, Node4, Node5],
+
+ {[ThisNode,
+ Node1,
+ Node3,
+ Node4,
+ Node5],
+ [Node2]}
+ = rpc:multicall(Nodes, erlang, node, []),
+
+ {[BlingError,
+ BlingError,
+ {badrpc, {'EXIT', _}},
+ BlingError,
+ BlingError],
+ [Node2]}
+ = rpc:multicall(Nodes, ?MODULE, call_func1, [bling]),
+
+ {badrpc, {'EXIT',
+ {bling,
+ [{?MODULE, call_func2, A, _},
+ {?MODULE, call_func1, 1, _}]}}} = BlingError,
+ true = (A == 1) orelse (A == [bling]),
+
+ {[], Nodes}
+ = rpc:multicall(Nodes, erlang, processes, [], 0),
+
+ OtherNodes = Nodes -- [ThisNode],
+
+ {[], OtherNodes}
+ = rpc:multicall(OtherNodes, erlang, halt, []),
+
+ case OldNodeTest of
+ true -> {comment, "Test with OTP 22 node as well"};
+ false -> {comment, "Test without OTP 22"}
+ end.
+
+call_func1(X) ->
+ call_func2(X),
+ ok.
+
+call_func2(X) ->
+ erlang:error(X, [X]).
+
+timeout_limit(Config) when is_list(Config) ->
+ Node = node(),
+ MaxTmo = (1 bsl 32) - 1,
+ erlang:send_after(100, self(), dummy_message),
+ try
+ receive
+ M ->
+ M
+ after MaxTmo + 1 ->
+ ok
+ end,
+ ct:fail("The ?MAX_INT_TIMEOUT define in rpc.erl needs "
+ "to be updated to reflect max timeout value "
+ "in a receive/after...")
+ catch
+ error:timeout_value ->
+ ok
+ end,
+ Node = rpc:call(Node, erlang, node, [], MaxTmo),
+ try
+ {badrpc, _} = rpc:call(Node, erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:_ ->
+ ok
+ end,
+ Node = rpc:block_call(Node, erlang, node, [], MaxTmo),
+ try
+ {badrpc, _} = rpc:block_call(Node, erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:_ ->
+ ok
+ end,
+ {[Node],[]} = rpc:multicall([Node], erlang, node, [], MaxTmo),
+ try
+ rpc:multicall([Node], erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:_ ->
+ ok
+ end,
+ ok.
+
+
%%%
%%% Utility functions.
%%%
+start_node(Config) ->
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-" ++ integer_to_list(erlang:system_time(second))
+ ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))),
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{args, "-pa " ++ Pa}]).
+
+start_22_node(Config) ->
+ Rel = "22_latest",
+ case test_server:is_release_available(Rel) of
+ false ->
+ notsup;
+ true ->
+ Cookie = atom_to_list(erlang:get_cookie()),
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-" ++ integer_to_list(erlang:system_time(second))
+ ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))),
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name,
+ peer,
+ [{args, "-pa " ++ Pa ++ " -setcookie "++Cookie},
+ {erl, [{release, Rel}]}])
+ end.
+
+stop_node(Node) ->
+ test_server:stop_node(Node).
+
flush(L) ->
receive
M ->
diff --git a/lib/kernel/test/seq_trace_SUITE.erl b/lib/kernel/test/seq_trace_SUITE.erl
index 52342560b1..d984a071f6 100644
--- a/lib/kernel/test/seq_trace_SUITE.erl
+++ b/lib/kernel/test/seq_trace_SUITE.erl
@@ -31,7 +31,8 @@
trace_exit/1, distributed_exit/1, call/1, port/1,
port_clean_token/1,
match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1,
- send_literal/1]).
+ send_literal/1,inherit_on_spawn/1,inherit_on_dist_spawn/1,
+ dist_spawn_error/1]).
%% internal exports
-export([simple_tracer/2, one_time_receiver/0, one_time_receiver/1,
@@ -56,7 +57,8 @@ all() ->
old_heap_token,
distributed_exit, call, port, match_set_seq_token,
port_clean_token,
- gc_seq_token, label_capability_mismatch].
+ gc_seq_token, label_capability_mismatch,
+ inherit_on_spawn, inherit_on_dist_spawn, dist_spawn_error].
groups() ->
[].
@@ -86,14 +88,27 @@ token_set_get(Config) when is_list(Config) ->
do_token_set_get(timestamp),
do_token_set_get(monotonic_timestamp),
do_token_set_get(strict_monotonic_timestamp).
-
+
+-define(SEQ_TRACE_SEND, 1). %(1 << 0)
+-define(SEQ_TRACE_RECEIVE, 2). %(1 << 1)
+-define(SEQ_TRACE_PRINT, 4). %(1 << 2)
+-define(SEQ_TRACE_NOW_TIMESTAMP, 8). %(1 << 3)
+-define(SEQ_TRACE_STRICT_MON_TIMESTAMP, 16). %(1 << 4)
+-define(SEQ_TRACE_MON_TIMESTAMP, 32). %(1 << 5)
+
do_token_set_get(TsType) ->
- io:format("Testing ~p~n", [TsType]),
+ BaseOpts = ?SEQ_TRACE_SEND bor
+ ?SEQ_TRACE_RECEIVE bor
+ ?SEQ_TRACE_PRINT,
Flags = case TsType of
- timestamp -> 15;
- strict_monotonic_timestamp -> 23;
- monotonic_timestamp -> 39
- end,
+ timestamp ->
+ BaseOpts bor ?SEQ_TRACE_NOW_TIMESTAMP;
+ strict_monotonic_timestamp ->
+ BaseOpts bor ?SEQ_TRACE_STRICT_MON_TIMESTAMP;
+ monotonic_timestamp ->
+ BaseOpts bor ?SEQ_TRACE_MON_TIMESTAMP
+ end,
+ ct:pal("Type ~p, flags = ~p~n", [TsType, Flags]),
Self = self(),
seq_trace:reset_trace(),
%% Test that initial seq_trace is disabled
@@ -208,9 +223,9 @@ do_send_literal(Msg) ->
seq_trace:reset_trace(),
start_tracer(),
Label = make_ref(),
+ Receiver = spawn_link(fun() -> receive ok -> ok end end),
seq_trace:set_token(label,Label),
set_token_flags([send, 'receive', no_timestamp]),
- Receiver = spawn_link(fun() -> receive ok -> ok end end),
[Receiver ! Msg || _ <- lists:seq(1, N)],
erlang:garbage_collect(Receiver),
[Receiver ! Msg || _ <- lists:seq(1, N)],
@@ -482,8 +497,6 @@ call(Config) when is_list(Config) ->
1 =
erlang:trace(Self, true,
[call, set_on_spawn, {tracer, TrB(pid)}]),
- Label = 17,
- seq_trace:set_token(label, Label), % Token enters here!!
RefB = make_ref(),
Pid2B = spawn_link(
fun() ->
@@ -497,6 +510,12 @@ call(Config) when is_list(Config) ->
RefB = call_tracee_1(RefB),
Pid2B ! {self(), msg, RefB}
end),
+
+ %% The token is set *AFTER* spawning to make sure we're testing that the
+ %% token follows on send and not that it inherits on spawn.
+ Label = 17,
+ seq_trace:set_token(label, Label),
+
Pid1B ! {Self, msg, RefB},
%% The message is passed Self -> Pid1B -> Pid2B -> Self, and the
%% seq_trace token follows invisibly. Traced functions are
@@ -517,6 +536,357 @@ call(Config) when is_list(Config) ->
seq_trace:reset_trace(),
ok.
+%% The token should follow spawn, just like it follows messages.
+inherit_on_spawn(Config) when is_list(Config) ->
+ lists:foreach(fun (Test) ->
+ inherit_on_spawn_test(Test)
+ end,
+ [spawn, spawn_link, spawn_monitor,
+ spawn_opt, spawn_request]),
+ ok.
+
+inherit_on_spawn_test(Spawn) ->
+ io:format("Testing ~p()~n", [Spawn]),
+
+ seq_trace:reset_trace(),
+ start_tracer(),
+
+ Ref = make_ref(),
+ seq_trace:set_token(label,Ref),
+ set_token_flags([send,'receive',strict_monotonic_timestamp]),
+
+ Self = self(),
+ GurkaMsg = {gurka,Ref},
+ SpawnFun = fun() -> Self ! GurkaMsg, receive after infinity -> ok end end,
+ {Other, Tag, KnownReqId, KnownSpawnReply}
+ = case Spawn of
+ spawn ->
+ {spawn(SpawnFun), spawn_reply, undefined, undefined};
+ spawn_link ->
+ {spawn_link(SpawnFun), spawn_reply, undefined, undefined};
+ spawn_monitor ->
+ {P, _} = spawn_monitor(SpawnFun),
+ {P, spawn_reply, undefined, undefined};
+ spawn_opt ->
+ {spawn_opt(SpawnFun, [link]), spawn_reply, undefined, undefined};
+ spawn_request ->
+ SReply = make_ref(),
+ RID = spawn_request(SpawnFun, [link, {reply_tag, SReply}]),
+ receive
+ {SReply, RID, ok, P} = M ->
+ {P, SReply, RID, M}
+ end
+ end,
+
+ receive {gurka,Ref} -> ok end,
+ seq_trace:reset_trace(),
+
+ Sequence = lists:keysort(3, stop_tracer(6)),
+ io:format("Sequence: ~p~n", [Sequence]),
+ [SSpawnRequest, RSpawnRequest, SSpawnReply, RSpawnReply,
+ SGurkaMsg, RGurkaMsg] = Sequence,
+
+ %% Spawn request...
+ {Ref,
+ {send,
+ {0,1},
+ Self,Other,
+ ReqMessage},
+ _} = SSpawnRequest,
+
+ spawn_request = element(1, ReqMessage),
+ ReqId = element(2, ReqMessage),
+ case KnownReqId of
+ undefined -> ok;
+ ReqId -> ok
+ end,
+
+ {Ref,
+ {'receive',
+ {0,1},
+ Self,Other,
+ ReqMessage},
+ _} = RSpawnRequest,
+
+ %% Spawn reply...
+ SpawnReply = {Tag,ReqId,ok,Other},
+ {Ref,
+ {send,
+ {1,2},
+ Other,Self,
+ SpawnReply},
+ _} = SSpawnReply,
+
+ case KnownSpawnReply of
+ undefined -> ok;
+ SpawnReply -> ok
+ end,
+
+ {Ref,
+ {'receive',
+ {1,2},
+ Other,Self,
+ SpawnReply},
+ _} = RSpawnReply,
+
+ %% Gurka message...
+ {Ref,
+ {send,
+ {1,3},
+ Other, Self,
+ GurkaMsg},
+ _} = SGurkaMsg,
+
+ {Ref,
+ {'receive',
+ {1,3},
+ Other, Self,
+ GurkaMsg},
+ _} = RGurkaMsg,
+
+ unlink(Other),
+ exit(Other, kill),
+
+ ok.
+
+inherit_on_dist_spawn(Config) when is_list(Config) ->
+ lists:foreach(fun (Test) ->
+ inherit_on_dist_spawn_test(Test)
+ end,
+ [spawn, spawn_link, spawn_monitor,
+ spawn_opt, spawn_request]),
+ ok.
+
+inherit_on_dist_spawn_test(Spawn) ->
+ io:format("Testing ~p()~n", [Spawn]),
+ Pa = "-pa "++filename:dirname(code:which(?MODULE)),
+ {ok, Node} = start_node(seq_trace_dist_spawn, Pa),
+ %% ensure module is loaded on remote node...
+ _ = rpc:call(Node, ?MODULE, module_info, []),
+
+ io:format("Self=~p~n",[self()]),
+
+ seq_trace:reset_trace(),
+ start_tracer(),
+ rpc:call(Node, seq_trace, reset_trace, []),
+ start_tracer(Node),
+
+ Ref = make_ref(),
+ io:format("Ref=~p~n",[Ref]),
+
+ seq_trace:set_token(label,Ref),
+ set_token_flags([send,'receive',strict_monotonic_timestamp]),
+
+ Self = self(),
+
+ GurkaMsg = {gurka,Ref},
+ SpawnFun = fun() -> Self ! GurkaMsg, receive after infinity -> ok end end,
+ {Other, Tag, KnownReqId, KnownSpawnReply}
+ = case Spawn of
+ spawn ->
+ {spawn(Node, SpawnFun), spawn_reply, undefined, undefined};
+ spawn_link ->
+ {spawn_link(Node, SpawnFun), spawn_reply, undefined, undefined};
+ spawn_monitor ->
+ {P, _} = spawn_monitor(Node, SpawnFun),
+ {P, spawn_reply, undefined, undefined};
+ spawn_opt ->
+ {spawn_opt(Node, SpawnFun, [link]), spawn_reply, undefined, undefined};
+ spawn_request ->
+ SReply = make_ref(),
+ RID = spawn_request(Node, SpawnFun, [{reply_tag, SReply}, link]),
+ receive
+ {SReply, RID, ok, P} = M ->
+ {P, SReply, RID, M}
+ end
+ end,
+
+ receive GurkaMsg -> ok end,
+ seq_trace:reset_trace(),
+
+ Sequence = lists:keysort(3,stop_tracer(4)),
+ io:format("Sequence: ~p~n", [Sequence]),
+ [StSpawnRequest, StAList, StSpawnReply, StGurkaMsg] = Sequence,
+
+ %% Spawn request...
+ {Ref,
+ {send,
+ {0,1},
+ Self,Node,
+ ReqMessage},
+ _} = StSpawnRequest,
+
+ spawn_request = element(1, ReqMessage),
+ ReqId = element(2, ReqMessage),
+ case KnownReqId of
+ undefined -> ok;
+ ReqId -> ok
+ end,
+
+ {Ref,
+ {send,
+ {0,2},
+ Self,Node,
+ ArgList},
+ _} = StAList,
+
+ %% Spawn reply...
+ SpawnReply = {Tag,ReqId,ok,Other},
+ case KnownSpawnReply of
+ undefined -> ok;
+ SpawnReply -> ok
+ end,
+
+ {Ref,
+ {'receive',
+ {1,2},
+ Other,Self,
+ SpawnReply},
+ _} = StSpawnReply,
+
+ %% Gurka message...
+ {Ref,
+ {'receive',
+ {2,3},
+ Other, Self,
+ GurkaMsg},
+ _} = StGurkaMsg,
+
+ SequenceNode = lists:keysort(3,stop_tracer(Node, 4)),
+ io:format("SequenceNode: ~p~n", [SequenceNode]),
+ [StSpawnRequestNode, StSpawnReplyNode, StAListNode, StGurkaMsgNode] = SequenceNode,
+
+
+ %% Spawn request...
+ {Ref,
+ {'receive',
+ {0,1},
+ Self,Other,
+ ReqMessage},
+ _} = StSpawnRequestNode,
+
+ %% Spawn reply...
+ {Ref,
+ {send,
+ {1,2},
+ Other,Self,
+ {spawn_reply, ReqId, ok, Other}},
+ _} = StSpawnReplyNode,
+
+ {Ref,
+ {'receive',
+ {0,2},
+ Self,Other,
+ ArgList},
+ _} = StAListNode,
+
+ %% Gurka message...
+ {Ref,
+ {send,
+ {2,3},
+ Other, Self,
+ GurkaMsg},
+ _} = StGurkaMsgNode,
+
+ unlink(Other),
+
+ stop_node(Node),
+
+ ok.
+
+dist_spawn_error(Config) when is_list(Config) ->
+ Pa = "-pa "++filename:dirname(code:which(?MODULE)),
+ {ok, Node} = start_node(seq_trace_dist_spawn, Pa),
+ %% ensure module is loaded on remote node...
+ _ = rpc:call(Node, ?MODULE, module_info, []),
+
+ io:format("Self=~p~n",[self()]),
+
+ seq_trace:reset_trace(),
+ start_tracer(),
+ rpc:call(Node, seq_trace, reset_trace, []),
+ start_tracer(Node),
+
+ Ref = make_ref(),
+ io:format("Ref=~p~n",[Ref]),
+
+ seq_trace:set_token(label,Ref),
+ set_token_flags([send,'receive',strict_monotonic_timestamp]),
+
+ Self = self(),
+ SpawnReplyTag = make_ref(),
+ GurkaMsg = {gurka,Ref},
+ ReqId = spawn_request(Node,
+ fun () ->
+ Self ! GurkaMsg,
+ receive after infinity -> ok end
+ end,
+ [lunk, {reply_tag, SpawnReplyTag}, link]),
+
+ receive
+ {SpawnReplyTag, ReqId, ResType, Err} ->
+ error = ResType,
+ badopt = Err
+ end,
+
+ seq_trace:reset_trace(),
+
+ Sequence = lists:keysort(3,stop_tracer(3)),
+ io:format("Sequence: ~p~n", [Sequence]),
+ [StSpawnRequest, StAList, StSpawnReply] = Sequence,
+
+ %% Spawn request...
+ {Ref,
+ {send,
+ {0,1},
+ Self,Node,
+ ReqMessage},
+ _} = StSpawnRequest,
+
+ spawn_request = element(1, ReqMessage),
+ ReqId = element(2, ReqMessage),
+
+ {Ref,
+ {send,
+ {0,2},
+ Self,Node,
+ _ArgList},
+ _} = StAList,
+
+ %% Spawn reply...
+ ReplyMessage = {SpawnReplyTag,ReqId,error,badopt},
+ {Ref,
+ {'receive',
+ {1,2},
+ Node,Self,
+ ReplyMessage},
+ _} = StSpawnReply,
+
+ SequenceNode = lists:keysort(3,stop_tracer(Node, 2)),
+ io:format("SequenceNode: ~p~n", [SequenceNode]),
+ [StSpawnRequestNode, StSpawnReplyNode] = SequenceNode,
+
+ %% Spawn request...
+ {Ref,
+ {'receive',
+ {0,1},
+ Self,Node,
+ ReqMessage},
+ _} = StSpawnRequestNode,
+
+ %% Spawn reply...
+ {Ref,
+ {send,
+ {1,2},
+ Node,Self,
+ {spawn_reply, ReqId, error, badopt}},
+ _} = StSpawnReplyNode,
+
+ stop_node(Node),
+
+ ok.
+
+
%% Send trace messages to a port.
port(Config) when is_list(Config) ->
lists:foreach(fun (TsType) -> do_port(TsType, Config) end,
@@ -951,24 +1321,52 @@ simple_tracer(Data, DN) ->
end.
stop_tracer(N) when is_integer(N) ->
- case catch (seq_trace_SUITE_tracer ! {stop,N,self()}) of
- {'EXIT', _} ->
- {error, not_started};
- _ ->
- receive
- {tracerlog,Data} ->
- Data
- after 1000 ->
- {error,timeout}
- end
+ stop_tracer(node(), N).
+
+stop_tracer(Node, N) when is_integer(N) ->
+ case rpc:call(Node,erlang,whereis,[seq_trace_SUITE_tracer]) of
+ Pid when is_pid(Pid) ->
+ unlink(Pid),
+ Mon = erlang:monitor(process, Pid),
+ Pid ! {stop,N,self()},
+ receive
+ {'DOWN', Mon, process, Pid, noproc} ->
+ {error, not_started};
+ {'DOWN', Mon, process, Pid, Reason} ->
+ {error, Reason};
+ {tracerlog,Data} ->
+ erlang:demonitor(Mon, [flush]),
+ Data
+ after 5000 ->
+ erlang:demonitor(Mon, [flush]),
+ {error,timeout}
+ end;
+ _ ->
+ {error, not_started}
end.
start_tracer() ->
- stop_tracer(0),
- Pid = spawn(?MODULE,simple_tracer,[[], 0]),
- register(seq_trace_SUITE_tracer,Pid),
- seq_trace:set_system_tracer(Pid),
- Pid.
+ start_tracer(node()).
+
+start_tracer(Node) ->
+ Me = self(),
+ Ref = make_ref(),
+ Pid = spawn_link(Node,
+ fun () ->
+ Self = self(),
+ stop_tracer(0),
+ register(seq_trace_SUITE_tracer,Self),
+ seq_trace:set_system_tracer(Self),
+ Self = seq_trace:get_system_tracer(),
+ Me ! Ref,
+ simple_tracer([], 0)
+ end),
+ receive
+ Ref ->
+ unlink(Pid),
+ Pid
+ end.
+
set_token_flags([]) ->
ok;
@@ -1020,7 +1418,7 @@ check_ts(strict_monotonic_timestamp, Ts) ->
ok.
start_node(Name, Param) ->
- test_server:start_node(Name, slave, [{args, Param}]).
+ test_server:start_node(Name, peer, [{args, Param}]).
stop_node(Node) ->
test_server:stop_node(Node).