diff options
Diffstat (limited to 'lib/kernel')
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). |