diff options
Diffstat (limited to 'lib/kernel')
81 files changed, 5105 insertions, 2840 deletions
diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml index 38c7b5acf1..4e32c1a3a5 100644 --- a/lib/kernel/doc/src/application.xml +++ b/lib/kernel/doc/src/application.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>application</module> + <module since="">application</module> <modulesummary>Generic OTP application functions</modulesummary> <description> <p>In OTP, <em>application</em> denotes a component implementing @@ -67,8 +67,8 @@ </datatypes> <funcs> <func> - <name name="ensure_all_started" arity="1"/> - <name name="ensure_all_started" arity="2"/> + <name name="ensure_all_started" arity="1" since="OTP R16B02"/> + <name name="ensure_all_started" arity="2" since="OTP R16B02"/> <fsummary>Load and start an application and its dependencies, recursively.</fsummary> <desc> <p>Equivalent to calling @@ -85,8 +85,8 @@ </desc> </func> <func> - <name name="ensure_started" arity="1"/> - <name name="ensure_started" arity="2"/> + <name name="ensure_started" arity="1" since="OTP R16B01"/> + <name name="ensure_started" arity="2" since="OTP R16B01"/> <fsummary>Load and start an application.</fsummary> <desc> <p>Equivalent to @@ -95,8 +95,8 @@ </desc> </func> <func> - <name name="get_all_env" arity="0"/> - <name name="get_all_env" arity="1"/> + <name name="get_all_env" arity="0" since=""/> + <name name="get_all_env" arity="1" since=""/> <fsummary>Get the configuration parameters for an application.</fsummary> <desc> <p>Returns the configuration parameters and their values for @@ -108,8 +108,8 @@ </desc> </func> <func> - <name name="get_all_key" arity="0"/> - <name name="get_all_key" arity="1"/> + <name name="get_all_key" arity="0" since=""/> + <name name="get_all_key" arity="1" since=""/> <fsummary>Get the application specification keys.</fsummary> <desc> <p>Returns the application specification keys and their values @@ -122,8 +122,8 @@ </desc> </func> <func> - <name name="get_application" arity="0"/> - <name name="get_application" arity="1"/> + <name name="get_application" arity="0" since=""/> + <name name="get_application" arity="1" since=""/> <fsummary>Get the name of an application containing a certain process or module.</fsummary> <desc> <p>Returns the name of the application to which the process @@ -136,8 +136,8 @@ </desc> </func> <func> - <name name="get_env" arity="1"/> - <name name="get_env" arity="2"/> + <name name="get_env" arity="1" since=""/> + <name name="get_env" arity="2" since=""/> <fsummary>Get the value of a configuration parameter.</fsummary> <desc> <p>Returns the value of configuration parameter <c><anno>Par</anno></c> @@ -153,7 +153,7 @@ </desc> </func> <func> - <name name="get_env" arity="3"/> + <name name="get_env" arity="3" since="OTP R16B"/> <fsummary>Get the value of a configuration parameter using a default.</fsummary> <desc> <p>Works like <seealso marker="#get_env/2"><c>get_env/2</c></seealso> but returns @@ -162,8 +162,8 @@ </desc> </func> <func> - <name name="get_key" arity="1"/> - <name name="get_key" arity="2"/> + <name name="get_key" arity="1" since=""/> + <name name="get_key" arity="2" since=""/> <fsummary>Get the value of an application specification key.</fsummary> <desc> <p>Returns the value of the application specification key @@ -180,8 +180,8 @@ </desc> </func> <func> - <name name="load" arity="1"/> - <name name="load" arity="2"/> + <name name="load" arity="1" since=""/> + <name name="load" arity="2" since=""/> <fsummary>Load an application.</fsummary> <type name="application_spec"/> <type name="application_opt"/> @@ -226,7 +226,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="loaded_applications" arity="0"/> + <name name="loaded_applications" arity="0" since=""/> <fsummary>Get the currently loaded applications.</fsummary> <desc> <p>Returns a list with information about the applications, and included @@ -238,7 +238,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="permit" arity="2"/> + <name name="permit" arity="2" since=""/> <fsummary>Change the permission for an application to run at a node.</fsummary> <desc> <p>Changes the permission for <c><anno>Application</anno></c> to run at @@ -271,8 +271,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="set_env" arity="3"/> - <name name="set_env" arity="4"/> + <name name="set_env" arity="3" since=""/> + <name name="set_env" arity="4" since=""/> <fsummary>Set the value of a configuration parameter.</fsummary> <desc> <p>Sets the value of configuration parameter <c><anno>Par</anno></c> for @@ -302,8 +302,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="start" arity="1"/> - <name name="start" arity="2"/> + <name name="start" arity="1" since=""/> + <name name="start" arity="2" since=""/> <fsummary>Load and start an application.</fsummary> <desc> <p>Starts <c><anno>Application</anno></c>. If it is not loaded, @@ -353,7 +353,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="start_type" arity="0"/> + <name name="start_type" arity="0" since=""/> <fsummary>Get the start type of an ongoing application startup.</fsummary> <desc> <p>This function is intended to be called by a process belonging @@ -370,7 +370,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="stop" arity="1"/> + <name name="stop" arity="1" since=""/> <fsummary>Stop an application.</fsummary> <desc> <p>Stops <c><anno>Application</anno></c>. The application master calls @@ -399,7 +399,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="takeover" arity="2"/> + <name name="takeover" arity="2" since=""/> <fsummary>Take over a distributed application.</fsummary> <desc> <p>Takes over the distributed application @@ -424,7 +424,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="unload" arity="1"/> + <name name="unload" arity="1" since=""/> <fsummary>Unload an application.</fsummary> <desc> <p>Unloads the application specification for <c><anno>Application</anno></c> @@ -435,8 +435,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="unset_env" arity="2"/> - <name name="unset_env" arity="3"/> + <name name="unset_env" arity="2" since=""/> + <name name="unset_env" arity="3" since=""/> <fsummary>Unset the value of a configuration parameter.</fsummary> <desc> <p>Removes the configuration parameter <c><anno>Par</anno></c> and its value @@ -459,8 +459,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="which_applications" arity="0"/> - <name name="which_applications" arity="1"/> + <name name="which_applications" arity="0" since=""/> + <name name="which_applications" arity="1" since=""/> <fsummary>Get the currently running applications.</fsummary> <desc> <p>Returns a list with information about the applications that @@ -484,7 +484,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </section> <funcs> <func> - <name>Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}</name> + <name since="">Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}</name> <fsummary>Start an application.</fsummary> <type> <v>StartType = <seealso marker="#type-start_type"><c>start_type()</c></seealso></v> @@ -526,7 +526,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name>Module:start_phase(Phase, StartType, PhaseArgs) -> ok | {error, Reason}</name> + <name since="">Module:start_phase(Phase, StartType, PhaseArgs) -> ok | {error, Reason}</name> <fsummary>Extended start of an application.</fsummary> <type> <v>Phase = atom()</v> @@ -551,7 +551,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name>Module:prep_stop(State) -> NewState</name> + <name since="">Module:prep_stop(State) -> NewState</name> <fsummary>Prepare an application for termination.</fsummary> <type> <v>State = NewState = term()</v> @@ -569,7 +569,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name>Module:stop(State)</name> + <name since="">Module:stop(State)</name> <fsummary>Clean up after termination of an application.</fsummary> <type> <v>State = term()</v> @@ -585,7 +585,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name>Module:config_change(Changed, New, Removed) -> ok</name> + <name since="">Module:config_change(Changed, New, Removed) -> ok</name> <fsummary>Update the configuration parameters for an application.</fsummary> <type> <v>Changed = [{Par,Val}]</v> diff --git a/lib/kernel/doc/src/auth.xml b/lib/kernel/doc/src/auth.xml index 5901446960..a57da18de9 100644 --- a/lib/kernel/doc/src/auth.xml +++ b/lib/kernel/doc/src/auth.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>auth</module> + <module since="">auth</module> <modulesummary>Erlang network authentication server.</modulesummary> <description> <p>This module is deprecated. For a description of the Magic @@ -42,7 +42,7 @@ </datatypes> <funcs> <func> - <name name="cookie" arity="0"/> + <name name="cookie" arity="0" since=""/> <fsummary>Magic cookie for local node (deprecated).</fsummary> <desc> <p>Use @@ -51,7 +51,7 @@ </desc> </func> <func> - <name name="cookie" arity="1"/> + <name name="cookie" arity="1" since=""/> <fsummary>Set the magic for the local node (deprecated).</fsummary> <type_desc variable="TheCookie"> The cookie can also be specified as a list with a single atom element. @@ -63,7 +63,7 @@ </desc> </func> <func> - <name name="is_auth" arity="1"/> + <name name="is_auth" arity="1" since=""/> <fsummary>Status of communication authorization (deprecated).</fsummary> <desc> <p>Returns <c>yes</c> if communication with <c><anno>Node</anno></c> is @@ -76,7 +76,7 @@ </desc> </func> <func> - <name>node_cookie([Node, Cookie]) -> yes | no</name> + <name since="">node_cookie([Node, Cookie]) -> yes | no</name> <fsummary>Set the magic cookie for a node and verify authorization (deprecated).</fsummary> <type> <v>Node = node()</v> @@ -88,7 +88,7 @@ </desc> </func> <func> - <name name="node_cookie" arity="2"/> + <name name="node_cookie" arity="2" since=""/> <fsummary>Set the magic cookie for a node and verify authorization (deprecated).</fsummary> <desc> <p>Sets the magic cookie of <c><anno>Node</anno></c> to diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml index 69ce4da61c..4aa9e8b9d2 100644 --- a/lib/kernel/doc/src/code.xml +++ b/lib/kernel/doc/src/code.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>code</module> + <module since="">code</module> <modulesummary>Erlang code server.</modulesummary> <description> <p>This module contains the interface to the Erlang @@ -322,7 +322,7 @@ zip:create("mnesia-4.4.7.ez", <funcs> <func> - <name name="set_path" arity="1"/> + <name name="set_path" arity="1" since=""/> <fsummary>Set the code server search path.</fsummary> <desc> <p>Sets the code path to the list of directories <c><anno>Path</anno></c>.</p> @@ -336,15 +336,15 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="get_path" arity="0"/> + <name name="get_path" arity="0" since=""/> <fsummary>Return the code server search path.</fsummary> <desc> <p>Returns the code path.</p> </desc> </func> <func> - <name name="add_path" arity="1"/> - <name name="add_pathz" arity="1"/> + <name name="add_path" arity="1" since=""/> + <name name="add_pathz" arity="1" since=""/> <fsummary>Add a directory to the end of the code path.</fsummary> <type name="add_path_ret"/> <desc> @@ -357,7 +357,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="add_patha" arity="1"/> + <name name="add_patha" arity="1" since=""/> <fsummary>Add a directory to the beginning of the code path.</fsummary> <type name="add_path_ret"/> <desc> @@ -370,8 +370,8 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="add_paths" arity="1"/> - <name name="add_pathsz" arity="1"/> + <name name="add_paths" arity="1" since=""/> + <name name="add_pathsz" arity="1" since=""/> <fsummary>Add directories to the end of the code path.</fsummary> <desc> <p>Adds the directories in <c><anno>Dirs</anno></c> to the end of the code @@ -381,7 +381,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="add_pathsa" arity="1"/> + <name name="add_pathsa" arity="1" since=""/> <fsummary>Add directories to the beginning of the code path.</fsummary> <desc> <p>Traverses <c><anno>Dirs</anno></c> and adds @@ -397,7 +397,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="del_path" arity="1"/> + <name name="del_path" arity="1" since=""/> <fsummary>Delete a directory from the code path.</fsummary> <desc> <p>Deletes a directory from the code path. The argument can be @@ -417,7 +417,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="replace_path" arity="2"/> + <name name="replace_path" arity="2" since=""/> <fsummary>Replace a directory with another in the code path.</fsummary> <desc> <p>Replaces an old occurrence of a directory @@ -441,7 +441,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="load_file" arity="1"/> + <name name="load_file" arity="1" since=""/> <fsummary>Load a module.</fsummary> <type name="load_ret"/> <desc> @@ -460,7 +460,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="load_abs" arity="1"/> + <name name="load_abs" arity="1" since=""/> <fsummary>Load a module, residing in a specified file.</fsummary> <type name="load_ret"/> <type name="loaded_filename"/> @@ -477,7 +477,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="ensure_loaded" arity="1"/> + <name name="ensure_loaded" arity="1" since=""/> <fsummary>Ensure that a module is loaded.</fsummary> <desc> <p>Tries to load a module in the same way as @@ -489,7 +489,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="load_binary" arity="3"/> + <name name="load_binary" arity="3" since=""/> <fsummary>Load object code for a module.</fsummary> <type name="loaded_filename"/> <type name="loaded_ret_atoms"/> @@ -507,7 +507,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="atomic_load" arity="1"/> + <name name="atomic_load" arity="1" since="OTP 19.0"/> <fsummary>Load a list of modules atomically</fsummary> <desc> <p>Tries to load all of the modules in the list @@ -566,7 +566,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="prepare_loading" arity="1"/> + <name name="prepare_loading" arity="1" since="OTP 19.0"/> <fsummary>Prepare a list of modules atomically</fsummary> <desc> <p>Prepares to load the modules in the list @@ -598,7 +598,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="finish_loading" arity="1"/> + <name name="finish_loading" arity="1" since="OTP 19.0"/> <fsummary>Finish loading a list of prepared modules atomically</fsummary> <desc> <p>Tries to load code for all modules that have been previously @@ -627,7 +627,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="ensure_modules_loaded" arity="1"/> + <name name="ensure_modules_loaded" arity="1" since="OTP 19.0"/> <fsummary>Ensure that a list of modules is loaded</fsummary> <desc> <p>Tries to load any modules not already loaded in the list @@ -639,7 +639,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="delete" arity="1"/> + <name name="delete" arity="1" since=""/> <fsummary>Remove current code for a module.</fsummary> <desc> <p>Removes the current code for <c><anno>Module</anno></c>, that is, @@ -652,7 +652,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="purge" arity="1"/> + <name name="purge" arity="1" since=""/> <fsummary>Remove old code for a module.</fsummary> <desc> <p>Purges the code for <c><anno>Module</anno></c>, that is, removes code @@ -668,7 +668,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="soft_purge" arity="1"/> + <name name="soft_purge" arity="1" since=""/> <fsummary>Remove old code for a module, unless no process uses it.</fsummary> <desc> <p>Purges the code for <c><anno>Module</anno></c>, that is, removes code @@ -683,7 +683,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="is_loaded" arity="1"/> + <name name="is_loaded" arity="1" since=""/> <fsummary>Check if a module is loaded.</fsummary> <type name="loaded_filename"/> <type name="loaded_ret_atoms"/> @@ -702,7 +702,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="all_loaded" arity="0"/> + <name name="all_loaded" arity="0" since=""/> <fsummary>Get all loaded modules.</fsummary> <type name="loaded_filename"/> <type name="loaded_ret_atoms"/> @@ -716,7 +716,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="which" arity="1"/> + <name name="which" arity="1" since=""/> <fsummary>The object code file of a module.</fsummary> <type name="loaded_ret_atoms"/> <desc> @@ -731,7 +731,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="get_object_code" arity="1"/> + <name name="get_object_code" arity="1" since=""/> <fsummary>Gets the object code for a module.</fsummary> <desc> <p>Searches the code path for the object code of module @@ -750,7 +750,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="root_dir" arity="0"/> + <name name="root_dir" arity="0" since=""/> <fsummary>Root directory of Erlang/OTP.</fsummary> <desc> <p>Returns the root directory of Erlang/OTP, which is @@ -762,7 +762,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="lib_dir" arity="0"/> + <name name="lib_dir" arity="0" since=""/> <fsummary>Library directory of Erlang/OTP.</fsummary> <desc> <p>Returns the library directory, <c>$OTPROOT/lib</c>, where @@ -774,7 +774,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="lib_dir" arity="1"/> + <name name="lib_dir" arity="1" since=""/> <fsummary>Library directory for an application.</fsummary> <desc> <p>Returns the path @@ -807,7 +807,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="lib_dir" arity="2"/> + <name name="lib_dir" arity="2" since=""/> <fsummary>Subdirectory for an application.</fsummary> <desc> <p>Returns the path to a subdirectory directly under the top @@ -827,7 +827,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="compiler_dir" arity="0"/> + <name name="compiler_dir" arity="0" since=""/> <fsummary>Library directory for the compiler.</fsummary> <desc> <p>Returns the compiler library directory. Equivalent to @@ -835,7 +835,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="priv_dir" arity="1"/> + <name name="priv_dir" arity="1" since=""/> <fsummary>Priv directory for an application.</fsummary> <desc> <p>Returns the path to the <c>priv</c> directory in an @@ -846,7 +846,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="objfile_extension" arity="0"/> + <name name="objfile_extension" arity="0" since=""/> <fsummary>Object code file extension.</fsummary> <desc> <p>Returns the object code file extension corresponding to @@ -854,7 +854,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="stick_dir" arity="1"/> + <name name="stick_dir" arity="1" since=""/> <fsummary>Mark a directory as sticky.</fsummary> <desc> <p>Marks <c><anno>Dir</anno></c> as sticky.</p> @@ -862,7 +862,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="unstick_dir" arity="1"/> + <name name="unstick_dir" arity="1" since=""/> <fsummary>Remove a sticky directory mark.</fsummary> <desc> <p>Unsticks a directory that is marked as @@ -871,7 +871,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="is_sticky" arity="1"/> + <name name="is_sticky" arity="1" since=""/> <fsummary>Test if a module is sticky.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Module</anno></c> is the @@ -882,7 +882,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="where_is_file" arity="1"/> + <name name="where_is_file" arity="1" since=""/> <fsummary>Full name of a file located in the code path.</fsummary> <desc> <p>Searches the code path for <c><anno>Filename</anno></c>, a file of @@ -893,7 +893,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="clash" arity="0"/> + <name name="clash" arity="0" since=""/> <fsummary>Search for modules with identical names.</fsummary> <desc> <p>Searches all directories in the code path for module names with @@ -901,7 +901,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="module_status" arity="1"/> + <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> <desc> <p>Returns:</p> @@ -934,7 +934,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="modified_modules" arity="0"/> + <name name="modified_modules" arity="0" since="OTP 20.0"/> <fsummary>Return a list of all modules modified on disk.</fsummary> <desc> <p>Returns the list of all currently loaded modules for which @@ -943,7 +943,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="is_module_native" arity="1"/> + <name name="is_module_native" arity="1" since=""/> <fsummary>Test if a module has native code.</fsummary> <desc> <p>Returns:</p> @@ -961,7 +961,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </func> <func> - <name name="get_mode" arity="0"/> + <name name="get_mode" arity="0" since="OTP R16B"/> <fsummary>The mode of the code server.</fsummary> <desc> <p>Returns an atom describing the mode of the code server: diff --git a/lib/kernel/doc/src/disk_log.xml b/lib/kernel/doc/src/disk_log.xml index 884cb32c0c..e308b06f3c 100644 --- a/lib/kernel/doc/src/disk_log.xml +++ b/lib/kernel/doc/src/disk_log.xml @@ -34,7 +34,7 @@ <rev>D</rev> <file>disk_log.sgml</file> </header> - <module>disk_log</module> + <module since="">disk_log</module> <modulesummary>A disk-based term logging facility.</modulesummary> <description> <p><c>disk_log</c> is a disk-based term logger that enables @@ -238,7 +238,7 @@ </datatypes> <funcs> <func> - <name name="accessible_logs" arity="0"/> + <name name="accessible_logs" arity="0" since=""/> <fsummary>Return the accessible disk logs on the current node.</fsummary> <desc> <p>Returns the names of the disk logs accessible on the current node. @@ -248,8 +248,8 @@ </desc> </func> <func> - <name name="alog" arity="2"/> - <name name="balog" arity="2"/> + <name name="alog" arity="2" since=""/> + <name name="balog" arity="2" since=""/> <fsummary>Asynchronously log an item on to a disk log.</fsummary> <type variable="Log"/> <type variable="Term" name_i="1"/> @@ -275,8 +275,8 @@ </desc> </func> <func> - <name name="alog_terms" arity="2"/> - <name name="balog_terms" arity="2"/> + <name name="alog_terms" arity="2" since=""/> + <name name="balog_terms" arity="2" since=""/> <fsummary>Asynchronously log many items on to a disk log.</fsummary> <type variable="Log"/> <type variable="TermList" name_i="1"/> @@ -303,8 +303,8 @@ </desc> </func> <func> - <name name="block" arity="1"/> - <name name="block" arity="2"/> + <name name="block" arity="1" since=""/> + <name name="block" arity="2" since=""/> <fsummary>Block a disk log.</fsummary> <type name="block_error_rsn"/> <desc> @@ -330,21 +330,21 @@ </desc> </func> <func> - <name name="change_header" arity="2"/> + <name name="change_header" arity="2" since=""/> <fsummary>Change option head or head_func for an owner of a disk log.</fsummary> <desc> <p>Changes the value of option <c>head</c> or <c>head_func</c> for an owner of a disk log.</p> </desc> </func> <func> - <name name="change_notify" arity="3"/> + <name name="change_notify" arity="3" since=""/> <fsummary>Change option notify for an owner of a disk log.</fsummary> <desc> <p>Changes the value of option <c>notify</c> for an owner of a disk log. </p> </desc> </func> <func> - <name name="change_size" arity="2"/> + <name name="change_size" arity="2" since=""/> <fsummary>Change the size of an open disk log.</fsummary> <desc> <p>Changes the size of an open log. @@ -384,10 +384,10 @@ </desc> </func> <func> - <name name="chunk" arity="2"/> - <name name="chunk" arity="3"/> - <name name="bchunk" arity="2"/> - <name name="bchunk" arity="3"/> + <name name="chunk" arity="2" since=""/> + <name name="chunk" arity="3" since=""/> + <name name="bchunk" arity="2" since=""/> + <name name="bchunk" arity="3" since=""/> <fsummary>Read a chunk of items written to a disk log.</fsummary> <type variable="Log"/> <type variable="Continuation"/> @@ -447,7 +447,7 @@ </desc> </func> <func> - <name name="chunk_info" arity="1"/> + <name name="chunk_info" arity="1" since=""/> <fsummary>Return information about a chunk continuation of a disk log.</fsummary> <desc> <p>Returns the pair <c>{node, <anno>Node</anno>}</c>, @@ -457,7 +457,7 @@ </desc> </func> <func> - <name name="chunk_step" arity="3"/> + <name name="chunk_step" arity="3" since=""/> <fsummary>Step forward or backward among the wrap log files of a disk log.</fsummary> <desc> <p>Can be used with <c>chunk/2,3</c> and <c>bchunk/2,3</c> @@ -480,7 +480,7 @@ </desc> </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a disk log.</fsummary> <type name="close_error_rsn"/> <desc> @@ -505,7 +505,7 @@ </desc> </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Return an English description of a disk log error reply.</fsummary> <desc> <p>Given the error returned by any function in this module, @@ -517,7 +517,7 @@ </desc> </func> <func> - <name name="inc_wrap_file" arity="1"/> + <name name="inc_wrap_file" arity="1" since=""/> <fsummary>Change to the next wrap log file of a disk log.</fsummary> <type name="inc_wrap_error_rsn"/> <type name="invalid_header"/> @@ -534,7 +534,7 @@ </desc> </func> <func> - <name name="info" arity="1"/> + <name name="info" arity="1" since=""/> <fsummary>Return information about a disk log.</fsummary> <type name="dlog_info"/> <desc> @@ -685,8 +685,8 @@ </desc> </func> <func> - <name name="lclose" arity="1"/> - <name name="lclose" arity="2"/> + <name name="lclose" arity="1" since=""/> + <name name="lclose" arity="2" since=""/> <fsummary>Close a disk log on one node.</fsummary> <type name="lclose_error_rsn"/> <desc> @@ -704,8 +704,8 @@ </desc> </func> <func> - <name name="log" arity="2"/> - <name name="blog" arity="2"/> + <name name="log" arity="2" since=""/> + <name name="blog" arity="2" since=""/> <fsummary>Log an item onto a disk log.</fsummary> <type variable="Log"/> <type variable="Term" name_i="1"/> @@ -739,8 +739,8 @@ </desc> </func> <func> - <name name="log_terms" arity="2"/> - <name name="blog_terms" arity="2"/> + <name name="log_terms" arity="2" since=""/> + <name name="blog_terms" arity="2" since=""/> <fsummary>Log many items onto a disk log.</fsummary> <type variable="Log"/> <type variable="TermList" name_i="1"/> @@ -768,7 +768,7 @@ </desc> </func> <func> - <name name="open" arity="1"/> + <name name="open" arity="1" since=""/> <fsummary>Open a disk log file.</fsummary> <type name="dlog_options"/> <type name="dlog_option"/> @@ -1041,7 +1041,7 @@ </desc> </func> <func> - <name name="pid2name" arity="1"/> + <name name="pid2name" arity="1" since=""/> <fsummary>Return the name of the disk log handled by a pid.</fsummary> <desc> <p>Returns the log name @@ -1053,9 +1053,9 @@ </desc> </func> <func> - <name name="reopen" arity="2"/> - <name name="reopen" arity="3"/> - <name name="breopen" arity="3"/> + <name name="reopen" arity="2" since=""/> + <name name="reopen" arity="3" since=""/> + <name name="breopen" arity="3" since=""/> <fsummary>Reopen a disk log and save the old log.</fsummary> <type variable="Log"/> <type variable="File" name_i="1"/> @@ -1087,7 +1087,7 @@ </desc> </func> <func> - <name name="sync" arity="1"/> + <name name="sync" arity="1" since=""/> <fsummary>Flush the contents of a disk log to the disk.</fsummary> <type name="sync_error_rsn"/> <desc> @@ -1097,9 +1097,9 @@ </desc> </func> <func> - <name name="truncate" arity="1"/> - <name name="truncate" arity="2"/> - <name name="btruncate" arity="2"/> + <name name="truncate" arity="1" since=""/> + <name name="truncate" arity="2" since=""/> + <name name="btruncate" arity="2" since=""/> <fsummary>Truncate a disk log.</fsummary> <type variable="Log"/> <type variable="Head" name_i="2"/> @@ -1129,7 +1129,7 @@ </desc> </func> <func> - <name name="unblock" arity="1"/> + <name name="unblock" arity="1" since=""/> <fsummary>Unblock a disk log.</fsummary> <type name="unblock_error_rsn"/> <desc> diff --git a/lib/kernel/doc/src/erl_boot_server.xml b/lib/kernel/doc/src/erl_boot_server.xml index 4109251387..89f9855c49 100644 --- a/lib/kernel/doc/src/erl_boot_server.xml +++ b/lib/kernel/doc/src/erl_boot_server.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>erl_boot_server</module> + <module since="">erl_boot_server</module> <modulesummary>Boot server for other Erlang machines.</modulesummary> <description> <p>This server is used to assist diskless Erlang nodes that fetch @@ -52,14 +52,14 @@ </description> <funcs> <func> - <name name="add_slave" arity="1"/> + <name name="add_slave" arity="1" since=""/> <fsummary>Add a slave to the list of allowed slaves.</fsummary> <desc> <p>Adds a <c><anno>Slave</anno></c> node to the list of allowed slave hosts.</p> </desc> </func> <func> - <name name="delete_slave" arity="1"/> + <name name="delete_slave" arity="1" since=""/> <fsummary>Delete a slave from the list of allowed slaves.</fsummary> <desc> <p>Deletes a <c><anno>Slave</anno></c> node from the list of allowed slave @@ -67,7 +67,7 @@ </desc> </func> <func> - <name name="start" arity="1"/> + <name name="start" arity="1" since=""/> <fsummary>Start the boot server.</fsummary> <desc> <p>Starts the boot server. <c><anno>Slaves</anno></c> is a list of @@ -76,7 +76,7 @@ </desc> </func> <func> - <name name="start_link" arity="1"/> + <name name="start_link" arity="1" since=""/> <fsummary>Start the boot server and link to the the caller.</fsummary> <desc> <p>Starts the boot server and links to the caller. This function @@ -85,7 +85,7 @@ </desc> </func> <func> - <name name="which_slaves" arity="0"/> + <name name="which_slaves" arity="0" since=""/> <fsummary>Return the current list of allowed slave hosts.</fsummary> <desc> <p>Returns the current list of allowed slave hosts.</p> diff --git a/lib/kernel/doc/src/erl_ddll.xml b/lib/kernel/doc/src/erl_ddll.xml index 75114e015c..f2d5e1b397 100644 --- a/lib/kernel/doc/src/erl_ddll.xml +++ b/lib/kernel/doc/src/erl_ddll.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>erl_ddll</module> + <module since="">erl_ddll</module> <modulesummary>Dynamic driver loader and linker.</modulesummary> <description> <p>This module provides an interface for loading @@ -196,7 +196,7 @@ </datatypes> <funcs> <func> - <name name="demonitor" arity="1"/> + <name name="demonitor" arity="1" since=""/> <fsummary>Remove a monitor for a driver.</fsummary> <desc> <p>Removes a driver monitor in much the same way as @@ -212,7 +212,7 @@ </desc> </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Format an error descriptor.</fsummary> <desc> <p>Takes an <c><anno>ErrorDesc</anno></c> returned by load, unload, or @@ -229,7 +229,7 @@ </desc> </func> <func> - <name name="info" arity="0"/> + <name name="info" arity="0" since=""/> <fsummary>Retrieve information about all drivers.</fsummary> <desc> <p>Returns a list of tuples <c>{<anno>DriverName</anno>, <anno>InfoList</anno>}</c>, @@ -240,7 +240,7 @@ </desc> </func> <func> - <name name="info" arity="1"/> + <name name="info" arity="1" since=""/> <fsummary>Retrieve information about one driver.</fsummary> <desc> <p>Returns a list of tuples <c>{<anno>Tag</anno>, <anno>Value</anno>}</c>, @@ -266,7 +266,7 @@ </desc> </func> <func> - <name name="info" arity="2"/> + <name name="info" arity="2" since=""/> <fsummary>Retrieve specific information about one driver.</fsummary> <desc> <p>Returns specific information about one aspect of a driver. @@ -328,7 +328,7 @@ </desc> </func> <func> - <name name="load" arity="2"/> + <name name="load" arity="2" since=""/> <fsummary>Load a driver.</fsummary> <desc> <p>Loads and links the dynamic driver <c><anno>Name</anno></c>. @@ -390,7 +390,7 @@ </desc> </func> <func> - <name name="load_driver" arity="2"/> + <name name="load_driver" arity="2" since=""/> <fsummary>Load a driver.</fsummary> <desc> <p>Works essentially as <c>load/2</c>, but loads the driver @@ -413,7 +413,7 @@ </desc> </func> <func> - <name name="loaded_drivers" arity="0"/> + <name name="loaded_drivers" arity="0" since=""/> <fsummary>List loaded drivers.</fsummary> <desc> <p>Returns a list of all the available drivers, both @@ -425,7 +425,7 @@ </desc> </func> <func> - <name name="monitor" arity="2"/> + <name name="monitor" arity="2" since=""/> <fsummary>Create a monitor for a driver.</fsummary> <desc> <p>Creates a driver monitor and works in many @@ -588,7 +588,7 @@ </desc> </func> <func> - <name name="reload" arity="2"/> + <name name="reload" arity="2" since=""/> <fsummary>Replace a driver.</fsummary> <desc> <p>Reloads the driver named <c><anno>Name</anno></c> from a possibly @@ -626,7 +626,7 @@ </desc> </func> <func> - <name name="reload_driver" arity="2"/> + <name name="reload_driver" arity="2" since=""/> <fsummary>Replace a driver.</fsummary> <desc> <p>Works exactly as <seealso marker="#reload/2"><c>reload/2</c></seealso>, @@ -644,7 +644,7 @@ </desc> </func> <func> - <name name="try_load" arity="3"/> + <name name="try_load" arity="3" since=""/> <fsummary>Load a driver.</fsummary> <desc> <p>Provides more control than the @@ -931,7 +931,7 @@ </desc> </func> <func> - <name name="try_unload" arity="2"/> + <name name="try_unload" arity="2" since=""/> <fsummary>Unload a driver.</fsummary> <desc> <p>This is the low-level function to unload (or decrement @@ -1116,7 +1116,7 @@ </desc> </func> <func> - <name name="unload" arity="1"/> + <name name="unload" arity="1" since=""/> <fsummary>Unload a driver.</fsummary> <desc> <p>Unloads, or at least dereferences the driver named @@ -1143,7 +1143,7 @@ </desc> </func> <func> - <name name="unload_driver" arity="1"/> + <name name="unload_driver" arity="1" since=""/> <fsummary>Unload a driver.</fsummary> <desc> <p>Unloads, or at least dereferences the driver named diff --git a/lib/kernel/doc/src/erl_epmd.xml b/lib/kernel/doc/src/erl_epmd.xml index 8b076cd2d7..2adbf11a28 100644 --- a/lib/kernel/doc/src/erl_epmd.xml +++ b/lib/kernel/doc/src/erl_epmd.xml @@ -28,7 +28,7 @@ <date>2018-02-19</date> <rev>A</rev> </header> - <module>erl_epmd</module> + <module since="OTP R14B">erl_epmd</module> <modulesummary> Erlang interface towards epmd </modulesummary> @@ -41,7 +41,7 @@ <funcs> <func> - <name name="start_link" arity="0"/> + <name name="start_link" arity="0" since="OTP 21.0"/> <fsummary>Callback for erl_distribution supervisor.</fsummary> <desc> <p>This function is invoked as this module is added as a child of the @@ -50,8 +50,8 @@ </func> <func> - <name name="register_node" arity="2"/> - <name name="register_node" arity="3"/> + <name name="register_node" arity="2" since="OTP 21.0"/> + <name name="register_node" arity="3" since="OTP 21.0"/> <fsummary>Registers the node with <c>epmd</c>.</fsummary> <desc> <p>Registers the node with <c>epmd</c> and tells epmd what port will be @@ -62,8 +62,8 @@ </func> <func> - <name name="port_please" arity="2"/> - <name name="port_please" arity="3"/> + <name name="port_please" arity="2" since="OTP 21.0"/> + <name name="port_please" arity="3" since="OTP 21.0"/> <fsummary>Returns the port number for a given node.</fsummary> <desc> <p>Requests the distribution port for the given node of an EPMD @@ -73,7 +73,7 @@ </func> <func> - <name name="address_please" arity="3"/> + <name name="address_please" arity="3" since="OTP 21.0"/> <fsummary>Returns address and port.</fsummary> <desc> <p>Called by the distribution module. Resolves the <c>Host</c> to an IP @@ -84,7 +84,7 @@ </func> <func> - <name name="names" arity="1"/> + <name name="names" arity="1" since="OTP 21.0"/> <fsummary>Names of Erlang nodes at a host.</fsummary> <desc> <p>Called by <seealso marker="net_adm"><c>net_adm:names/0</c></seealso>. diff --git a/lib/kernel/doc/src/error_handler.xml b/lib/kernel/doc/src/error_handler.xml index e5639487dc..eb01e87aee 100644 --- a/lib/kernel/doc/src/error_handler.xml +++ b/lib/kernel/doc/src/error_handler.xml @@ -30,7 +30,7 @@ <date></date> <rev></rev> </header> - <module>error_handler</module> + <module since="">error_handler</module> <modulesummary>Default system error handler.</modulesummary> <description> <p>This module defines what happens when certain types @@ -38,7 +38,7 @@ </description> <funcs> <func> - <name name="raise_undef_exception" arity="3"/> + <name name="raise_undef_exception" arity="3" since="OTP R16B"/> <fsummary>Raise an undef exception.</fsummary> <type_desc variable="Args"> A (possibly empty) list of arguments <c>Arg1,..,ArgN</c> @@ -51,7 +51,7 @@ </desc> </func> <func> - <name name="undefined_function" arity="3"/> + <name name="undefined_function" arity="3" since=""/> <fsummary>Called when an undefined function is encountered.</fsummary> <type_desc variable="Args"> A (possibly empty) list of arguments <c>Arg1,..,ArgN</c> @@ -93,7 +93,7 @@ </desc> </func> <func> - <name name="undefined_lambda" arity="3"/> + <name name="undefined_lambda" arity="3" since=""/> <fsummary>Called when an undefined lambda (fun) is encountered.</fsummary> <type_desc variable="Args"> A (possibly empty) list of arguments <c>Arg1,..,ArgN</c> diff --git a/lib/kernel/doc/src/error_logger.xml b/lib/kernel/doc/src/error_logger.xml index c3d68fd79f..c170b4fa34 100644 --- a/lib/kernel/doc/src/error_logger.xml +++ b/lib/kernel/doc/src/error_logger.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>error_logger</module> + <module since="">error_logger</module> <modulesummary>Erlang error logger.</modulesummary> <description> @@ -76,8 +76,8 @@ </datatypes> <funcs> <func> - <name name="add_report_handler" arity="1"/> - <name name="add_report_handler" arity="2"/> + <name name="add_report_handler" arity="1" since=""/> + <name name="add_report_handler" arity="2" since=""/> <fsummary>Add an event handler to the error logger.</fsummary> <desc> <p>Adds a new event handler to the error logger. The event @@ -96,7 +96,7 @@ </desc> </func> <func> - <name name="delete_report_handler" arity="1"/> + <name name="delete_report_handler" arity="1" since=""/> <fsummary>Delete an event handler from the error logger.</fsummary> <desc> <p>Deletes an event handler from the error logger by calling @@ -108,9 +108,9 @@ </desc> </func> <func> - <name name="error_msg" arity="1"/> - <name name="error_msg" arity="2"/> - <name name="format" arity="2"/> + <name name="error_msg" arity="1" since=""/> + <name name="error_msg" arity="2" since=""/> + <name name="format" arity="2" since=""/> <fsummary>Log a standard error event.</fsummary> <desc> <p>Log a standard error event. The <c><anno>Format</anno></c> @@ -142,7 +142,7 @@ ok</pre> </desc> </func> <func> - <name name="error_report" arity="1"/> + <name name="error_report" arity="1" since=""/> <fsummary>Log a standard error event.</fsummary> <desc> <p>Log a standard error event. Error logger forwards the event @@ -169,7 +169,7 @@ ok</pre> </desc> </func> <func> - <name name="error_report" arity="2"/> + <name name="error_report" arity="2" since=""/> <fsummary>Log a user-defined error event.</fsummary> <desc> <p>Log a user-defined error event. Error logger forwards the @@ -191,7 +191,7 @@ ok</pre> </desc> </func> <func> - <name name="get_format_depth" arity="0"/> + <name name="get_format_depth" arity="0" since="OTP 20.0"/> <fsummary>Get the value of the Kernel application variable <c>error_logger_format_depth</c>.</fsummary> <desc> @@ -211,8 +211,8 @@ ok</pre> </desc> </func> <func> - <name name="info_msg" arity="1"/> - <name name="info_msg" arity="2"/> + <name name="info_msg" arity="1" since=""/> + <name name="info_msg" arity="2" since=""/> <fsummary>Log a standard information event.</fsummary> <desc> <p>Log a standard information event. The <c><anno>Format</anno></c> @@ -244,7 +244,7 @@ ok</pre> </desc> </func> <func> - <name name="info_report" arity="1"/> + <name name="info_report" arity="1" since=""/> <fsummary>Log a standard information event.</fsummary> <desc> <p>Log a standard information event. Error logger forwards the @@ -271,7 +271,7 @@ ok</pre> </desc> </func> <func> - <name name="info_report" arity="2"/> + <name name="info_report" arity="2" since=""/> <fsummary>Log a user-defined information event.</fsummary> <desc> <p>Log a user-defined information event. Error logger forwards @@ -294,9 +294,9 @@ ok</pre> </desc> </func> <func> - <name name="logfile" arity="1" clause_i="1"/> - <name name="logfile" arity="1" clause_i="2"/> - <name name="logfile" arity="1" clause_i="3"/> + <name name="logfile" arity="1" clause_i="1" since=""/> + <name name="logfile" arity="1" clause_i="2" since=""/> + <name name="logfile" arity="1" clause_i="3" since=""/> <fsummary>Enable or disable error printouts to a file.</fsummary> <type variable="Filename"/> <type variable="OpenReason" name_i="1"/> @@ -346,7 +346,7 @@ ok</pre> </desc> </func> <func> - <name name="tty" arity="1"/> + <name name="tty" arity="1" since=""/> <fsummary>Enable or disable printouts to the terminal.</fsummary> <desc> <p>Enables (<c><anno>Flag</anno> == true</c>) or disables @@ -363,7 +363,7 @@ ok</pre> </desc> </func> <func> - <name name="warning_map" arity="0"/> + <name name="warning_map" arity="0" since=""/> <fsummary>Return the current mapping for warning events.</fsummary> <desc> <p>Returns the current mapping for warning events. Events sent @@ -400,8 +400,8 @@ ok</pre> </desc> </func> <func> - <name name="warning_msg" arity="1"/> - <name name="warning_msg" arity="2"/> + <name name="warning_msg" arity="1" since=""/> + <name name="warning_msg" arity="2" since=""/> <fsummary>Log a standard warning event.</fsummary> <desc> <p>Log a standard warning event. The <c><anno>Format</anno></c> @@ -429,7 +429,7 @@ ok</pre> </desc> </func> <func> - <name name="warning_report" arity="1"/> + <name name="warning_report" arity="1" since=""/> <fsummary>Log a standard warning event.</fsummary> <desc> <p>Log a standard warning event. Error logger forwards the event @@ -446,7 +446,7 @@ ok</pre> </desc> </func> <func> - <name name="warning_report" arity="2"/> + <name name="warning_report" arity="2" since=""/> <fsummary>Log a user-defined warning event.</fsummary> <desc> <p>Log a user-defined warning event. Error logger forwards the diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index 9acaf6b41e..fc25e83d40 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>file</module> + <module since="">file</module> <modulesummary>File interface module.</modulesummary> <description> <p>This module provides an interface to the file system.</p> @@ -186,7 +186,7 @@ <funcs> <func> - <name name="advise" arity="4"/> + <name name="advise" arity="4" since="OTP R14B"/> <fsummary>Predeclare an access pattern for file data.</fsummary> <type name="posix_file_advise"/> <desc> @@ -197,7 +197,7 @@ </desc> </func> <func> - <name name="allocate" arity="3"/> + <name name="allocate" arity="3" since="OTP R16B"/> <fsummary>Allocate file space.</fsummary> <desc> <p><c>allocate/3</c> can be used to preallocate space for a file.</p> @@ -209,7 +209,7 @@ </desc> </func> <func> - <name name="change_group" arity="2"/> + <name name="change_group" arity="2" since=""/> <fsummary>Change group of a file.</fsummary> <desc> <p>Changes group of a file. See @@ -217,7 +217,7 @@ </desc> </func> <func> - <name name="change_mode" arity="2"/> + <name name="change_mode" arity="2" since="OTP R14B"/> <fsummary>Change permissions of a file.</fsummary> <desc> <p>Changes permissions of a file. See @@ -225,7 +225,7 @@ </desc> </func> <func> - <name name="change_owner" arity="2"/> + <name name="change_owner" arity="2" since=""/> <fsummary>Change owner of a file.</fsummary> <desc> <p>Changes owner of a file. See @@ -233,7 +233,7 @@ </desc> </func> <func> - <name name="change_owner" arity="3"/> + <name name="change_owner" arity="3" since=""/> <fsummary>Change owner and group of a file.</fsummary> <desc> <p>Changes owner and group of a file. See @@ -241,7 +241,7 @@ </desc> </func> <func> - <name name="change_time" arity="2"/> + <name name="change_time" arity="2" since=""/> <fsummary>Change the modification time of a file.</fsummary> <desc> <p>Changes the modification and access times of a file. See @@ -249,7 +249,7 @@ </desc> </func> <func> - <name name="change_time" arity="3"/> + <name name="change_time" arity="3" since=""/> <fsummary>Change the modification and last access time of a file.</fsummary> <desc> <p>Changes the modification and last access times of a file. See @@ -257,7 +257,7 @@ </desc> </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a file.</fsummary> <desc> <p>Closes the file referenced by <c><anno>IoDevice</anno></c>. It mostly @@ -270,7 +270,7 @@ </desc> </func> <func> - <name name="consult" arity="1"/> + <name name="consult" arity="1" since=""/> <fsummary>Read Erlang terms from a file.</fsummary> <desc> <p>Reads Erlang terms, separated by '.', from @@ -308,8 +308,8 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="copy" arity="2"/> - <name name="copy" arity="3"/> + <name name="copy" arity="2" since=""/> + <name name="copy" arity="3" since=""/> <fsummary>Copy file contents.</fsummary> <desc> <p>Copies <c><anno>ByteCount</anno></c> bytes from @@ -346,7 +346,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="datasync" arity="1"/> + <name name="datasync" arity="1" since="OTP R14B"/> <fsummary>Synchronize the in-memory data of a file, ignoring most of its metadata, with that on the physical medium.</fsummary> <desc> <p>Ensures that any buffers kept by the operating system @@ -369,7 +369,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="del_dir" arity="1"/> + <name name="del_dir" arity="1" since=""/> <fsummary>Delete a directory.</fsummary> <desc> <p>Tries to delete directory <c><anno>Dir</anno></c>. @@ -405,7 +405,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="delete" arity="1"/> + <name name="delete" arity="1" since=""/> <fsummary>Delete a file.</fsummary> <desc> <p>Tries to delete file <c><anno>Filename</anno></c>. @@ -442,7 +442,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="eval" arity="1"/> + <name name="eval" arity="1" since=""/> <fsummary>Evaluate Erlang expressions in a file.</fsummary> <desc> <p>Reads and evaluates Erlang expressions, separated by '.' (or @@ -476,7 +476,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="eval" arity="2"/> + <name name="eval" arity="2" since=""/> <fsummary>Evaluate Erlang expressions in a file.</fsummary> <desc> <p>The same as <c>eval/1</c>, but the variable bindings @@ -486,7 +486,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Return a descriptive string for an error reason.</fsummary> <desc> <p>Given the error reason returned by any function in this @@ -494,7 +494,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="get_cwd" arity="0"/> + <name name="get_cwd" arity="0" since=""/> <fsummary>Get the current working directory.</fsummary> <desc> <p>Returns <c>{ok, <anno>Dir</anno>}</c>, where <c><anno>Dir</anno></c> @@ -516,7 +516,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="get_cwd" arity="1"/> + <name name="get_cwd" arity="1" since=""/> <fsummary>Get the current working directory for the specified drive.</fsummary> <desc> <p>Returns <c>{ok, <anno>Dir</anno>}</c> or @@ -547,7 +547,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="list_dir" arity="1"/> + <name name="list_dir" arity="1" since=""/> <fsummary>List files in a directory.</fsummary> <desc> <p>Lists all files in a directory, <em>except</em> files @@ -578,7 +578,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="list_dir_all" arity="1"/> + <name name="list_dir_all" arity="1" since="OTP R16B"/> <fsummary>List all files in a directory.</fsummary> <desc> <p><marker id="list_dir_all"/>Lists all the files in a directory, @@ -603,7 +603,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="make_dir" arity="1"/> + <name name="make_dir" arity="1" since=""/> <fsummary>Make a directory.</fsummary> <desc> <p>Tries to create directory <c><anno>Dir</anno></c>. Missing parent @@ -637,7 +637,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="make_link" arity="2"/> + <name name="make_link" arity="2" since=""/> <fsummary>Make a hard link to a file.</fsummary> <desc> <p>Makes a hard link from <c><anno>Existing</anno></c> to @@ -666,7 +666,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="make_symlink" arity="2"/> + <name name="make_symlink" arity="2" since=""/> <fsummary>Make a symbolic link to a file or directory.</fsummary> <desc> <p>Creates a symbolic link <c><anno>New</anno></c> to @@ -702,7 +702,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="native_name_encoding" arity="0"/> + <name name="native_name_encoding" arity="0" since="OTP R14B01"/> <fsummary>Return the configured filename encoding of the VM.</fsummary> <desc> <p><marker id="native_name_encoding"/>Returns @@ -714,7 +714,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="open" arity="2"/> + <name name="open" arity="2" since=""/> <fsummary>Open a file.</fsummary> <desc> <p>Opens file <c><anno>File</anno></c> in the mode determined @@ -997,7 +997,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_consult" arity="2"/> + <name name="path_consult" arity="2" since=""/> <fsummary>Read Erlang terms from a file.</fsummary> <desc> <p>Searches the path <c><anno>Path</anno></c> (a list of directory @@ -1039,7 +1039,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_eval" arity="2"/> + <name name="path_eval" arity="2" since=""/> <fsummary>Evaluate Erlang expressions in a file.</fsummary> <desc> <p>Searches the path <c><anno>Path</anno></c> (a list of directory @@ -1085,7 +1085,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_open" arity="3"/> + <name name="path_open" arity="3" since=""/> <fsummary>Open a file.</fsummary> <desc> <p>Searches the path <c><anno>Path</anno></c> (a list of directory @@ -1114,7 +1114,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_script" arity="2"/> + <name name="path_script" arity="2" since=""/> <fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary> <desc> <p>Searches the path <c><anno>Path</anno></c> (a list of directory @@ -1158,7 +1158,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_script" arity="3"/> + <name name="path_script" arity="3" since=""/> <fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary> <desc> <p>The same as <c>path_script/2</c> but the variable bindings @@ -1168,7 +1168,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pid2name" arity="1"/> + <name name="pid2name" arity="1" since=""/> <fsummary>Return the name of the file handled by a pid.</fsummary> <desc> <p>If <c><anno>Pid</anno></c> is an I/O device, that is, a pid returned from @@ -1193,7 +1193,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="position" arity="2"/> + <name name="position" arity="2" since=""/> <fsummary>Set position in a file.</fsummary> <desc> <p>Sets the position of the file referenced by <c><anno>IoDevice</anno></c> @@ -1245,7 +1245,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pread" arity="2"/> + <name name="pread" arity="2" since=""/> <fsummary>Read from a file at certain positions.</fsummary> <desc> <p>Performs a sequence of <c>pread/3</c> in one operation, @@ -1263,7 +1263,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pread" arity="3"/> + <name name="pread" arity="3" since=""/> <fsummary>Read from a file at a certain position.</fsummary> <desc> <p>Combines <c>position/2</c> and <c>read/2</c> in one @@ -1283,7 +1283,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pwrite" arity="2"/> + <name name="pwrite" arity="2" since=""/> <fsummary>Write to a file at certain positions.</fsummary> <desc> <p>Performs a sequence of <c>pwrite/3</c> in one operation, @@ -1298,7 +1298,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pwrite" arity="3"/> + <name name="pwrite" arity="3" since=""/> <fsummary>Write to a file at a certain position.</fsummary> <desc> <p>Combines <c>position/2</c> and <c>write/2</c> in one @@ -1317,7 +1317,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read" arity="2"/> + <name name="read" arity="2" since=""/> <fsummary>Read from a file.</fsummary> <desc> <p>Reads <c><anno>Number</anno></c> bytes/characters from the file @@ -1371,7 +1371,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_file" arity="1"/> + <name name="read_file" arity="1" since=""/> <fsummary>Read a file.</fsummary> <desc> <p>Returns <c>{ok, <anno>Binary</anno>}</c>, where @@ -1407,8 +1407,8 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_file_info" arity="1"/> - <name name="read_file_info" arity="2"/> + <name name="read_file_info" arity="1" since=""/> + <name name="read_file_info" arity="2" since="OTP R15B"/> <fsummary>Retrieve information about a file.</fsummary> <desc> <p>Retrieves information about a file. Returns @@ -1562,7 +1562,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_line" arity="1"/> + <name name="read_line" arity="1" since=""/> <fsummary>Read a line from a file.</fsummary> <desc> <p>Reads a line of bytes/characters from the file referenced by @@ -1619,7 +1619,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_link" arity="1"/> + <name name="read_link" arity="1" since=""/> <fsummary>See what a link is pointing to.</fsummary> <desc> <p><marker id="read_link_all"/>Returns @@ -1649,7 +1649,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_link_all" arity="1"/> + <name name="read_link_all" arity="1" since="OTP R16B"/> <fsummary>See what a link is pointing to.</fsummary> <desc> <p>Returns <c>{ok, <anno>Filename</anno>}</c> if @@ -1677,8 +1677,8 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_link_info" arity="1"/> - <name name="read_link_info" arity="2"/> + <name name="read_link_info" arity="1" since=""/> + <name name="read_link_info" arity="2" since="OTP R15B"/> <fsummary>Retrieve information about a link or file.</fsummary> <desc> <p>Works like @@ -1699,7 +1699,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="rename" arity="2"/> + <name name="rename" arity="2" since=""/> <fsummary>Rename a file.</fsummary> <desc> <p>Tries to rename the file <c><anno>Source</anno></c> to @@ -1762,7 +1762,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="script" arity="1"/> + <name name="script" arity="1" since=""/> <fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary> <desc> <p>Reads and evaluates Erlang expressions, separated by '.' (or @@ -1797,7 +1797,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="script" arity="2"/> + <name name="script" arity="2" since=""/> <fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary> <desc> <p>The same as <c>script/1</c> but the variable bindings @@ -1807,7 +1807,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="sendfile" arity="2"/> + <name name="sendfile" arity="2" since="OTP R15B"/> <fsummary>Send a file to a socket.</fsummary> <desc> <p>Sends the file <c>Filename</c> to <c>Socket</c>. @@ -1816,7 +1816,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="sendfile" arity="5"/> + <name name="sendfile" arity="5" since="OTP R15B"/> <fsummary>Send a file to a socket.</fsummary> <type name="sendfile_option"/> <desc> @@ -1843,7 +1843,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="set_cwd" arity="1"/> + <name name="set_cwd" arity="1" since=""/> <fsummary>Set the current working directory.</fsummary> <desc> <p>Sets the current working directory of the file server to @@ -1890,7 +1890,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="sync" arity="1"/> + <name name="sync" arity="1" since=""/> <fsummary>Synchronize the in-memory state of a file with that on the physical medium.</fsummary> <desc> <p>Ensures that any buffers kept by the operating system @@ -1906,7 +1906,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="truncate" arity="1"/> + <name name="truncate" arity="1" since=""/> <fsummary>Truncate a file.</fsummary> <desc> <p>Truncates the file referenced by <c><anno>IoDevice</anno></c> at @@ -1915,7 +1915,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="write" arity="2"/> + <name name="write" arity="2" since=""/> <fsummary>Write to a file.</fsummary> <desc> <p>Writes <c><anno>Bytes</anno></c> to the file referenced by @@ -1941,7 +1941,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="write_file" arity="2"/> + <name name="write_file" arity="2" since=""/> <fsummary>Write a file.</fsummary> <desc> <p>Writes the contents of the <c>iodata</c> term <c><anno>Bytes</anno></c> @@ -1978,7 +1978,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="write_file" arity="3"/> + <name name="write_file" arity="3" since=""/> <fsummary>Write a file.</fsummary> <desc> <p>Same as <c>write_file/2</c>, but takes a third argument @@ -1989,8 +1989,8 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="write_file_info" arity="2"/> - <name name="write_file_info" arity="3"/> + <name name="write_file_info" arity="2" since=""/> + <name name="write_file_info" arity="3" since="OTP R15B"/> <fsummary>Change file information.</fsummary> <desc> <p>Changes file information. Returns <c>ok</c> if successful, diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml index 1e08b25f66..f70d6c24db 100644 --- a/lib/kernel/doc/src/gen_sctp.xml +++ b/lib/kernel/doc/src/gen_sctp.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>gen_sctp.xml</file> </header> - <module>gen_sctp</module> + <module since="">gen_sctp</module> <modulesummary>Functions for communicating with sockets using the SCTP protocol.</modulesummary> <description> @@ -100,7 +100,7 @@ <funcs> <func> - <name name="abort" arity="2"/> + <name name="abort" arity="2" since=""/> <fsummary>Abnormally terminate the association specified by <c>Assoc</c>, without flushing of unsent data.</fsummary> <desc> @@ -113,7 +113,7 @@ </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close the socket and all associations on it.</fsummary> <desc> <p>Closes the socket and all associations on it. The unsent @@ -128,7 +128,7 @@ </func> <func> - <name name="connect" arity="4"/> + <name name="connect" arity="4" since=""/> <fsummary>Same as <c>connect(Socket, Addr, Port, Opts, infinity)</c>.</fsummary> <desc> <p>Same as <c>connect(<anno>Socket</anno>, <anno>Addr</anno>, @@ -137,7 +137,7 @@ </func> <func> - <name name="connect" arity="5"/> + <name name="connect" arity="5" since=""/> <fsummary>Establish a new association for socket <c>Socket</c>, with a peer (SCTP server socket).</fsummary> <desc> @@ -213,7 +213,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="connect_init" arity="4"/> + <name name="connect_init" arity="4" since="OTP R13B04"/> <fsummary>Same as <c>connect_init(Socket, Addr, Port, Opts, infinity)</c>..</fsummary> <desc> <p>Same as <c>connect_init(<anno>Socket</anno>, <anno>Addr</anno>, @@ -222,7 +222,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="connect_init" arity="5"/> + <name name="connect_init" arity="5" since="OTP R13B04"/> <fsummary>Initiate a new association for socket <c>Socket</c>, with a peer (SCTP server socket).</fsummary> <desc> @@ -248,7 +248,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="controlling_process" arity="2"/> + <name name="controlling_process" arity="2" since=""/> <fsummary>Assign a new controlling process pid to the socket.</fsummary> <desc> <p>Assigns a new controlling process <c><anno>Pid</anno></c> to @@ -259,7 +259,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="eof" arity="2"/> + <name name="eof" arity="2" since=""/> <fsummary>Gracefully terminate the association specified by <c>Assoc</c>, with flushing of all unsent data.</fsummary> <desc> @@ -272,7 +272,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="error_string" arity="1"/> + <name name="error_string" arity="1" since=""/> <fsummary>Translate an SCTP error number into a string.</fsummary> <desc> <p>Translates an SCTP error number from, for example, @@ -283,8 +283,8 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="listen" arity="2" clause_i="1"/> - <name name="listen" arity="2" clause_i="2"/> + <name name="listen" arity="2" clause_i="1" since=""/> + <name name="listen" arity="2" clause_i="2" since="OTP R15B"/> <fsummary>Set up a socket to listen.</fsummary> <desc> <p>Sets up a socket to listen on the IP address and port number @@ -300,10 +300,10 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="open" arity="0"/> - <name name="open" arity="1" clause_i="1"/> - <name name="open" arity="1" clause_i="2"/> - <name name="open" arity="2"/> + <name name="open" arity="0" since=""/> + <name name="open" arity="1" clause_i="1" since=""/> + <name name="open" arity="1" clause_i="2" since=""/> + <name name="open" arity="2" since=""/> <fsummary>Create an SCTP socket and binds it to local addresses.</fsummary> <desc> <p>Creates an SCTP socket and binds it to the local addresses @@ -366,7 +366,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="peeloff" arity="2"/> + <name name="peeloff" arity="2" since="OTP R15B"/> <fsummary>Peel off a type <c>stream</c> socket from a type <c>seqpacket</c> one.</fsummary> <desc> @@ -387,8 +387,8 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="recv" arity="1"/> - <name name="recv" arity="2"/> + <name name="recv" arity="1" since=""/> + <name name="recv" arity="2" since=""/> <fsummary>Receive a message from a socket.</fsummary> <desc> <p>Receives the <c><anno>Data</anno></c> message from any association @@ -532,7 +532,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="send" arity="3"/> + <name name="send" arity="3" since=""/> <fsummary>Send a message using an <c>#sctp_sndrcvinfo{}</c>record.</fsummary> <desc> <p>Sends the <c><anno>Data</anno></c> message with all sending @@ -547,7 +547,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="send" arity="4"/> + <name name="send" arity="4" since=""/> <fsummary>Send a message over an existing association and specified stream.</fsummary> <desc> diff --git a/lib/kernel/doc/src/gen_tcp.xml b/lib/kernel/doc/src/gen_tcp.xml index cf649991d0..fc16473393 100644 --- a/lib/kernel/doc/src/gen_tcp.xml +++ b/lib/kernel/doc/src/gen_tcp.xml @@ -27,7 +27,7 @@ <date>1997-10-24</date> <rev>A</rev> </header> - <module>gen_tcp</module> + <module since="">gen_tcp</module> <modulesummary>Interface to TCP/IP sockets.</modulesummary> <description> <p>This module provides functions for communicating @@ -74,13 +74,25 @@ do_recv(Sock, Bs) -> <desc> <p> If the platform implements the IPv4 option - <c>IP_PKTOPTIONS</c> (probably Linux specific), or the IPv6 option + <c>IP_PKTOPTIONS</c>, or the IPv6 option <c>IPV6_PKTOPTIONS</c> or <c>IPV6_2292PKTOPTIONS</c> for the socket this value is returned from <seealso marker="inet#getopts/2"><c>inet:getopts/2</c></seealso> when called with the option name <seealso marker="#type-option_name"><c>pktoptions</c></seealso>. </p> + <note> + <p> + This option appears to be VERY Linux specific, + and its existence in future Linux kernel versions + is also worrying since the option is part of RFC 2292 + which is since long (2003) obsoleted by RFC 3542 + that <em>explicitly</em> removes this possibility to get + packet information from a stream socket. + For comparision: it has existed in FreeBSD but is now removed, + at least since FreeBSD 10. + </p> + </note> </desc> </datatype> <datatype> @@ -104,8 +116,8 @@ do_recv(Sock, Bs) -> <funcs> <func> - <name name="accept" arity="1"/> - <name name="accept" arity="2"/> + <name name="accept" arity="1" since=""/> + <name name="accept" arity="2" since=""/> <fsummary>Accept an incoming connection request on a listening socket.</fsummary> <type_desc variable="ListenSocket">Returned by <seealso marker="#listen/2"><c>listen/2</c></seealso>. @@ -151,7 +163,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a TCP socket.</fsummary> <desc> <p>Closes a TCP socket.</p> @@ -176,8 +188,8 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="connect" arity="3"/> - <name name="connect" arity="4"/> + <name name="connect" arity="3" since=""/> + <name name="connect" arity="4" since=""/> <fsummary>Connect to a TCP port.</fsummary> <desc> <p>Connects to a server on TCP port <c><anno>Port</anno></c> on the host @@ -256,7 +268,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="controlling_process" arity="2"/> + <name name="controlling_process" arity="2" since=""/> <fsummary>Change controlling process of a socket.</fsummary> <desc> <p>Assigns a new controlling process <c><anno>Pid</anno></c> to @@ -280,7 +292,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="listen" arity="2"/> + <name name="listen" arity="2" since=""/> <fsummary>Set up a socket to listen on a port.</fsummary> <desc> <p>Sets up a socket to listen on port <c><anno>Port</anno></c> on @@ -337,8 +349,8 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="recv" arity="2"/> - <name name="recv" arity="3"/> + <name name="recv" arity="2" since=""/> + <name name="recv" arity="3" since=""/> <fsummary>Receive a packet from a passive socket.</fsummary> <type_desc variable="HttpPacket">See the description of <c>HttpPacket</c> in @@ -363,7 +375,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="send" arity="2"/> + <name name="send" arity="2" since=""/> <fsummary>Send a packet.</fsummary> <desc> <p>Sends a packet on a socket.</p> @@ -374,7 +386,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="shutdown" arity="2"/> + <name name="shutdown" arity="2" since=""/> <fsummary>Asynchronously close a socket.</fsummary> <desc> <p>Closes a socket in one or two directions.</p> diff --git a/lib/kernel/doc/src/gen_udp.xml b/lib/kernel/doc/src/gen_udp.xml index 840ca3c188..d20fc1fdfd 100644 --- a/lib/kernel/doc/src/gen_udp.xml +++ b/lib/kernel/doc/src/gen_udp.xml @@ -28,7 +28,7 @@ <date>1997-12-03</date> <rev>A</rev> </header> - <module>gen_udp</module> + <module since="">gen_udp</module> <modulesummary>Interface to UDP sockets.</modulesummary> <description> <p>This module provides functions for communicating @@ -53,7 +53,7 @@ <funcs> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a UDP socket.</fsummary> <desc> <p>Closes a UDP socket.</p> @@ -61,7 +61,7 @@ </func> <func> - <name name="controlling_process" arity="2"/> + <name name="controlling_process" arity="2" since=""/> <fsummary>Change controlling process of a socket.</fsummary> <desc> <p>Assigns a new controlling process <c><anno>Pid</anno></c> to @@ -77,8 +77,8 @@ </func> <func> - <name name="open" arity="1"/> - <name name="open" arity="2"/> + <name name="open" arity="1" since=""/> + <name name="open" arity="2" since=""/> <fsummary>Associate a UDP port number with the process calling it.</fsummary> <desc> <p>Associates a UDP port number (<c><anno>Port</anno></c>) with the @@ -189,8 +189,8 @@ </func> <func> - <name name="recv" arity="2"/> - <name name="recv" arity="3"/> + <name name="recv" arity="2" since=""/> + <name name="recv" arity="3" since=""/> <fsummary>Receive a packet from a passive socket.</fsummary> <desc> <p> @@ -213,7 +213,7 @@ </func> <func> - <name name="send" arity="4"/> + <name name="send" arity="4" since=""/> <fsummary>Send a packet.</fsummary> <desc> <p> diff --git a/lib/kernel/doc/src/global.xml b/lib/kernel/doc/src/global.xml index 4442741f54..dfe71de5ce 100644 --- a/lib/kernel/doc/src/global.xml +++ b/lib/kernel/doc/src/global.xml @@ -28,7 +28,7 @@ <date>1997-11-17</date> <rev></rev> </header> - <module>global</module> + <module since="">global</module> <modulesummary>A global name registration facility.</modulesummary> <description> <p>This module consists of the following services:</p> @@ -100,8 +100,8 @@ <funcs> <func> - <name name="del_lock" arity="1"/> - <name name="del_lock" arity="2"/> + <name name="del_lock" arity="1" since=""/> + <name name="del_lock" arity="2" since=""/> <fsummary>Delete a lock.</fsummary> <desc> <p>Deletes the lock <c><anno>Id</anno></c> synchronously.</p> @@ -109,7 +109,7 @@ </func> <func> - <name name="notify_all_name" arity="3"/> + <name name="notify_all_name" arity="3" since=""/> <fsummary>Name resolving function that notifies both pids.</fsummary> <desc> <p>Can be used as a name resolving function for @@ -123,7 +123,7 @@ </func> <func> - <name name="random_exit_name" arity="3"/> + <name name="random_exit_name" arity="3" since=""/> <fsummary>Name resolving function that kills one pid.</fsummary> <desc> <p>Can be used as a name resolving function for @@ -136,7 +136,7 @@ </func> <func> - <name name="random_notify_name" arity="3"/> + <name name="random_notify_name" arity="3" since=""/> <fsummary>Name resolving function that notifies one pid.</fsummary> <desc> <p>Can be used as a name resolving function for @@ -150,8 +150,8 @@ </func> <func> - <name name="re_register_name" arity="2"/> - <name name="re_register_name" arity="3"/> + <name name="re_register_name" arity="2" since=""/> + <name name="re_register_name" arity="3" since=""/> <fsummary>Atomically re-register a name.</fsummary> <type name="method"/> <type_desc name="method">{<c>Module</c>, <c>Function</c>} @@ -167,8 +167,8 @@ </func> <func> - <name name="register_name" arity="2"/> - <name name="register_name" arity="3"/> + <name name="register_name" arity="2" since=""/> + <name name="register_name" arity="3" since=""/> <fsummary>Globally register a name for a pid.</fsummary> <type name="method"/> <type_desc name="method">{<c>Module</c>, <c>Function</c>} is also @@ -221,7 +221,7 @@ </func> <func> - <name name="registered_names" arity="0"/> + <name name="registered_names" arity="0" since=""/> <fsummary>All globally registered names.</fsummary> <desc> <p>Returns a list of all globally registered names.</p> @@ -229,7 +229,7 @@ </func> <func> - <name name="send" arity="2"/> + <name name="send" arity="2" since=""/> <fsummary>Send a message to a globally registered pid.</fsummary> <desc> <p>Sends message <c><anno>Msg</anno></c> to the pid globally registered @@ -241,9 +241,9 @@ </func> <func> - <name name="set_lock" arity="1"/> - <name name="set_lock" arity="2"/> - <name name="set_lock" arity="3"/> + <name name="set_lock" arity="1" since=""/> + <name name="set_lock" arity="2" since=""/> + <name name="set_lock" arity="3" since=""/> <fsummary>Set a lock on the specified nodes.</fsummary> <type name="id"/> <type name="retries"/> @@ -287,7 +287,7 @@ </func> <func> - <name name="sync" arity="0"/> + <name name="sync" arity="0" since=""/> <fsummary>Synchronize the global name server.</fsummary> <desc> <p>Synchronizes the global name server with all nodes known to @@ -302,9 +302,9 @@ </func> <func> - <name name="trans" arity="2"/> - <name name="trans" arity="3"/> - <name name="trans" arity="4"/> + <name name="trans" arity="2" since=""/> + <name name="trans" arity="3" since=""/> + <name name="trans" arity="4" since=""/> <fsummary>Micro transaction facility.</fsummary> <type name="retries"/> <type name="trans_fun"/> @@ -322,7 +322,7 @@ </func> <func> - <name name="unregister_name" arity="1"/> + <name name="unregister_name" arity="1" since=""/> <fsummary>Remove a globally registered name for a pid.</fsummary> <desc> <p>Removes the globally registered name <c><anno>Name</anno></c> from @@ -331,7 +331,7 @@ </func> <func> - <name name="whereis_name" arity="1"/> + <name name="whereis_name" arity="1" since=""/> <fsummary>Get the pid with a specified globally registered name.</fsummary> <desc> <p>Returns the pid with the globally registered name diff --git a/lib/kernel/doc/src/global_group.xml b/lib/kernel/doc/src/global_group.xml index 8f947b9adf..74d15cd476 100644 --- a/lib/kernel/doc/src/global_group.xml +++ b/lib/kernel/doc/src/global_group.xml @@ -28,7 +28,7 @@ <date>1998-12-18</date> <rev>B</rev> </header> - <module>global_group</module> + <module since="">global_group</module> <modulesummary>Grouping nodes to global name registration groups.</modulesummary> <description> <p>This module makes it possible to partition the nodes of a @@ -105,7 +105,7 @@ <funcs> <func> - <name name="global_groups" arity="0"/> + <name name="global_groups" arity="0" since=""/> <fsummary>Return the global group names.</fsummary> <desc> <p>Returns a tuple containing the name of the global group that @@ -116,7 +116,7 @@ </func> <func> - <name name="info" arity="0"/> + <name name="info" arity="0" since=""/> <fsummary>Information about global groups.</fsummary> <type name="info_item"/> <type name="sync_state"/> @@ -173,7 +173,7 @@ </func> <func> - <name name="monitor_nodes" arity="1"/> + <name name="monitor_nodes" arity="1" since=""/> <fsummary>Subscribe to node status changes.</fsummary> <desc> <p>Depending on <c><anno>Flag</anno></c>, the calling process @@ -187,7 +187,7 @@ </func> <func> - <name name="own_nodes" arity="0"/> + <name name="own_nodes" arity="0" since=""/> <fsummary>Return the group nodes.</fsummary> <desc> <p>Returns the names of all group nodes, regardless of their @@ -196,7 +196,7 @@ </func> <func> - <name name="registered_names" arity="1"/> + <name name="registered_names" arity="1" since=""/> <fsummary>Return globally registered names.</fsummary> <desc> <p>Returns a list of all names that are globally registered @@ -205,8 +205,8 @@ </func> <func> - <name name="send" arity="2"/> - <name name="send" arity="3"/> + <name name="send" arity="2" since=""/> + <name name="send" arity="3" since=""/> <fsummary>Send a message to a globally registered pid.</fsummary> <desc> <p>Searches for <c><anno>Name</anno></c>, globally registered on @@ -224,7 +224,7 @@ </func> <func> - <name name="sync" arity="0"/> + <name name="sync" arity="0" since=""/> <fsummary>Synchronize the group nodes.</fsummary> <desc> <p>Synchronizes the group nodes, that is, the global name @@ -242,8 +242,8 @@ </func> <func> - <name name="whereis_name" arity="1"/> - <name name="whereis_name" arity="2"/> + <name name="whereis_name" arity="1" since=""/> + <name name="whereis_name" arity="2" since=""/> <fsummary>Get the pid with a specified globally registered name.</fsummary> <desc> <p>Searches for <c><anno>Name</anno></c>, globally registered on diff --git a/lib/kernel/doc/src/heart.xml b/lib/kernel/doc/src/heart.xml index ad1a2ffeb9..4243b1ffe8 100644 --- a/lib/kernel/doc/src/heart.xml +++ b/lib/kernel/doc/src/heart.xml @@ -28,7 +28,7 @@ <date>1998-01-28</date> <rev>A</rev> </header> - <module>heart</module> + <module since="">heart</module> <modulesummary>Heartbeat monitoring of an Erlang runtime system.</modulesummary> <description> <p>This modules contains the interface to the <c>heart</c> process. @@ -119,7 +119,7 @@ <funcs> <func> - <name name="set_cmd" arity="1"/> + <name name="set_cmd" arity="1" since=""/> <fsummary>Set a temporary reboot command.</fsummary> <desc> <p>Sets a temporary reboot command. This command is used if @@ -136,7 +136,7 @@ </func> <func> - <name name="clear_cmd" arity="0"/> + <name name="clear_cmd" arity="0" since=""/> <fsummary>Clear the temporary boot command.</fsummary> <desc> <p>Clears the temporary boot command. If the system terminates, @@ -145,7 +145,7 @@ </func> <func> - <name name="get_cmd" arity="0"/> + <name name="get_cmd" arity="0" since=""/> <fsummary>Get the temporary reboot command.</fsummary> <desc> <p>Gets the temporary reboot command. If the command is cleared, @@ -154,7 +154,7 @@ </func> <func> - <name name="set_callback" arity="2"/> + <name name="set_callback" arity="2" since="OTP 18.3"/> <fsummary>Set a validation callback</fsummary> <desc> <p> This validation callback will be executed before any @@ -166,14 +166,14 @@ </desc> </func> <func> - <name name="clear_callback" arity="0"/> + <name name="clear_callback" arity="0" since="OTP 18.3"/> <fsummary>Clear the validation callback</fsummary> <desc> <p>Removes the validation callback call before heartbeats.</p> </desc> </func> <func> - <name name="get_callback" arity="0"/> + <name name="get_callback" arity="0" since="OTP 18.3"/> <fsummary>Get the validation callback</fsummary> <desc> <p>Get the validation callback. If the callback is cleared, <c>none</c> will be returned.</p> @@ -181,7 +181,7 @@ </func> <func> - <name name="set_options" arity="1"/> + <name name="set_options" arity="1" since="OTP 18.3"/> <fsummary>Set a list of options</fsummary> <desc> <p> Valid options <c>set_options</c> are: </p> @@ -199,7 +199,7 @@ </desc> </func> <func> - <name name="get_options" arity="0"/> + <name name="get_options" arity="0" since="OTP 18.3"/> <fsummary>Get the temporary reboot command</fsummary> <desc> <p>Returns <c>{ok, Options}</c> where <c>Options</c> is a list of current options enabled for heart. diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml index ed775d67eb..709ba8e8fd 100644 --- a/lib/kernel/doc/src/inet.xml +++ b/lib/kernel/doc/src/inet.xml @@ -28,7 +28,7 @@ <date>1998-02-04</date> <rev>A</rev> </header> - <module>inet</module> + <module since="">inet</module> <modulesummary>Access to TCP/IP protocols.</modulesummary> <description> <p>This module provides access to TCP/IP protocols.</p> @@ -198,6 +198,79 @@ fe80::204:acff:fe17:bf38 </desc> </datatype> <datatype> + <name name="getifaddrs_ifopts"/> + <desc> + <p> + Interface address description list returned from + <seealso marker="#getifaddrs/0"><c>getifaddrs/0,1</c></seealso> + for a named interface, translated from the returned + data of the POSIX API function <c>getaddrinfo()</c>. + </p> + <p> + <c><anno>Hwaddr</anno></c> is hardware dependent, + for example, on Ethernet interfaces it is + the 6-byte Ethernet address (MAC address (EUI-48 address)). + </p> + <p> + The tuples <c>{addr,<anno>Addr</anno>}</c>, + <c>{netmask,<anno>Netmask</anno>}</c>, and possibly + <c>{broadaddr,<anno>Broadaddr</anno>}</c> or + <c>{dstaddr,<anno>Dstaddr</anno>}</c> + are repeated in the list + if the interface has got multiple addresses. + An interface may have multiple <c>{flag,_}</c> tuples + for example if it has different flags + for different address families. + Multiple <c>{hwaddr,<anno>Hwaddr</anno>}</c> tuples + is hard to say anything definite about, though. + The tuple <c>{flag,<anno>Flags</anno>}</c> is mandatory, + all others are optional. + </p> + <p> + Do not rely too much on the order + of <c><anno>Flags</anno></c> atoms + or the <c><anno>Ifopt</anno></c> tuples. + There are however some rules: + </p> + <list type="bulleted"> + <item><p> + A <c>{flag,_}</c> tuple applies to all other tuples that follow. + </p></item> + <item><p> + Immediately after <c>{addr,_}</c> follows <c>{netmask,_}</c>. + </p></item> + <item><p> + Immediately thereafter may <c>{broadaddr,_}</c> follow + if <c>broadcast</c> is member of <c><anno>Flags</anno></c>, + or <c>{dstaddr,_}</c> if <c>pointtopoint</c> + is member of <c><anno>Flags</anno></c>. + Both <c>{dstaddr,_}</c> and <c>{broadaddr,_}</c> + does not occur for the same <c>{addr,_}</c>. + </p></item> + <item><p> + Any <c>{netmask,_}</c>, <c>{broadaddr,_}</c>, or + <c>{dstaddr,_}</c> tuples that follow an + <c>{addr,<anno>Addr</anno>}</c> + tuple concerns the address <c><anno>Addr</anno></c>. + </p></item> + </list> + <p> + The tuple <c>{hwaddr,_}</c> is not returned on Solaris, as the + hardware address historically belongs to the link layer + and it is not returned by the Solaris API function + <c>getaddrinfo()</c>. + </p> + <warning> + <p> + On Windows, the data is fetched from different + OS API functions, so the <c><anno>Netmask</anno></c> + and <c><anno>Broadaddr</anno></c> values may be calculated, + just as some <c><anno>Flags</anno></c> values. + </p> + </warning> + </desc> + </datatype> + <datatype> <name name="posix"/> <desc> <p>An atom that is named from the POSIX error codes used in Unix, @@ -225,7 +298,7 @@ fe80::204:acff:fe17:bf38 <funcs> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a socket of any type.</fsummary> <desc> <p>Closes a socket of any type.</p> @@ -233,7 +306,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Return a descriptive string for an error reason.</fsummary> <desc> <p>Returns a diagnostic error string. For possible POSIX values and @@ -243,7 +316,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="get_rc" arity="0"/> + <name name="get_rc" arity="0" since=""/> <fsummary>Return a list of IP configuration parameters.</fsummary> <desc> <p> @@ -262,7 +335,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="getaddr" arity="2"/> + <name name="getaddr" arity="2" since=""/> <fsummary>Return the IP address for a host.</fsummary> <desc> <p>Returns the IP address for <c><anno>Host</anno></c> as a tuple of @@ -272,7 +345,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="getaddrs" arity="2"/> + <name name="getaddrs" arity="2" since=""/> <fsummary>Return the IP addresses for a host.</fsummary> <desc> <p>Returns a list of all IP addresses for <c><anno>Host</anno></c>. @@ -282,7 +355,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="gethostbyaddr" arity="1"/> + <name name="gethostbyaddr" arity="1" since=""/> <fsummary>Return a hostent record for the host with the specified address.</fsummary> <desc> @@ -291,7 +364,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="gethostbyname" arity="1"/> + <name name="gethostbyname" arity="1" since=""/> <fsummary>Return a hostent record for the host with the specified name. </fsummary> <desc> @@ -303,7 +376,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="gethostbyname" arity="2"/> + <name name="gethostbyname" arity="2" since=""/> <fsummary>Return a hostent record for the host with the specified name. </fsummary> <desc> @@ -313,7 +386,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="gethostname" arity="0"/> + <name name="gethostname" arity="0" since=""/> <fsummary>Return the local hostname.</fsummary> <desc> <p>Returns the local hostname. Never fails.</p> @@ -321,46 +394,72 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="getifaddrs" arity="0"/> + <name name="getifaddrs" arity="0" since="OTP R14B01"/> <fsummary>Return a list of interfaces and their addresses.</fsummary> <desc> - <p>Returns a list of 2-tuples containing interface names and the - interface addresses. <c><anno>Ifname</anno></c> is a Unicode string. - <c><anno>Hwaddr</anno></c> is hardware dependent, for example, on - Ethernet interfaces - it is the 6-byte Ethernet address (MAC address (EUI-48 address)).</p> - <p>The tuples <c>{addr,<anno>Addr</anno>}</c>, <c>{netmask,_}</c>, and - <c>{broadaddr,_}</c> are repeated in the result list if the interface - has multiple addresses. If you come across an interface with - multiple <c>{flag,_}</c> or <c>{hwaddr,_}</c> tuples, you have - a strange interface or possibly a bug in this function. The tuple - <c>{flag,_}</c> is mandatory, all others are optional.</p> - <p>Do not rely too much on the order of <c><anno>Flag</anno></c> atoms - or <c><anno>Ifopt</anno></c> tuples. There are however some rules:</p> - <list type="bulleted"> - <item><p>Immediately after - <c>{addr,_}</c> follows <c>{netmask,_}</c>.</p></item> - <item><p>Immediately thereafter follows <c>{broadaddr,_}</c> if flag - <c>broadcast</c> is <em>not</em> set and flag - <c>pointtopoint</c> <em>is</em> set.</p></item> - <item><p>Any <c>{netmask,_}</c>, <c>{broadaddr,_}</c>, or - <c>{dstaddr,_}</c> tuples that follow an <c>{addr,_}</c> - tuple concerns that address.</p></item> - </list> - <p>The tuple <c>{hwaddr,_}</c> is not returned on Solaris, as the - hardware address historically belongs to the link layer and only - the superuser can read such addresses.</p> - <warning> - <p>On Windows, the data is fetched from different OS API functions, - so the <c><anno>Netmask</anno></c> and <c><anno>Broadaddr</anno></c> - values can be calculated, just as some <c><anno>Flag</anno></c> - values. Report flagrant bugs.</p> - </warning> + <p> + Returns a list of 2-tuples containing interface names and + the interfaces' addresses. <c><anno>Ifname</anno></c> + is a Unicode string and + <c><anno>Ifopts</anno></c> is a list of + interface address description tuples. + </p> + <p> + The interface address description tuples + are documented under the type of the + <seealso marker="#type-getifaddrs_ifopts"> + <c><anno>Ifopts</anno></c> + </seealso> + value. + </p> </desc> </func> <func> - <name name="getopts" arity="2"/> + <name since="OTP 21.2">getifaddrs(Opts) -> + {ok, [{Ifname, Ifopts}]} | {error, Posix} + </name> + <fsummary>Return a list of interfaces and their addresses.</fsummary> + <type> + <v> + Opts = [{netns, Namespace}] + </v> + <v> + Namespace = + <seealso marker="file#type-filename_all"> + file:filename_all() + </seealso> + </v> + <v>Ifname = string()</v> + <v> + Ifopts = + <seealso marker="#type-getifaddrs_ifopts"> + getifaddrs_ifopts() + </seealso> + </v> + <v>Posix = <seealso marker="#type-posix">posix()</seealso></v> + </type> + <desc> + <p> + The same as + <seealso marker="#getifaddrs/0"><c>getifaddrs/0</c></seealso> + but the <c>Option</c> + <c>{netns, Namespace}</c> sets a network namespace + for the OS call, on platforms that supports that feature. + </p> + <p> + See the socket option + <seealso marker="#option-netns"> + <c>{netns, Namespace}</c> + </seealso> + under + <seealso marker="#setopts/2"><c>setopts/2</c></seealso>. + </p> + </desc> + </func> + + <func> + <name name="getopts" arity="2" since=""/> <fsummary>Get one or more options for a socket.</fsummary> <desc> <p>Gets one or more options for a socket. For a list of available @@ -430,8 +529,8 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="getstat" arity="1"/> - <name name="getstat" arity="2"/> + <name name="getstat" arity="1" since=""/> + <name name="getstat" arity="2" since=""/> <fsummary>Get one or more statistic options for a socket.</fsummary> <type name="stat_option"/> <desc> @@ -487,9 +586,9 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="i" arity="0" /> - <name name="i" arity="1" /> - <name name="i" arity="2" /> + <name name="i" arity="0" since="OTP 21.0"/> + <name name="i" arity="1" since="OTP 21.0"/> + <name name="i" arity="2" since="OTP 21.0"/> <fsummary>Displays information and statistics about sockets on the terminal</fsummary> <desc> <p> @@ -542,7 +641,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="ntoa" arity="1" /> + <name name="ntoa" arity="1" since="OTP R16B02"/> <fsummary>Convert IPv6/IPV4 address to ASCII.</fsummary> <desc> <p>Parses an @@ -552,7 +651,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_address" arity="1" /> + <name name="parse_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv4 or IPv6 address.</fsummary> <desc> <p>Parses an IPv4 or IPv6 address string and returns an @@ -563,7 +662,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_ipv4_address" arity="1" /> + <name name="parse_ipv4_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv4 address.</fsummary> <desc> <p>Parses an IPv4 address string and returns an @@ -573,7 +672,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_ipv4strict_address" arity="1" /> + <name name="parse_ipv4strict_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv4 address strict.</fsummary> <desc> <p>Parses an IPv4 address string containing four fields, that is, @@ -584,7 +683,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_ipv6_address" arity="1" /> + <name name="parse_ipv6_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv6 address.</fsummary> <desc> <p>Parses an IPv6 address string and returns an @@ -595,7 +694,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_ipv6strict_address" arity="1" /> + <name name="parse_ipv6strict_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv6 address strict.</fsummary> <desc> <p>Parses an IPv6 address string and returns an @@ -605,7 +704,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="ipv4_mapped_ipv6_address" arity="1" /> + <name name="ipv4_mapped_ipv6_address" arity="1" since="OTP 21.0"/> <fsummary>Convert to and from IPv4-mapped IPv6 address.</fsummary> <desc> <p> @@ -618,7 +717,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_strict_address" arity="1" /> + <name name="parse_strict_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv4 or IPv6 address strict.</fsummary> <desc> <p>Parses an IPv4 or IPv6 address string and returns an @@ -629,7 +728,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="peername" arity="1"/> + <name name="peername" arity="1" since=""/> <fsummary>Return the address and port for the other end of a connection. </fsummary> <desc> @@ -642,7 +741,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="peernames" arity="1"/> + <name name="peernames" arity="1" since="OTP R16B03"/> <fsummary>Return all address/port numbers for the other end of a connection.</fsummary> <desc> @@ -656,7 +755,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="peernames" arity="2"/> + <name name="peernames" arity="2" since="OTP R16B03"/> <fsummary>Return all address/port numbers for the other end of a connection.</fsummary> <desc> @@ -675,7 +774,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="port" arity="1"/> + <name name="port" arity="1" since=""/> <fsummary>Return the local port number for a socket.</fsummary> <desc> <p>Returns the local port number for a socket.</p> @@ -683,7 +782,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="setopts" arity="2"/> + <name name="setopts" arity="2" since=""/> <fsummary>Set one or more options for a socket.</fsummary> <desc> <p>Sets one or more options for a socket.</p> @@ -908,13 +1007,34 @@ get_tcpi_sacked(Sock) -> <marker id="option-linger"></marker> </item> <tag><c>{linger, {true|false, Seconds}}</c></tag> - <item> + <item> <p>Determines the time-out, in seconds, for flushing unsent data - in the <c>close/1</c> socket call. If the first component of - the value tuple is <c>false</c>, the second is ignored. This - means that <c>close/1</c> returns immediately, not waiting - for data to be flushed. Otherwise, the second component is - the flushing time-out, in seconds.</p> + in the <c>close/1</c> socket call. </p> + <p>The first component is if linger is enabled, the second component + is the flushing time-out, in seconds. There are 3 alternatives:</p> + <taglist> + <tag><c>{false, _}</c></tag> + <item> + <p>close/1 or shutdown/2 returns immediately, + not waiting for data to be flushed, with closing + happening in the background.</p> + </item> + <tag><c>{true, 0}</c></tag> + <item> + <p>Aborts the connection when it is closed. + Discards any data still remaining in the send buffers + and sends RST to the peer.</p> + <p>This avoids TCP's TIME_WAIT state, but leaves open + the possibility that another "incarnation" of this connection + being created.</p> + </item> + <tag><c>{true, Time} when Time > 0</c></tag> + <item> + <p>close/1 or shutdown/2 will not return until + all queued messages for the socket have been successfully + sent or the linger timeout (Time) has been reached.</p> + </item> + </taglist> </item> <tag><c>{low_msgq_watermark, Size}</c></tag> <item> @@ -950,20 +1070,29 @@ get_tcpi_sacked(Sock) -> </item> <tag><c>{mode, Mode :: binary | list}</c></tag> <item> - <p>Received <c>Packet</c> is delivered as defined by <c>Mode</c>. + <p> + Received <c>Packet</c> is delivered as defined by <c>Mode</c>. </p> </item> - <tag><c>{netns, Namespace :: file:filename_all()}</c></tag> + <tag> + <marker id="option-netns"/> + <c>{netns, Namespace :: file:filename_all()}</c> + </tag> <item> - <p>Sets a network namespace for the socket. Parameter + <p> + Sets a network namespace for the socket. Parameter <c>Namespace</c> is a filename defining the namespace, for example, <c>"/var/run/netns/example"</c>, typically created by command <c>ip netns add example</c>. This option must be used in a function call that creates a socket, that is, <seealso marker="gen_tcp#connect/3"><c>gen_tcp:connect/3,4</c></seealso>, <seealso marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seealso>, - <seealso marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seealso>, or - <seealso marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seealso>.</p> + <seealso marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seealso> + or + <seealso marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seealso>, + and also + <seealso marker="#getifaddrs/1"><c>getifaddrs/1</c></seealso>. + </p> <p>This option uses the Linux-specific syscall <c>setns()</c>, such as in Linux kernel 3.0 or later, and therefore only exists when the runtime system @@ -1039,6 +1168,18 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code> is turned on for the socket, which means that also small amounts of data are sent immediately.</p> </item> + <tag><c>{nopush, Boolean}</c>(TCP/IP sockets)</tag> + <item> + <p>This translates to <c>TCP_NOPUSH</c> on BSD and + to <c>TCP_CORK</c> on Linux.</p> + <p>If <c>Boolean == true</c>, the corresponding option + is turned on for the socket, which means that small + amounts of data are accumulated until a full MSS-worth + of data is available or this option is turned off.</p> + <p>Note that while <c>TCP_NOPUSH</c> socket option is available on OSX, its semantics + is very different (e.g., unsetting it does not cause immediate send + of accumulated data). Hence, <c>nopush</c> option is intentionally ignored on OSX.</p> + </item> <tag><c>{packet, PacketType}</c>(TCP/IP sockets)</tag> <item> <p><marker id="packet"/>Defines the type of packets to use for a socket. @@ -1366,7 +1507,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code> </func> <func> - <name name="sockname" arity="1"/> + <name name="sockname" arity="1" since=""/> <fsummary>Return the local address and port number for a socket. </fsummary> <desc> @@ -1379,7 +1520,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code> </func> <func> - <name name="socknames" arity="1"/> + <name name="socknames" arity="1" since="OTP R16B03"/> <fsummary>Return all local address/port numbers for a socket.</fsummary> <desc> <p>Equivalent to @@ -1389,7 +1530,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code> </func> <func> - <name name="socknames" arity="2"/> + <name name="socknames" arity="2" since="OTP R16B03"/> <fsummary>Return all local address/port numbers for a socket.</fsummary> <desc> <p>Returns a list of all local address/port number pairs for a socket diff --git a/lib/kernel/doc/src/inet_res.xml b/lib/kernel/doc/src/inet_res.xml index 351d86a93a..1904e371f7 100644 --- a/lib/kernel/doc/src/inet_res.xml +++ b/lib/kernel/doc/src/inet_res.xml @@ -28,7 +28,7 @@ <date>2009-09-11</date> <rev>A</rev> </header> - <module>inet_res</module> + <module since="">inet_res</module> <modulesummary>A rudimentary DNS client.</modulesummary> <description> <p>This module performs DNS name resolving to recursive name servers.</p> @@ -185,8 +185,8 @@ inet_dns:record_type(_) -> undefined.</pre> <funcs> <func> - <name name="getbyname" arity="2"/> - <name name="getbyname" arity="3"/> + <name name="getbyname" arity="2" since=""/> + <name name="getbyname" arity="3" since=""/> <fsummary>Resolve a DNS record of the specified type for the specified host.</fsummary> <desc> @@ -205,8 +205,8 @@ inet_dns:record_type(_) -> undefined.</pre> </func> <func> - <name name="gethostbyaddr" arity="1"/> - <name name="gethostbyaddr" arity="2"/> + <name name="gethostbyaddr" arity="1" since=""/> + <name name="gethostbyaddr" arity="2" since=""/> <fsummary>Return a hostent record for the host with the specified address.</fsummary> <desc> @@ -217,9 +217,9 @@ inet_dns:record_type(_) -> undefined.</pre> </func> <func> - <name name="gethostbyname" arity="1"/> - <name name="gethostbyname" arity="2"/> - <name name="gethostbyname" arity="3"/> + <name name="gethostbyname" arity="1" since=""/> + <name name="gethostbyname" arity="2" since=""/> + <name name="gethostbyname" arity="3" since=""/> <fsummary>Return a hostent record for the host with the specified name. </fsummary> <desc> @@ -235,9 +235,9 @@ inet_dns:record_type(_) -> undefined.</pre> </func> <func> - <name name="lookup" arity="3"/> - <name name="lookup" arity="4"/> - <name name="lookup" arity="5"/> + <name name="lookup" arity="3" since=""/> + <name name="lookup" arity="4" since=""/> + <name name="lookup" arity="5" since=""/> <fsummary>Resolve the DNS data for the record of the specified type and class for the specified name.</fsummary> <desc> @@ -257,9 +257,9 @@ inet_dns:record_type(_) -> undefined.</pre> </func> <func> - <name name="resolve" arity="3"/> - <name name="resolve" arity="4"/> - <name name="resolve" arity="5"/> + <name name="resolve" arity="3" since=""/> + <name name="resolve" arity="4" since=""/> + <name name="resolve" arity="5" since=""/> <fsummary>Resolve a DNS record of the specified type and class for the specified name.</fsummary> <desc> @@ -326,9 +326,9 @@ example_lookup(Name, Class, Type) -> <funcs> <func> - <name name="nslookup" arity="3"/> - <name name="nslookup" arity="4" clause_i="1"/> - <name name="nslookup" arity="4" clause_i="2"/> + <name name="nslookup" arity="3" since=""/> + <name name="nslookup" arity="4" clause_i="1" since=""/> + <name name="nslookup" arity="4" clause_i="2" since=""/> <fsummary>Resolve a DNS record of the specified type and class for the specified name.</fsummary> <type variable="Name"/> @@ -344,8 +344,8 @@ example_lookup(Name, Class, Type) -> </func> <func> - <name name="nnslookup" arity="4"/> - <name name="nnslookup" arity="5"/> + <name name="nnslookup" arity="4" since=""/> + <name name="nnslookup" arity="5" since=""/> <fsummary>Resolve a DNS record of the specified type and class for the specified name.</fsummary> <desc> diff --git a/lib/kernel/doc/src/logger.xml b/lib/kernel/doc/src/logger.xml index 464c65ba76..0668676096 100644 --- a/lib/kernel/doc/src/logger.xml +++ b/lib/kernel/doc/src/logger.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger.xml</file> </header> - <module>logger</module> + <module since="OTP 21.0">logger</module> <modulesummary>API module for Logger, the standard logging facility in Erlang/OTP.</modulesummary> @@ -245,6 +245,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </desc> </datatype> <datatype> + <name name="olp_config"/> + <desc> + <p></p> + </desc> + </datatype> + <datatype> <name name="primary_config"/> <desc> <p>Primary configuration data for Logger. The following @@ -290,7 +296,10 @@ logger:error("error happened because: ~p", [Reason]). % Without macro <section> <title>Macros</title> - <p>The following macros are defined:</p> + <p>The following macros are defined in <c>logger.hrl</c>, which + is included in a module with the directive</p> + <code> + -include_lib("kernel/include/logger.hrl").</code> <list> <item><c>?LOG_EMERGENCY(StringOrReport[,Metadata])</c></item> @@ -331,9 +340,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </section> <funcs> <func> - <name>emergency(StringOrReport[,Metadata])</name> - <name>emergency(Format,Args[,Metadata])</name> - <name>emergency(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">emergency(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">emergency(Format,Args[,Metadata])</name> + <name since="OTP 21.0">emergency(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>emergency</c>.</fsummary> <desc> <p>Equivalent to @@ -342,9 +351,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>alert(StringOrReport[,Metadata])</name> - <name>alert(Format,Args[,Metadata])</name> - <name>alert(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">alert(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">alert(Format,Args[,Metadata])</name> + <name since="OTP 21.0">alert(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>alert</c>.</fsummary> <desc> <p>Equivalent to @@ -353,9 +362,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>critical(StringOrReport[,Metadata])</name> - <name>critical(Format,Args[,Metadata])</name> - <name>critical(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">critical(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">critical(Format,Args[,Metadata])</name> + <name since="OTP 21.0">critical(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>critical</c>.</fsummary> <desc> <p>Equivalent to @@ -364,9 +373,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>error(StringOrReport[,Metadata])</name> - <name>error(Format,Args[,Metadata])</name> - <name>error(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">error(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">error(Format,Args[,Metadata])</name> + <name since="OTP 21.0">error(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>error</c>.</fsummary> <desc> <p>Equivalent to @@ -375,9 +384,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>warning(StringOrReport[,Metadata])</name> - <name>warning(Format,Args[,Metadata])</name> - <name>warning(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">warning(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">warning(Format,Args[,Metadata])</name> + <name since="OTP 21.0">warning(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>warning</c>.</fsummary> <desc> <p>Equivalent to @@ -386,9 +395,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>notice(StringOrReport[,Metadata])</name> - <name>notice(Format,Args[,Metadata])</name> - <name>notice(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">notice(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">notice(Format,Args[,Metadata])</name> + <name since="OTP 21.0">notice(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>notice</c>.</fsummary> <desc> <p>Equivalent to @@ -397,9 +406,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>info(StringOrReport[,Metadata])</name> - <name>info(Format,Args[,Metadata])</name> - <name>info(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">info(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">info(Format,Args[,Metadata])</name> + <name since="OTP 21.0">info(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>info</c>.</fsummary> <desc> <p>Equivalent to @@ -408,9 +417,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>debug(StringOrReport[,Metadata])</name> - <name>debug(Format,Args[,Metadata])</name> - <name>debug(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">debug(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">debug(Format,Args[,Metadata])</name> + <name since="OTP 21.0">debug(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>debug</c>.</fsummary> <desc> <p>Equivalent to @@ -419,12 +428,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name name="log" arity="2"/> - <name name="log" arity="3" clause_i="1"/> - <name name="log" arity="3" clause_i="2"/> - <name name="log" arity="3" clause_i="3"/> - <name name="log" arity="4" clause_i="1"/> - <name name="log" arity="4" clause_i="2"/> + <name name="log" arity="2" since="OTP 21.0"/> + <name name="log" arity="3" clause_i="1" since="OTP 21.0"/> + <name name="log" arity="3" clause_i="2" since="OTP 21.0"/> + <name name="log" arity="3" clause_i="3" since="OTP 21.0"/> + <name name="log" arity="4" clause_i="1" since="OTP 21.0"/> + <name name="log" arity="4" clause_i="2" since="OTP 21.0"/> <fsummary>Logs the given message.</fsummary> <type variable="Level"/> <type variable="StringOrReport" name_i="1"/> @@ -445,7 +454,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </section> <funcs> <func> - <name name="add_handler" arity="3"/> + <name name="add_handler" arity="3" since="OTP 21.0"/> <fsummary>Add a handler with the given configuration.</fsummary> <desc> <p>Add a handler with the given configuration.</p> @@ -456,7 +465,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name name="add_handler_filter" arity="3"/> + <name name="add_handler_filter" arity="3" since="OTP 21.0"/> <fsummary>Add a filter to the specified handler.</fsummary> <desc> <p>Add a filter to the specified handler.</p> @@ -497,7 +506,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name name="add_handlers" arity="1" clause_i="1"/> + <name name="add_handlers" arity="1" clause_i="1" since="OTP 21.0"/> <fsummary>Set up log handlers from the application's configuration parameters.</fsummary> <desc> @@ -507,7 +516,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name name="add_handlers" arity="1" clause_i="2"/> + <name name="add_handlers" arity="1" clause_i="2" since="OTP 21.0"/> <fsummary>Setup logger handlers.</fsummary> <type name="config_handler"/> <desc> @@ -550,7 +559,7 @@ start(_, []) -> </func> <func> - <name name="add_primary_filter" arity="2"/> + <name name="add_primary_filter" arity="2" since="OTP 21.0"/> <fsummary>Add a primary filter to Logger.</fsummary> <desc> <p>Add a primary filter to Logger.</p> @@ -591,16 +600,16 @@ start(_, []) -> </func> <func> - <name name="get_config" arity="0"/> + <name name="get_config" arity="0" since="OTP 21.0"/> <fsummary>Look up the current Logger configuration</fsummary> <desc> - <p>Look up all current Logger configuration, including primary - and handler configuration, and module level settings.</p> + <p>Look up all current Logger configuration, including primary, + handler, and proxy configuration, and module level settings.</p> </desc> </func> <func> - <name name="get_handler_config" arity="0"/> + <name name="get_handler_config" arity="0" since="OTP 21.0"/> <fsummary>Look up the current configuration for all handlers.</fsummary> <desc> <p>Look up the current configuration for all handlers.</p> @@ -608,7 +617,7 @@ start(_, []) -> </func> <func> - <name name="get_handler_config" arity="1"/> + <name name="get_handler_config" arity="1" since="OTP 21.0"/> <fsummary>Look up the current configuration for the given handler.</fsummary> <desc> @@ -617,7 +626,7 @@ start(_, []) -> </func> <func> - <name name="get_handler_ids" arity="0"/> + <name name="get_handler_ids" arity="0" since="OTP 21.0"/> <fsummary>Look up the identities for all installed handlers.</fsummary> <desc> <p>Look up the identities for all installed handlers.</p> @@ -625,7 +634,7 @@ start(_, []) -> </func> <func> - <name name="get_primary_config" arity="0"/> + <name name="get_primary_config" arity="0" since="OTP 21.0"/> <fsummary>Look up the current primary configuration for Logger.</fsummary> <desc> <p>Look up the current primary configuration for Logger.</p> @@ -633,7 +642,18 @@ start(_, []) -> </func> <func> - <name name="get_module_level" arity="0"/> + <name name="get_proxy_config" arity="0" since="OTP 21.3"/> + <fsummary>Look up the current configuration for the Logger proxy.</fsummary> + <desc> + <p>Look up the current configuration for the Logger proxy.</p> + <p>For more information about the proxy, see + section <seealso marker="logger_chapter#proxy">Logger + Proxy</seealso> in the Kernel User's Guide.</p> + </desc> + </func> + + <func> + <name name="get_module_level" arity="0" since="OTP 21.0"/> <fsummary>Look up all current module levels.</fsummary> <desc> <p>Look up all current module levels. Returns a list @@ -645,7 +665,7 @@ start(_, []) -> </func> <func> - <name name="get_module_level" arity="1"/> + <name name="get_module_level" arity="1" since="OTP 21.0"/> <fsummary>Look up the current level for the given modules.</fsummary> <desc> <p>Look up the current level for the given modules. Returns a @@ -657,7 +677,7 @@ start(_, []) -> </func> <func> - <name name="get_process_metadata" arity="0"/> + <name name="get_process_metadata" arity="0" since="OTP 21.0"/> <fsummary>Retrieve data set with set_process_metadata/1.</fsummary> <desc> <p>Retrieve data set @@ -669,7 +689,7 @@ start(_, []) -> </func> <func> - <name name="remove_handler" arity="1"/> + <name name="remove_handler" arity="1" since="OTP 21.0"/> <fsummary>Remove the handler with the specified identity.</fsummary> <desc> <p>Remove the handler identified by <c><anno>HandlerId</anno></c>.</p> @@ -677,7 +697,7 @@ start(_, []) -> </func> <func> - <name name="remove_handler_filter" arity="2"/> + <name name="remove_handler_filter" arity="2" since="OTP 21.0"/> <fsummary>Remove a filter from the specified handler.</fsummary> <desc> <p>Remove the filter identified @@ -687,7 +707,7 @@ start(_, []) -> </func> <func> - <name name="remove_primary_filter" arity="1"/> + <name name="remove_primary_filter" arity="1" since="OTP 21.0"/> <fsummary>Remove a primary filter from Logger.</fsummary> <desc> <p>Remove the primary filter identified @@ -696,7 +716,7 @@ start(_, []) -> </func> <func> - <name name="set_application_level" arity="2"/> + <name name="set_application_level" arity="2" since="OTP 21.1"/> <fsummary>Set the log level for all modules in the specified application.</fsummary> <desc> <p>Set the log level for all the modules of the specified application.</p> @@ -707,7 +727,7 @@ start(_, []) -> </func> <func> - <name name="set_handler_config" arity="2"/> + <name name="set_handler_config" arity="2" since="OTP 21.0"/> <fsummary>Set configuration data for the specified handler.</fsummary> <desc> <p>Set configuration data for the specified handler. This @@ -728,11 +748,11 @@ start(_, []) -> </func> <func> - <name name="set_handler_config" arity="3" clause_i="1"/> - <name name="set_handler_config" arity="3" clause_i="2"/> - <name name="set_handler_config" arity="3" clause_i="3"/> - <name name="set_handler_config" arity="3" clause_i="4"/> - <name name="set_handler_config" arity="3" clause_i="5"/> + <name name="set_handler_config" arity="3" clause_i="1" since="OTP 21.0"/> + <name name="set_handler_config" arity="3" clause_i="2" since="OTP 21.0"/> + <name name="set_handler_config" arity="3" clause_i="3" since="OTP 21.0"/> + <name name="set_handler_config" arity="3" clause_i="4" since="OTP 21.0"/> + <name name="set_handler_config" arity="3" clause_i="5" since="OTP 21.0"/> <fsummary>Add or update configuration data for the specified handler.</fsummary> <type variable="HandlerId"/> @@ -748,6 +768,14 @@ start(_, []) -> exists, its associated value will be changed to the given value. If it does not exist, it will be added.</p> + <p>If the value is incomplete, which for example can be the + case for the <c>config</c> key, it is up to the handler + implementation how the unspecified parts are set. For all + handlers in the Kernel application, unspecified data for + the <c>config</c> key is set to default values. To update + only specified data, and keep the existing configuration for + the rest, use <seealso marker="#update_handler_config-3"> + <c>update_handler_config/3</c></seealso>.</p> <p>See the definition of the <seealso marker="#type-handler_config"> <c>handler_config()</c></seealso> type for more @@ -756,7 +784,7 @@ start(_, []) -> </func> <func> - <name name="set_primary_config" arity="1"/> + <name name="set_primary_config" arity="1" since="OTP 21.0"/> <fsummary>Set primary configuration data for Logger.</fsummary> <desc> <p>Set primary configuration data for Logger. This @@ -774,9 +802,9 @@ start(_, []) -> </func> <func> - <name name="set_primary_config" arity="2" clause_i="1"/> - <name name="set_primary_config" arity="2" clause_i="2"/> - <name name="set_primary_config" arity="2" clause_i="3"/> + <name name="set_primary_config" arity="2" clause_i="1" since="OTP 21.0"/> + <name name="set_primary_config" arity="2" clause_i="2" since="OTP 21.0"/> + <name name="set_primary_config" arity="2" clause_i="3" since="OTP 21.0"/> <fsummary>Add or update primary configuration data for Logger.</fsummary> <type variable="Level" name_i="1"/> <type variable="FilterDefault" name_i="2"/> @@ -790,7 +818,28 @@ start(_, []) -> </func> <func> - <name name="set_module_level" arity="2"/> + <name name="set_proxy_config" arity="1" since="OTP 21.3"/> + <fsummary>Set configuration data for the Logger proxy.</fsummary> + <desc> + <p>Set configuration data for the Logger proxy. This + overwrites the current proxy configuration. Keys that are not + specified in the <c><anno>Config</anno></c> map gets default + values.</p> + <p>To modify the existing configuration, + use <seealso marker="#update_proxy_config-1"> + <c>update_proxy_config/1</c></seealso>, or, if a more + complex merge is needed, read the current configuration + with <seealso marker="#get_proxy_config-0"><c>get_proxy_config/0</c> + </seealso>, then do the merge before writing the new + configuration back with this function.</p> + <p>For more information about the proxy, see + section <seealso marker="logger_chapter#proxy">Logger + Proxy</seealso> in the Kernel User's Guide.</p> + </desc> + </func> + + <func> + <name name="set_module_level" arity="2" since="OTP 21.0"/> <fsummary>Set the log level for the specified modules.</fsummary> <desc> <p>Set the log level for the specified modules.</p> @@ -830,7 +879,7 @@ start(_, []) -> </func> <func> - <name name="set_process_metadata" arity="1"/> + <name name="set_process_metadata" arity="1" since="OTP 21.0"/> <fsummary>Set metadata to use when logging from current process.</fsummary> <desc> <p>Set metadata which Logger shall automatically insert in @@ -849,7 +898,7 @@ start(_, []) -> </func> <func> - <name name="unset_application_level" arity="1"/> + <name name="unset_application_level" arity="1" since="OTP 21.1"/> <fsummary>Unset the log level for all modules in the specified application.</fsummary> <desc> <p>Unset the log level for all the modules of the specified application.</p> @@ -860,7 +909,7 @@ start(_, []) -> </func> <func> - <name name="unset_module_level" arity="0"/> + <name name="unset_module_level" arity="0" since="OTP 21.0"/> <fsummary>Remove module specific log settings for all modules.</fsummary> <desc> <p>Remove module specific log settings. After this, the @@ -869,7 +918,7 @@ start(_, []) -> </func> <func> - <name name="unset_module_level" arity="1"/> + <name name="unset_module_level" arity="1" since="OTP 21.0"/> <fsummary>Remove module specific log settings for the given modules.</fsummary> <desc> @@ -879,7 +928,7 @@ start(_, []) -> </func> <func> - <name name="unset_process_metadata" arity="0"/> + <name name="unset_process_metadata" arity="0" since="OTP 21.0"/> <fsummary>Delete data set with set_process_metadata/1.</fsummary> <desc> <p>Delete data set @@ -891,7 +940,7 @@ start(_, []) -> </func> <func> - <name name="update_formatter_config" arity="2"/> + <name name="update_formatter_config" arity="2" since="OTP 21.0"/> <fsummary>Update the formatter configuration for the specified handler.</fsummary> <desc> <p>Update the formatter configuration for the specified handler.</p> @@ -906,7 +955,7 @@ start(_, []) -> </func> <func> - <name name="update_formatter_config" arity="3"/> + <name name="update_formatter_config" arity="3" since="OTP 21.0"/> <fsummary>Update the formatter configuration for the specified handler.</fsummary> <desc> <p>Update the formatter configuration for the specified handler.</p> @@ -917,7 +966,7 @@ start(_, []) -> </func> <func> - <name name="update_handler_config" arity="2"/> + <name name="update_handler_config" arity="2" since="OTP 21.0"/> <fsummary>Update configuration data for the specified handler.</fsummary> <desc> <p>Update configuration data for the specified handler. This function @@ -933,7 +982,43 @@ logger:set_handler_config(HandlerId, maps:merge(Old, Config)). </func> <func> - <name name="update_primary_config" arity="1"/> + <name name="update_handler_config" arity="3" clause_i="1" since="OTP 21.2"/> + <name name="update_handler_config" arity="3" clause_i="2" since="OTP 21.2"/> + <name name="update_handler_config" arity="3" clause_i="3" since="OTP 21.2"/> + <name name="update_handler_config" arity="3" clause_i="4" since="OTP 21.2"/> + <name name="update_handler_config" arity="3" clause_i="5" since="OTP 21.2"/> + <fsummary>Add or update configuration data for the specified + handler.</fsummary> + <type variable="HandlerId"/> + <type variable="Level" name_i="1"/> + <type variable="FilterDefault" name_i="2"/> + <type variable="Filters" name_i="3"/> + <type variable="Formatter" name_i="4"/> + <type variable="Config" name_i="5"/> + <type variable="Return"/> + <desc> + <p>Add or update configuration data for the specified + handler. If the given <c><anno>Key</anno></c> already + exists, its associated value will be changed + to the given value. If it does not exist, it will + be added.</p> + <p>If the value is incomplete, which for example can be the + case for the <c>config</c> key, it is up to the handler + implementation how the unspecified parts are set. For all + handlers in the Kernel application, unspecified data for + the <c>config</c> key is not changed. To reset unspecified + data to default values, + use <seealso marker="#set_handler_config-3"> + <c>set_handler_config/3</c></seealso>.</p> + <p>See the definition of + the <seealso marker="#type-handler_config"> + <c>handler_config()</c></seealso> type for more + information about the different parameters.</p> + </desc> + </func> + + <func> + <name name="update_primary_config" arity="1" since="OTP 21.0"/> <fsummary>Update primary configuration data for Logger.</fsummary> <desc> <p>Update primary configuration data for Logger. This function @@ -949,7 +1034,7 @@ logger:set_primary_config(maps:merge(Old, Config)). </func> <func> - <name name="update_process_metadata" arity="1"/> + <name name="update_process_metadata" arity="1" since="OTP 21.0"/> <fsummary>Set or update metadata to use when logging from current process.</fsummary> <desc> @@ -966,6 +1051,25 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </seealso>.</p> </desc> </func> + + <func> + <name name="update_proxy_config" arity="1" since="OTP 21.3"/> + <fsummary>Update configuration data for the Logger proxy.</fsummary> + <desc> + <p>Update configuration data for the Logger proxy. This function + behaves as if it was implemented as follows:</p> + <code type="erl"> +Old = logger:get_proxy_config(), +logger:set_proxy_config(maps:merge(Old, Config)). + </code> + <p>To overwrite the existing configuration without any merge, + use <seealso marker="#set_proxy_config-1"><c>set_proxy_config/1</c> + </seealso>.</p> + <p>For more information about the proxy, see + section <seealso marker="logger_chapter#proxy">Logger + Proxy</seealso> in the Kernel User's Guide.</p> + </desc> + </func> </funcs> <section> @@ -974,7 +1078,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </section> <funcs> <func> - <name name="compare_levels" arity="2"/> + <name name="compare_levels" arity="2" since="OTP 21.0"/> <fsummary>Compare the severity of two log levels.</fsummary> <desc> <p>Compare the severity of two log levels. Returns <c>gt</c> @@ -985,7 +1089,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </func> <func> - <name name="format_report" arity="1"/> + <name name="format_report" arity="1" since="OTP 21.0"/> <fsummary>Convert a log message on report form to {Format, Args}.</fsummary> <desc> <p>Convert a log message on report form to <c>{Format, @@ -1015,7 +1119,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). <funcs> <func> - <name>HModule:adding_handler(Config1) -> {ok, Config2} | {error, + <name since="OTP 21.0">HModule:adding_handler(Config1) -> {ok, Config2} | {error, Reason}</name> <fsummary>An instance of this handler is about to be added.</fsummary> <type> @@ -1041,10 +1145,11 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </func> <func> - <name>HModule:changing_config(Config1, Config2) -> {ok, Config3} | {error, Reason}</name> + <name since="OTP 21.2">HModule:changing_config(SetOrUpdate, OldConfig, NewConfig) -> {ok, Config} | {error, Reason}</name> <fsummary>The configuration for this handler is about to change.</fsummary> <type> - <v>Config1 = Config2 = Config3 = + <v>SetOrUpdate = set | update</v> + <v>OldConfig = NewConfig = Config = <seealso marker="#type-handler_config">handler_config()</seealso></v> <v>Reason = term()</v> </type> @@ -1053,19 +1158,52 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). <p>The function is called on a temporary process when the configuration for a handler is about to change. The purpose is to verify and act on the new configuration.</p> - <p><c>Config1</c> is the existing configuration - and <c>Config2</c> is the new configuration.</p> + <p><c>OldConfig</c> is the existing configuration + and <c>NewConfig</c> is the new configuration.</p> <p>The handler identity is associated with the <c>id</c> key - in <c>Config1</c>.</p> + in <c>OldConfig</c>.</p> + <p><c>SetOrUpdate</c> has the value <c>set</c> if the + configuration change originates from a call to + <seealso marker="#set_handler_config-2"> + <c>set_handler_config/2,3</c></seealso>, and <c>update</c> + if it originates from <seealso marker="#update_handler_config-2"> + <c>update_handler_config/2,3</c></seealso>. The handler can + use this parameteter to decide how to update the value of + the <c>config</c> field, that is, the handler specific + configuration data. Typically, if <c>SetOrUpdate</c> + equals <c>set</c>, values that are not specified must be + given their default values. If <c>SetOrUpdate</c> + equals <c>update</c>, the values found in <c>OldConfig</c> + must be used instead.</p> <p>If everything succeeds, the callback function must return a - possibly adjusted configuration in <c>{ok,Config3}</c>.</p> + possibly adjusted configuration in <c>{ok,Config}</c>.</p> <p>If the configuration is faulty, the callback function must return <c>{error,Reason}</c>.</p> </desc> </func> <func> - <name>HModule:log(LogEvent, Config) -> void()</name> + <name since="OTP 21.2">HModule:filter_config(Config) -> FilteredConfig</name> + <fsummary>Remove internal data from configuration.</fsummary> + <type> + <v>Config = FilteredConfig = + <seealso marker="#type-handler_config">handler_config()</seealso></v> + </type> + <desc> + <p>This callback function is optional.</p> + <p>The function is called when one of the Logger API functions + for fetching the handler configuration is called, for + example + <seealso marker="#get_handler_config-1"> + <c>logger:get_handler_config/1</c></seealso>.</p> + <p>It allows the handler to remove internal data fields from + its configuration data before it is returned to the + caller.</p> + </desc> + </func> + + <func> + <name since="OTP 21.0">HModule:log(LogEvent, Config) -> void()</name> <fsummary>Log the given log event.</fsummary> <type> <v>LogEvent = @@ -1088,7 +1226,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </func> <func> - <name>HModule:removing_handler(Config) -> ok</name> + <name since="OTP 21.0">HModule:removing_handler(Config) -> ok</name> <fsummary>The given handler is about to be removed.</fsummary> <type> <v>Config = @@ -1116,7 +1254,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). <funcs> <func> - <name>FModule:check_config(FConfig) -> ok | {error, Reason}</name> + <name since="OTP 21.0">FModule:check_config(FConfig) -> ok | {error, Reason}</name> <fsummary>Validate the given formatter configuration.</fsummary> <type> <v>FConfig = @@ -1136,7 +1274,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). <item><seealso marker="logger#set_handler_config-2"> <c>logger:set_handler_config/2,3</c></seealso></item> <item><seealso marker="logger#update_handler_config-2"> - <c>logger:updata_handler_config/2</c></seealso></item> + <c>logger:update_handler_config/2,3</c></seealso></item> <item><seealso marker="logger#update_formatter_config-2"> <c>logger:update_formatter_config/2</c></seealso></item> </list> @@ -1147,7 +1285,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </desc> </func> <func> - <name>FModule:format(LogEvent, FConfig) -> FormattedLogEntry</name> + <name since="OTP 21.0">FModule:format(LogEvent, FConfig) -> FormattedLogEntry</name> <fsummary>Format the given log event.</fsummary> <type> <v>LogEvent = diff --git a/lib/kernel/doc/src/logger_chapter.xml b/lib/kernel/doc/src/logger_chapter.xml index fdb21fa24c..03b9edcf8f 100644 --- a/lib/kernel/doc/src/logger_chapter.xml +++ b/lib/kernel/doc/src/logger_chapter.xml @@ -113,7 +113,10 @@ of functions on the form <c>logger:Level/1,2,3</c>, which are all shortcuts for <seealso marker="logger#log-2"> - <c>logger:log(Level,Arg1[,Arg2[,Arg3]])</c></seealso>.</p> + <c>logger:log(Level,Arg1[,Arg2[,Arg3]])</c></seealso>.</p> + <p>The macros are defined in <c>logger.hrl</c>, which is included + in a module with the directive</p> + <code>-include_lib("kernel/include/logger.hrl").</code> <p>The difference between using the macros and the exported functions is that macros add location (originator) information to the metadata, and performs lazy evaluation by wrapping the @@ -384,8 +387,8 @@ logger:debug(#{got => connection_request, id => Id, state => State}, <p>In addition to the mandatory callback function <c>log/2</c>, a handler module can export the optional callback - functions <c>adding_handler/1</c>, <c>changing_config/2</c> - and <c>removing_handler/1</c>. See + functions <c>adding_handler/1</c>, <c>changing_config/3</c>, + <c>filter_config/1</c>, and <c>removing_handler/1</c>. See section <seealso marker="logger#handler_callback_functions">Handler Callback Functions</seealso> in the logger(3) manual page for more information about these function.</p> @@ -555,7 +558,7 @@ logger:debug(#{got => connection_request, id => Id, state => State}, <item><seealso marker="logger#set_handler_config-2"> <c>set_handler_config/2,3</c></seealso></item> <item><seealso marker="logger#update_handler_config-2"> - <c>update_handler_config/2</c></seealso></item> + <c>update_handler_config/2,3</c></seealso></item> <item><seealso marker="logger#add_handler_filter-3"> <c>add_handler_filter/3</c></seealso></item> <item><seealso marker="logger#remove_handler_filter-2"> @@ -690,8 +693,10 @@ logger:debug(#{got => connection_request, id => Id, state => State}, with <seealso marker="#logger_sasl_compatible"> <c>logger_sasl_compatible</c></seealso>.</p> <p>With this parameter, you can modify or disable the default - handler, add custom handlers and primary logger filters, and - set log levels per module.</p> + handler, add custom handlers and primary logger filters, set + log levels per module, and modify + the <seealso marker="#proxy">proxy</seealso> + configuration.</p> <p><c>Config</c> is any (zero or more) of the following:</p> <taglist> <tag><c>{handler, default, undefined}</c></tag> @@ -704,9 +709,13 @@ logger:debug(#{got => connection_request, id => Id, state => State}, <item> <p>If <c>HandlerId</c> is <c>default</c>, then this entry modifies the default handler, equivalent to calling</p> - <pre><seealso marker="logger#set_handler_config-2"> - logger:set_handler_config(default, Module, HandlerConfig) - </seealso></pre> + <pre><seealso marker="logger#remove_handler-1"> + logger:remove_handler(default) + </seealso></pre> + <p>followed by</p> + <pre><seealso marker="logger#add_handler-3"> + logger:add_handler(default, Module, HandlerConfig) + </seealso></pre> <p>For all other values of <c>HandlerId</c>, this entry adds a new handler, equivalent to calling</p> <pre><seealso marker="logger:add_handler/3"> @@ -739,6 +748,14 @@ logger:debug(#{got => connection_request, id => Id, state => State}, <p>for each <c>Module</c>.</p> <p>Multiple entries of this type are allowed.</p> </item> + <tag><c>{proxy, ProxyConfig}</c></tag> + <item> + <p>Sets the proxy configuration, equivalent to calling</p> + <pre><seealso marker="logger#set_proxy_config/1"> + logger:set_proxy_config(ProxyConfig) + </seealso></pre> + <p>Only one entry of this type is allowed.</p> + </item> </taglist> <p>See section <seealso marker="#config_examples">Configuration @@ -1024,7 +1041,8 @@ ok</pre> <list> <item><c>adding_handler(Config)</c></item> <item><c>removing_handler(Config)</c></item> - <item><c>changing_config(OldConfig, NewConfig)</c></item> + <item><c>changing_config(SetOrUpdate, OldConfig, NewConfig)</c></item> + <item><c>filter_config(Config)</c></item> </list> <p>When a handler is added, by for example a call to <seealso marker="logger#add_handler-3"> @@ -1043,11 +1061,18 @@ ok</pre> <p>When <seealso marker="logger#set_handler_config-2"> <c>logger:set_handler_config/2,3</c></seealso> or <seealso marker="logger#update_handler_config/2"> - <c>logger:update_handler_config/2</c></seealso> is called, + <c>logger:update_handler_config/2,3</c></seealso> is called, Logger - calls <c>HModule:changing_config(OldConfig, NewConfig)</c>. If + calls <c>HModule:changing_config(SetOrUpdate, OldConfig, NewConfig)</c>. If this function returns <c>{ok,NewConfig1}</c>, Logger writes <c>NewConfig1</c> to the configuration database.</p> + <p>When <seealso marker="logger#get_config-0"> + <c>logger:get_config/0</c></seealso> or + <seealso marker="logger#get_handler_config-0"> + <c>logger:get_handler_config/0,1</c></seealso> is called, + Logger calls <c>HModule:filter_config(Config)</c>. This function + must return the handler configuration where internal data is + removed.</p> <p>A simple handler that prints to the terminal can be implemented as follows:</p> @@ -1319,9 +1344,50 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h, </section> <section> + <marker id="proxy"/> + <title>Logger Proxy</title> + <p>The Logger proxy is an Erlang process which is part of the + Kernel application's supervision tree. During startup, the proxy + process registers itself as the <c>system_logger</c>, meaning + that log events produced by the emulator are sent to this + process.</p> + <p>When a log event is issued on a process which has its group + leader on a remote node, Logger automatically forwards the log + event to the group leader's node. To achieve this, it first + sends the log event as an Erlang message from the original + client process to the proxy on the local node, and the proxy in + turn forwards the event to the proxy on the remote node.</p> + <p>When receiving a log event, either from the emulator or from a + remote node, the proxy calls the Logger API to log the event.</p> + <p>The proxy process is overload protected in the same way as + described in + section <seealso marker="#overload_protection">Protecting the + Handler from Overload</seealso>, but with the following default + values:</p> + <code> + #{sync_mode_qlen => 500, + drop_mode_qlen => 1000, + flush_qlen => 5000, + burst_limit_enable => false, + overload_kill_enable => false}</code> + <p>For log events from the emulator, synchronous message passing + mode is not applicable, since all messages are passed + asynchronously by the emulator. Drop mode is achieved by setting + the <c>system_logger</c> to <c>undefined</c>, forcing the + emulator to drop events until it is set back to the proxy pid + again.</p> + <p>The proxy uses <seealso marker="erts:erlang#send_nosuspend/2"> + <c>erlang:send_nosuspend/2</c></seealso> when sending log + events to a remote node. If the message could not be sent + without suspending the sender, it is dropped. This is to avoid + blocking the proxy process.</p> + </section> + + <section> <title>See Also</title> <p> <seealso marker="disk_log"><c>disk_log(3)</c></seealso>, + <seealso marker="erts:erlang"><c>erlang(3)</c></seealso>, <seealso marker="error_logger"><c>error_logger(3)</c></seealso>, <seealso marker="logger"><c>logger(3)</c></seealso>, <seealso marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seealso>, diff --git a/lib/kernel/doc/src/logger_disk_log_h.xml b/lib/kernel/doc/src/logger_disk_log_h.xml index dfe2ab3275..5b2374690e 100644 --- a/lib/kernel/doc/src/logger_disk_log_h.xml +++ b/lib/kernel/doc/src/logger_disk_log_h.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger_disk_log_h.xml</file> </header> - <module>logger_disk_log_h</module> + <module since="OTP 21.0">logger_disk_log_h</module> <modulesummary>A disk_log based handler for Logger</modulesummary> <description> @@ -66,6 +66,10 @@ corresponds to the <c>name</c> property in the <seealso marker="disk_log#open-1"><c>dlog_option()</c></seealso> datatype.</p> + <p>The value is set when the handler is added, and it can not + be changed in runtime.</p> + <p>Defaults to the same name as the handler identity, in the + current directory.</p> </item> <tag><c>type</c></tag> <item> @@ -73,6 +77,8 @@ corresponds to the <c>type</c> property in the <seealso marker="disk_log#open-1"><c>dlog_option()</c></seealso> datatype.</p> + <p>The value is set when the handler is added, and it can not + be changed in runtime.</p> <p>Defaults to <c>wrap</c>.</p> </item> <tag><c>max_no_files</c></tag> @@ -82,6 +88,8 @@ corresponds to the <c>MaxNoFiles</c> element in the <c>size</c> property in the <seealso marker="disk_log#open-1"><c>dlog_option()</c></seealso> datatype.</p> + <p>The value is set when the handler is added, and it can not + be changed in runtime.</p> <p>Defaults to <c>10</c>.</p> <p>The setting has no effect on a halt log.</p> </item> @@ -93,6 +101,8 @@ corresponds to the <c>MaxNoBytes</c> element in the <c>size</c> property in the <seealso marker="disk_log#open-1"><c>dlog_option()</c></seealso> datatype.</p> + <p>The value is set when the handler is added, and it can not + be changed in runtime.</p> <p>Defaults to <c>1048576</c> bytes for a wrap log, and <c>infinity</c> for a halt log.</p> </item> @@ -138,7 +148,7 @@ erl -kernel logger '[{handler,default,logger_disk_log_h, <funcs> <func> - <name name="filesync" arity="1" clause_i="1"/> + <name name="filesync" arity="1" clause_i="1" since="OTP 21.0"/> <fsummary>Writes buffered data to disk.</fsummary> <desc> <p>Write buffered data to disk.</p> diff --git a/lib/kernel/doc/src/logger_filters.xml b/lib/kernel/doc/src/logger_filters.xml index 90f1fcc270..0a02342864 100644 --- a/lib/kernel/doc/src/logger_filters.xml +++ b/lib/kernel/doc/src/logger_filters.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger_filters.xml</file> </header> - <module>logger_filters</module> + <module since="OTP 21.0">logger_filters</module> <modulesummary>Filters to use with Logger.</modulesummary> <description> @@ -51,7 +51,7 @@ <funcs> <func> - <name name="domain" arity="2"/> + <name name="domain" arity="2" since="OTP 21.0"/> <fsummary>Filter log events based on the domain field in metadata.</fsummary> <desc> @@ -152,7 +152,7 @@ ok</code> </func> <func> - <name name="level" arity="2"/> + <name name="level" arity="2" since="OTP 21.0"/> <fsummary>Filter log events based on the log level.</fsummary> <desc> <p>This filter provides a way of filtering log events based @@ -212,7 +212,7 @@ ok</code> </func> <func> - <name name="progress" arity="2"/> + <name name="progress" arity="2" since="OTP 21.0"/> <fsummary>Filter progress reports from supervisor and application_controller.</fsummary> <desc> <p>This filter matches all progress reports @@ -227,7 +227,7 @@ ok</code> </func> <func> - <name name="remote_gl" arity="2"/> + <name name="remote_gl" arity="2" since="OTP 21.0"/> <fsummary>Filter events with group leader on remote node.</fsummary> <desc> <p>This filter matches all events originating from a process diff --git a/lib/kernel/doc/src/logger_formatter.xml b/lib/kernel/doc/src/logger_formatter.xml index 24772fd6c4..6dc83d24e1 100644 --- a/lib/kernel/doc/src/logger_formatter.xml +++ b/lib/kernel/doc/src/logger_formatter.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger_formatter.xml</file> </header> - <module>logger_formatter</module> + <module since="OTP 21.0">logger_formatter</module> <modulesummary>Default formatter for Logger.</modulesummary> <description> @@ -289,7 +289,7 @@ exit_reason: "It crashed"</code> <funcs> <func> - <name name="check_config" arity="1"/> + <name name="check_config" arity="1" since="OTP 21.0"/> <fsummary>Validates the given formatter configuration.</fsummary> <desc> <p>The function is called by Logger when the formatter @@ -303,14 +303,14 @@ exit_reason: "It crashed"</code> <item><seealso marker="logger#set_handler_config-2"> <c>logger:set_handler_config/2,3</c></seealso></item> <item><seealso marker="logger#update_handler_config-2"> - <c>logger:updata_handler_config/2</c></seealso></item> + <c>logger:update_handler_config/2</c></seealso></item> <item><seealso marker="logger#update_formatter_config-2"> <c>logger:update_formatter_config/2</c></seealso></item> </list> </desc> </func> <func> - <name name="format" arity="2"/> + <name name="format" arity="2" since="OTP 21.0"/> <fsummary>Formats the given message.</fsummary> <desc> <p>This the formatter callback function to be called from diff --git a/lib/kernel/doc/src/logger_std_h.xml b/lib/kernel/doc/src/logger_std_h.xml index fcd8189bae..fcd180abd6 100644 --- a/lib/kernel/doc/src/logger_std_h.xml +++ b/lib/kernel/doc/src/logger_std_h.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger_std_h.xml</file> </header> - <module>logger_std_h</module> + <module since="OTP 21.0">logger_std_h</module> <modulesummary>Standard handler for Logger.</modulesummary> <description> @@ -74,7 +74,9 @@ circular logging. Use the disk_log handler, <seealso marker="logger_disk_log_h"><c>logger_disk_log_h</c></seealso>, for this.</p> - <p> Defaults to <c>standard_io</c>.</p> + <p>The value is set when the handler is added, and it can not + be changed in runtime.</p> + <p>Defaults to <c>standard_io</c>.</p> </item> <tag><c>filesync_repeat_interval</c></tag> <item> @@ -119,7 +121,7 @@ erl -kernel logger '[{handler,default,logger_std_h, <funcs> <func> - <name name="filesync" arity="1" clause_i="1"/> + <name name="filesync" arity="1" clause_i="1" since="OTP 21.0"/> <fsummary>Writes buffered data to disk.</fsummary> <desc> <p>Write buffered data to disk.</p> diff --git a/lib/kernel/doc/src/net_adm.xml b/lib/kernel/doc/src/net_adm.xml index 6957a3b5e4..c3e1619f1b 100644 --- a/lib/kernel/doc/src/net_adm.xml +++ b/lib/kernel/doc/src/net_adm.xml @@ -28,7 +28,7 @@ <date>1996-09-10</date> <rev>A</rev> </header> - <module>net_adm</module> + <module since="">net_adm</module> <modulesummary>Various Erlang net administration routines.</modulesummary> <description> <p>This module contains various network utility functions.</p> @@ -36,7 +36,7 @@ <funcs> <func> - <name name="dns_hostname" arity="1"/> + <name name="dns_hostname" arity="1" since=""/> <fsummary>Official name of a host.</fsummary> <desc> <p>Returns the official name of <c><anno>Host</anno></c>, or @@ -46,7 +46,7 @@ </func> <func> - <name name="host_file" arity="0"/> + <name name="host_file" arity="0" since=""/> <fsummary>Read file <c>.hosts.erlang</c>.</fsummary> <desc> <p>Reads file <c>.hosts.erlang</c>, see section @@ -58,7 +58,7 @@ </func> <func> - <name name="localhost" arity="0"/> + <name name="localhost" arity="0" since=""/> <fsummary>Name of the local host.</fsummary> <desc> <p>Returns the name of the local host. If Erlang was started @@ -68,8 +68,8 @@ </func> <func> - <name name="names" arity="0"/> - <name name="names" arity="1"/> + <name name="names" arity="0" since=""/> + <name name="names" arity="1" since=""/> <fsummary>Names of Erlang nodes at a host.</fsummary> <desc> <p>Similar to <c>epmd -names</c>, see @@ -86,7 +86,7 @@ </func> <func> - <name name="ping" arity="1"/> + <name name="ping" arity="1" since=""/> <fsummary>Set up a connection to a node.</fsummary> <desc> <p>Sets up a connection to <c><anno>Node</anno></c>. Returns @@ -95,8 +95,8 @@ </func> <func> - <name name="world" arity="0"/> - <name name="world" arity="1"/> + <name name="world" arity="0" since=""/> + <name name="world" arity="1" since=""/> <fsummary>Lookup and connect to all nodes at all hosts in <c>.hosts.erlang</c>.</fsummary> <type name="verbosity"/> @@ -117,8 +117,8 @@ </func> <func> - <name name="world_list" arity="1"/> - <name name="world_list" arity="2"/> + <name name="world_list" arity="1" since=""/> + <name name="world_list" arity="2" since=""/> <fsummary>Lookup and connect to all nodes at specified hosts.</fsummary> <type name="verbosity"/> <desc> diff --git a/lib/kernel/doc/src/net_kernel.xml b/lib/kernel/doc/src/net_kernel.xml index d3bd7365e2..419d3cad84 100644 --- a/lib/kernel/doc/src/net_kernel.xml +++ b/lib/kernel/doc/src/net_kernel.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1996</year><year>2017</year> + <year>1996</year><year>2018</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -28,7 +28,7 @@ <date>1996-09-10</date> <rev>A</rev> </header> - <module>net_kernel</module> + <module since="">net_kernel</module> <modulesummary>Erlang networking kernel.</modulesummary> <description> <p>The net kernel is a system process, registered as @@ -81,7 +81,7 @@ $ <input>erl -sname foobar</input></pre> <funcs> <func> - <name name="allow" arity="1"/> + <name name="allow" arity="1" since=""/> <fsummary>Permit access to a specified set of nodes</fsummary> <desc> <p>Permits access to the specified set of nodes.</p> @@ -98,7 +98,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="connect_node" arity="1"/> + <name name="connect_node" arity="1" since=""/> <fsummary>Establish a connection to a node.</fsummary> <desc> <p>Establishes a connection to <c><anno>Node</anno></c>. Returns @@ -110,7 +110,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="get_net_ticktime" arity="0"/> + <name name="get_net_ticktime" arity="0" since=""/> <fsummary>Get <c>net_ticktime</c>.</fsummary> <desc> <p>Gets <c>net_ticktime</c> (see @@ -131,7 +131,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="getopts" arity="2"/> + <name name="getopts" arity="2" since="OTP 19.1"/> <fsummary>Get distribution socket options.</fsummary> <desc> <p>Get one or more options for the distribution socket @@ -146,8 +146,8 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="monitor_nodes" arity="1"/> - <name name="monitor_nodes" arity="2"/> + <name name="monitor_nodes" arity="1" since=""/> + <name name="monitor_nodes" arity="2" since=""/> <fsummary>Subscribe to node status change messages.</fsummary> <desc> <p>The calling process subscribes or unsubscribes to node @@ -267,8 +267,8 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="set_net_ticktime" arity="1"/> - <name name="set_net_ticktime" arity="2"/> + <name name="set_net_ticktime" arity="1" since=""/> + <name name="set_net_ticktime" arity="2" since=""/> <fsummary>Set <c>net_ticktime</c>.</fsummary> <desc> <p>Sets <c>net_ticktime</c> (see @@ -324,7 +324,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="setopts" arity="2"/> + <name name="setopts" arity="2" since="OTP 19.1"/> <fsummary>Set distribution socket options.</fsummary> <desc> <p>Set one or more options for distribution sockets. @@ -345,9 +345,9 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name>start([Name]) -> {ok, pid()} | {error, Reason}</name> - <name>start([Name, NameType]) -> {ok, pid()} | {error, Reason}</name> - <name>start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason}</name> + <name since="">start([Name]) -> {ok, pid()} | {error, Reason}</name> + <name since="">start([Name, NameType]) -> {ok, pid()} | {error, Reason}</name> + <name since="">start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason}</name> <fsummary>Turn an Erlang runtime system into a distributed node.</fsummary> <type> <v>Name = atom()</v> @@ -364,7 +364,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="stop" arity="0"/> + <name name="stop" arity="0" since=""/> <fsummary>Turn a node into a non-distributed Erlang runtime system.</fsummary> <desc> <p>Turns a distributed node into a non-distributed node. For diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml index c766c18233..021ecfa40d 100644 --- a/lib/kernel/doc/src/notes.xml +++ b/lib/kernel/doc/src/notes.xml @@ -31,6 +31,228 @@ </header> <p>This document describes the changes made to the Kernel application.</p> +<section><title>Kernel 6.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + A new function, <c>logger:update_handler_config/3</c> is + added, and the handler callback <c>changing_config</c> + now has a new argument, <c>SetOrUpdate</c>, which + indicates if the configuration change comes from + <c>set_handler_config/2,3</c> or + <c>update_handler_config/2,3</c>.</p> + <p> + This allows the handler to consistently merge the new + configuration with the old (if the change comes from + <c>update_handler_config/2,3</c>) or with the default (if + the change comes from <c>set_handler_config/2,3</c>).</p> + <p> + The built-in handlers <c>logger_std_h</c> and + <c>logger_disk_log_h</c> are updated accordingly. A bug + which could cause inconsistency between the handlers' + internal state and the stored configuration is also + corrected.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-15364</p> + </item> + <item> + <p> + Fix fallback when custom erl_epmd client does not + implement address_please.</p> + <p> + Own Id: OTP-15388 Aux Id: PR-1983 </p> + </item> + <item> + <p> + The logger ets table did not have the + <c>read_concurrency</c> option. This is now added.</p> + <p> + Own Id: OTP-15453 Aux Id: ERL-782 </p> + </item> + <item> + <p> + During system start, logger has a simple handler which + prints to stdout. After the kernel supervision is + started, this handler is removed and replaced by the + default handler. Due to a bug, logger earlier issued a + debug printout saying it received an unexpected message, + which was the EXIT message from the simple handler's + process. This is now corrected. The simple handler's + process now unlinks from the logger process before + terminating.</p> + <p> + Own Id: OTP-15466 Aux Id: ERL-788 </p> + </item> + <item> + <p> + The logger handler <c>logger_std_h</c> would not + re-create it's log file if it was removed. Due to this it + could not be used with tools like 'logrotate'. This is + now corrected.</p> + <p> + Own Id: OTP-15469</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + A function <c>inet:getifaddrs/1</c> that takes a list + with a namespace option has been added, for platforms + that support that feature, for example Linux (only?).</p> + <p> + Own Id: OTP-15121 Aux Id: ERIERL-189, PR-1974 </p> + </item> + <item> + <p>Added the <c>nopush</c> option for TCP sockets, which + corresponds to <c>TCP_NOPUSH</c> on *BSD and + <c>TCP_CORK</c> on Linux.</p> + <p>This is also used internally in <c>file:sendfile</c> + to reduce latency on subsequent send operations.</p> + <p> + Own Id: OTP-15357 Aux Id: ERL-698 </p> + </item> + <item> + <p> + Optimize handling of send_delay for tcp sockes to better + work with the new pollthread implementation introduced in + OTP-21.</p> + <p> + Own Id: OTP-15471 Aux Id: ERIERL-229 </p> + </item> + </list> + </section> + +</section> + +<section><title>Kernel 6.1.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix bug causing net_kernel process crash on connection + attempt from node with name identical to local node.</p> + <p> + Own Id: OTP-15438 Aux Id: ERL-781 </p> + </item> + </list> + </section> + +</section> + +<section><title>Kernel 6.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The values <c>all</c> and <c>none</c> are documented as + valid value for the Kernel configuration parameter + <c>logger_level</c>, but would cause a crash during node + start. This is now corrected.</p> + <p> + Own Id: OTP-15143</p> + </item> + <item> + <p> + Fix some potential buggy behavior in how ticks are sent + on inter node distribution connections. Tick is now sent + to c-node even if there are unsent buffered data, as + c-nodes need ticks in order to send reply ticks. The + amount of sent data was also calculated wrongly when + ticks were suppressed due to unsent buffered data.</p> + <p> + Own Id: OTP-15162 Aux Id: ERIERL-191 </p> + </item> + <item> + <p> + Non semantic change in dist_util.erl to silence dialyzer + warning.</p> + <p> + Own Id: OTP-15170</p> + </item> + <item> + <p> + Fixed <c>net_kernel:connect_node(node())</c> to return + <c>true</c> (and do nothing) as it always has before + OTP-21.0. Also documented this successful "self connect" + as the expected behavior.</p> + <p> + Own Id: OTP-15182 Aux Id: ERL-643 </p> + </item> + <item> + <p> + The single_line option on logger_formatter would in some + cases add an unwanted comma after the association arrows + in a map. This is now corrected.</p> + <p> + Own Id: OTP-15228</p> + </item> + <item> + <p> + Improved robustness of distribution connection setup. In + OTP-21.0 a truly asynchronous connection setup was + introduced. This is further improvement on that work to + make the emulator more robust and also be able to recover + in cases when involved Erlang processes misbehave.</p> + <p> + Own Id: OTP-15297 Aux Id: OTP-15279, OTP-15280 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + A new macro, <c>?LOG(Level,...)</c>, is added. This is + equivalent to the existing <c>?LOG_<LEVEL>(...)</c> + macros.</p> + <p> + A new variant of Logger report callback is added, which + takes an extra argument containing options for size + limiting and line breaks. Module <c>proc_lib</c> in + <c>STDLIB</c> uses this for crash reports.</p> + <p> + Logger configuration is now checked a bit more for + errors.</p> + <p> + Own Id: OTP-15132</p> + </item> + <item> + <p> + The socket options <c>recvtos</c>, <c>recvttl</c>, + <c>recvtclass</c> and <c>pktoptions</c> have been + implemented in the socket modules. See the documentation + for the <c>gen_tcp</c>, <c>gen_udp</c> and <c>inet</c> + modules. Note that support for these in the runtime + system is platform dependent. Especially for + <c>pktoptions</c> which is very Linux specific and + obsoleted by the RFCs that defined it.</p> + <p> + Own Id: OTP-15145 Aux Id: ERIERL-187 </p> + </item> + <item> + <p> + Add <c>logger:set_application_level/2</c> for setting the + logger level of all modules in one application.</p> + <p> + Own Id: OTP-15146</p> + </item> + </list> + </section> + +</section> + <section><title>Kernel 6.0.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/kernel/doc/src/os.xml b/lib/kernel/doc/src/os.xml index c95e615c6b..0500e4cfb3 100644 --- a/lib/kernel/doc/src/os.xml +++ b/lib/kernel/doc/src/os.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>os</module> + <module since="">os</module> <modulesummary>Operating system-specific functions.</modulesummary> <description> <p>The functions in this module are operating system-specific. @@ -134,8 +134,8 @@ <funcs> <func> - <name name="cmd" arity="1"/> - <name name="cmd" arity="2"/> + <name name="cmd" arity="1" since=""/> + <name name="cmd" arity="2" since="OTP 20.2.3"/> <fsummary>Execute a command in a shell of the target OS.</fsummary> <desc> <p>Executes <c><anno>Command</anno></c> in a command shell of the @@ -173,8 +173,8 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="find_executable" arity="1"/> - <name name="find_executable" arity="2"/> + <name name="find_executable" arity="1" since=""/> + <name name="find_executable" arity="2" since=""/> <fsummary>Absolute filename of a program.</fsummary> <desc> <p>These two functions look up an executable program, with the @@ -190,7 +190,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="getenv" arity="0"/> + <name name="getenv" arity="0" since=""/> <fsummary>List all environment variables.</fsummary> <desc> <p>Returns a list of all environment variables. @@ -205,7 +205,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="getenv" arity="1"/> + <name name="getenv" arity="1" since=""/> <fsummary>Get the value of an environment variable.</fsummary> <desc> <p>Returns the <c><anno>Value</anno></c> of the environment variable @@ -220,7 +220,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="getenv" arity="2"/> + <name name="getenv" arity="2" since="OTP 18.0"/> <fsummary>Get the value of an environment variable.</fsummary> <desc> <p>Returns the <c><anno>Value</anno></c> of the environment variable @@ -235,7 +235,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="getpid" arity="0"/> + <name name="getpid" arity="0" since=""/> <fsummary>Return the process identifier of the emulator process.</fsummary> <desc> @@ -251,7 +251,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="putenv" arity="2"/> + <name name="putenv" arity="2" since=""/> <fsummary>Set a new value for an environment variable.</fsummary> <desc> <p>Sets a new <c><anno>Value</anno></c> for environment variable @@ -277,7 +277,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="set_signal" arity="2"/> + <name name="set_signal" arity="2" since="OTP 20.0"/> <fsummary>Enables or disables handling of OS signals.</fsummary> <desc> <p>Enables or disables OS signals.</p> @@ -304,7 +304,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="system_time" arity="0"/> + <name name="system_time" arity="0" since="OTP 18.0"/> <fsummary>Current OS system time.</fsummary> <desc> <p>Returns the current @@ -317,7 +317,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="system_time" arity="1"/> + <name name="system_time" arity="1" since="OTP 18.0"/> <fsummary>Current OS system time.</fsummary> <desc> <p>Returns the current @@ -332,7 +332,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="timestamp" arity="0"/> + <name name="timestamp" arity="0" since=""/> <fsummary>Current OS system time on the <c>erlang:timestamp/0</c> format.</fsummary> <type_desc variable="Timestamp">Timestamp = {MegaSecs, Secs, MicroSecs}</type_desc> <desc> @@ -373,7 +373,7 @@ calendar:now_to_universal_time(TS), </func> <func> - <name name="perf_counter" arity="0"/> + <name name="perf_counter" arity="0" since="OTP 19.0"/> <fsummary>Returns a performance counter</fsummary> <desc> <p>Returns the current performance counter value in <c>perf_counter</c> @@ -383,7 +383,7 @@ calendar:now_to_universal_time(TS), </desc> </func> <func> - <name name="perf_counter" arity="1"/> + <name name="perf_counter" arity="1" since="OTP 19.0"/> <fsummary>Returns a performance counter</fsummary> <desc><p>Returns a performance counter that can be used as a very fast and high resolution timestamp. This counter is read directly from the hardware or operating @@ -397,7 +397,7 @@ calendar:now_to_universal_time(TS), </desc> </func> <func> - <name name="type" arity="0"/> + <name name="type" arity="0" since=""/> <fsummary>Return the OS family and, in some cases, the OS name of the current OS.</fsummary> <desc> @@ -417,7 +417,7 @@ calendar:now_to_universal_time(TS), </func> <func> - <name name="unsetenv" arity="1"/> + <name name="unsetenv" arity="1" since="OTP R16B03"/> <fsummary>Delete an environment variable.</fsummary> <desc> <p>Deletes the environment variable <c><anno>VarName</anno></c>.</p> @@ -429,7 +429,7 @@ calendar:now_to_universal_time(TS), </func> <func> - <name name="version" arity="0"/> + <name name="version" arity="0" since=""/> <fsummary>Return the OS versions.</fsummary> <desc> <p>Returns the OS version. diff --git a/lib/kernel/doc/src/pg2.xml b/lib/kernel/doc/src/pg2.xml index 0631b317b4..058d711756 100644 --- a/lib/kernel/doc/src/pg2.xml +++ b/lib/kernel/doc/src/pg2.xml @@ -32,7 +32,7 @@ <rev>A2</rev> <file>pg2.xml</file> </header> - <module>pg2</module> + <module since="">pg2</module> <modulesummary>Distributed named process groups.</modulesummary> <description> <p>This module implements process groups. Each message can be sent @@ -66,7 +66,7 @@ <funcs> <func> - <name name="create" arity="1"/> + <name name="create" arity="1" since=""/> <fsummary>Create a new, empty process group.</fsummary> <desc> <p>Creates a new, empty process group. The group is globally @@ -75,7 +75,7 @@ </func> <func> - <name name="delete" arity="1"/> + <name name="delete" arity="1" since=""/> <fsummary>Delete a process group.</fsummary> <desc> <p>Deletes a process group.</p> @@ -83,7 +83,7 @@ </func> <func> - <name name="get_closest_pid" arity="1"/> + <name name="get_closest_pid" arity="1" since=""/> <fsummary>Common dispatch function.</fsummary> <desc> <p>A useful dispatch function that can be used from @@ -93,7 +93,7 @@ </func> <func> - <name name="get_local_members" arity="1"/> + <name name="get_local_members" arity="1" since=""/> <fsummary>Return all local processes in a group.</fsummary> <desc> <p>Returns all processes running on the local node in the @@ -104,7 +104,7 @@ </func> <func> - <name name="get_members" arity="1"/> + <name name="get_members" arity="1" since=""/> <fsummary>Return all processes in a group.</fsummary> <desc> <p>Returns all processes in the group <c>Name</c>. This @@ -114,7 +114,7 @@ </func> <func> - <name name="join" arity="2"/> + <name name="join" arity="2" since=""/> <fsummary>Join a process to a group.</fsummary> <desc> <p>Joins the process <c>Pid</c> to the group <c>Name</c>. @@ -124,7 +124,7 @@ </func> <func> - <name name="leave" arity="2"/> + <name name="leave" arity="2" since=""/> <fsummary>Make a process leave a group.</fsummary> <desc> <p>Makes the process <c>Pid</c> leave the group <c>Name</c>. @@ -134,8 +134,8 @@ </func> <func> - <name name="start" arity="0"/> - <name name="start_link" arity="0"/> + <name name="start" arity="0" since=""/> + <name name="start_link" arity="0" since=""/> <fsummary>Start the <c>pg2</c> server.</fsummary> <desc> <p>Starts the <c>pg2</c> server. Normally, the server does not need @@ -149,7 +149,7 @@ </func> <func> - <name name="which_groups" arity="0"/> + <name name="which_groups" arity="0" since=""/> <fsummary>Return a list of all known groups.</fsummary> <desc> <p>Returns a list of all known groups.</p> diff --git a/lib/kernel/doc/src/rpc.xml b/lib/kernel/doc/src/rpc.xml index fab616e630..c55454506e 100644 --- a/lib/kernel/doc/src/rpc.xml +++ b/lib/kernel/doc/src/rpc.xml @@ -28,7 +28,7 @@ <date>1996-09-10</date> <rev>A</rev> </header> - <module>rpc</module> + <module since="">rpc</module> <modulesummary>Remote Procedure Call services.</modulesummary> <description> <p>This module contains services similar to Remote @@ -51,7 +51,7 @@ <funcs> <func> - <name name="abcast" arity="2"/> + <name name="abcast" arity="2" since=""/> <fsummary>Broadcast a message asynchronously to a registered process on all nodes.</fsummary> <desc> @@ -61,7 +61,7 @@ </func> <func> - <name name="abcast" arity="3"/> + <name name="abcast" arity="3" since=""/> <fsummary>Broadcast a message asynchronously to a registered process on specific nodes.</fsummary> <desc> @@ -72,7 +72,7 @@ </func> <func> - <name name="async_call" arity="4"/> + <name name="async_call" arity="4" since=""/> <fsummary>Evaluate a function call on a node, asynchronous version.</fsummary> <desc> @@ -98,7 +98,7 @@ </func> <func> - <name name="block_call" arity="4"/> + <name name="block_call" arity="4" since=""/> <fsummary>Evaluate a function call on a node in the RPC server's context.</fsummary> <desc> @@ -115,7 +115,7 @@ </func> <func> - <name name="block_call" arity="5"/> + <name name="block_call" arity="5" since=""/> <fsummary>Evaluate a function call on a node in the RPC server's context.</fsummary> <desc> @@ -127,7 +127,7 @@ </func> <func> - <name name="call" arity="4"/> + <name name="call" arity="4" since=""/> <fsummary>Evaluate a function call on a node.</fsummary> <desc> <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>, @@ -138,7 +138,7 @@ </func> <func> - <name name="call" arity="5"/> + <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>, @@ -158,7 +158,7 @@ </func> <func> - <name name="cast" arity="4"/> + <name name="cast" arity="4" since=""/> <fsummary>Run a function on a node ignoring the result.</fsummary> <desc> <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>, @@ -171,7 +171,7 @@ </func> <func> - <name name="eval_everywhere" arity="3"/> + <name name="eval_everywhere" arity="3" since=""/> <fsummary>Run a function on all nodes, ignoring the result.</fsummary> <desc> <p>Equivalent to <c>eval_everywhere([node()|nodes()], @@ -181,7 +181,7 @@ </func> <func> - <name name="eval_everywhere" arity="4"/> + <name name="eval_everywhere" arity="4" since=""/> <fsummary>Run a function on specific nodes, ignoring the result.</fsummary> <desc> @@ -192,7 +192,7 @@ </func> <func> - <name name="multi_server_call" arity="2"/> + <name name="multi_server_call" arity="2" since=""/> <fsummary>Interact with the servers on a number of nodes.</fsummary> <desc> <p>Equivalent to <c>multi_server_call([node()|nodes()], @@ -201,7 +201,7 @@ </func> <func> - <name name="multi_server_call" arity="3"/> + <name name="multi_server_call" arity="3" since=""/> <fsummary>Interact with the servers on a number of nodes.</fsummary> <desc> <p>Can be used when interacting with servers called @@ -224,7 +224,7 @@ </func> <func> - <name name="multicall" arity="3"/> + <name name="multicall" arity="3" since=""/> <fsummary>Evaluate a function call on a number of nodes.</fsummary> <desc> <p>Equivalent to <c>multicall([node()|nodes()], <anno>Module</anno>, @@ -233,7 +233,7 @@ </func> <func> - <name name="multicall" arity="4" clause_i="1"/> + <name name="multicall" arity="4" clause_i="1" since=""/> <fsummary>Evaluate a function call on a number of nodes.</fsummary> <desc> <p>Equivalent to <c>multicall(<anno>Nodes</anno>, <anno>Module</anno>, @@ -242,7 +242,7 @@ </func> <func> - <name name="multicall" arity="4" clause_i="2"/> + <name name="multicall" arity="4" clause_i="2" since=""/> <fsummary>Evaluate a function call on a number of nodes.</fsummary> <desc> <p>Equivalent to <c>multicall([node()|nodes()], <anno>Module</anno>, @@ -252,7 +252,7 @@ </func> <func> - <name name="multicall" arity="5"/> + <name name="multicall" arity="5" since=""/> <fsummary>Evaluate a function call on a number of nodes.</fsummary> <desc> <p>In contrast to an RPC, a multicall is an RPC that is sent @@ -288,7 +288,7 @@ </func> <func> - <name name="nb_yield" arity="1"/> + <name name="nb_yield" arity="1" since=""/> <fsummary>Deliver the result of evaluating a function call on a node (non-blocking).</fsummary> <desc> @@ -297,7 +297,7 @@ </func> <func> - <name name="nb_yield" arity="2"/> + <name name="nb_yield" arity="2" since=""/> <fsummary>Deliver the result of evaluating a function call on a node (non-blocking).</fsummary> <desc> @@ -315,7 +315,7 @@ </func> <func> - <name name="parallel_eval" arity="1"/> + <name name="parallel_eval" arity="1" since=""/> <fsummary>Evaluate many function calls on all nodes in parallel.</fsummary> <desc> @@ -328,7 +328,7 @@ </func> <func> - <name name="pinfo" arity="1"/> + <name name="pinfo" arity="1" since=""/> <fsummary>Information about a process.</fsummary> <desc> <p>Location transparent version of the BIF @@ -337,8 +337,8 @@ </func> <func> - <name name="pinfo" arity="2" clause_i="1"/> - <name name="pinfo" arity="2" clause_i="2"/> + <name name="pinfo" arity="2" clause_i="1" since=""/> + <name name="pinfo" arity="2" clause_i="2" since=""/> <fsummary>Information about a process.</fsummary> <desc> <p>Location transparent version of the BIF @@ -347,7 +347,7 @@ </func> <func> - <name name="pmap" arity="3"/> + <name name="pmap" arity="3" since=""/> <fsummary>Parallel evaluation of mapping a function over a list.</fsummary> <desc> @@ -360,7 +360,7 @@ </func> <func> - <name name="sbcast" arity="2"/> + <name name="sbcast" arity="2" since=""/> <fsummary>Broadcast a message synchronously to a registered process on all nodes.</fsummary> <desc> @@ -370,7 +370,7 @@ </func> <func> - <name name="sbcast" arity="3"/> + <name name="sbcast" arity="3" since=""/> <fsummary>Broadcast a message synchronously to a registered process on specific nodes.</fsummary> <desc> @@ -391,7 +391,7 @@ </func> <func> - <name name="server_call" arity="4"/> + <name name="server_call" arity="4" since=""/> <fsummary>Interact with a server on a node.</fsummary> <desc> <p>Can be used when interacting with a server called @@ -410,7 +410,7 @@ </func> <func> - <name name="yield" arity="1"/> + <name name="yield" arity="1" since=""/> <fsummary>Deliver the result of evaluating a function call on a node (blocking).</fsummary> <desc> diff --git a/lib/kernel/doc/src/seq_trace.xml b/lib/kernel/doc/src/seq_trace.xml index 1a4a74419a..aa29223dd0 100644 --- a/lib/kernel/doc/src/seq_trace.xml +++ b/lib/kernel/doc/src/seq_trace.xml @@ -28,7 +28,7 @@ <date>1998-04-16</date> <rev>A</rev> </header> - <module>seq_trace</module> + <module since="">seq_trace</module> <modulesummary>Sequential tracing of messages.</modulesummary> <description> <p>Sequential tracing makes it possible to trace all messages @@ -51,7 +51,7 @@ </datatypes> <funcs> <func> - <name name="set_token" arity="1"/> + <name name="set_token" arity="1" since=""/> <fsummary>Set the trace token</fsummary> <desc> <p>Sets the trace token for the calling process to <c><anno>Token</anno></c>. @@ -71,7 +71,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="set_token" arity="2"/> + <name name="set_token" arity="2" since=""/> <fsummary>Set a component of the trace token</fsummary> <type name="component"/> <type name="flag"/> @@ -158,7 +158,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="get_token" arity="0"/> + <name name="get_token" arity="0" since=""/> <fsummary>Return the value of the trace token</fsummary> <desc> <p>Returns the value of the trace token for the calling process. @@ -169,7 +169,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="get_token" arity="1"/> + <name name="get_token" arity="1" since=""/> <fsummary>Return the value of a trace token component</fsummary> <type name="component"/> <type name="flag"/> @@ -182,7 +182,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="print" arity="1"/> + <name name="print" arity="1" since=""/> <fsummary>Put the Erlang term <c>TraceInfo</c>into the sequential trace output</fsummary> <desc> <p>Puts the Erlang term <c><anno>TraceInfo</anno></c> into the sequential @@ -192,7 +192,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="print" arity="2"/> + <name name="print" arity="2" since=""/> <fsummary>Put the Erlang term <c>TraceInfo</c>into the sequential trace output</fsummary> <desc> <p>Same as <c>print/1</c> with the additional condition that @@ -201,7 +201,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="reset_trace" arity="0"/> + <name name="reset_trace" arity="0" since=""/> <fsummary>Stop all sequential tracing on the local node</fsummary> <desc> <p>Sets the trace token to empty for all processes on the @@ -213,7 +213,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="set_system_tracer" arity="1"/> + <name name="set_system_tracer" arity="1" since=""/> <fsummary>Set the system tracer</fsummary> <type name="tracer"/> <desc> @@ -227,7 +227,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="get_system_tracer" arity="0"/> + <name name="get_system_tracer" arity="0" since=""/> <fsummary>Return the pid() or port() of the current system tracer.</fsummary> <type name="tracer"/> <desc> diff --git a/lib/kernel/doc/src/wrap_log_reader.xml b/lib/kernel/doc/src/wrap_log_reader.xml index 7fb9c1c023..5f37e7ec5f 100644 --- a/lib/kernel/doc/src/wrap_log_reader.xml +++ b/lib/kernel/doc/src/wrap_log_reader.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>wrap_log_reader.sgml</file> </header> - <module>wrap_log_reader</module> + <module since="">wrap_log_reader</module> <modulesummary>A service to read internally formatted wrap disk logs. </modulesummary> <description> @@ -65,8 +65,8 @@ <funcs> <func> - <name name="chunk" arity="1"/> - <name name="chunk" arity="2"/> + <name name="chunk" arity="1" since=""/> + <name name="chunk" arity="2" since=""/> <fsummary>Read a chunk of objects written to a wrap log.</fsummary> <type name="chunk_ret"/> <desc> @@ -105,7 +105,7 @@ </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a log.</fsummary> <desc> <p>Closes a log file properly.</p> @@ -113,8 +113,8 @@ </func> <func> - <name name="open" arity="1"/> - <name name="open" arity="2"/> + <name name="open" arity="1" since=""/> + <name name="open" arity="2" since=""/> <fsummary>Open a log file.</fsummary> <type name="open_ret"/> <desc> diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile index 7db05ebc90..43b776f37e 100644 --- a/lib/kernel/src/Makefile +++ b/lib/kernel/src/Makefile @@ -118,6 +118,8 @@ MODULES = \ logger_h_common \ logger_filters \ logger_formatter \ + logger_olp \ + logger_proxy \ logger_server \ logger_simple_h \ logger_sup \ @@ -150,7 +152,7 @@ INTERNAL_HRL_FILES= application_master.hrl disk_log.hrl \ inet_dns.hrl inet_res.hrl \ inet_boot.hrl inet_config.hrl inet_int.hrl \ inet_dns_record_adts.hrl \ - logger_internal.hrl logger_h_common.hrl + logger_internal.hrl logger_olp.hrl logger_h_common.hrl ERL_FILES= $(MODULES:%=%.erl) @@ -278,6 +280,8 @@ $(EBIN)/logger_config.beam: logger_internal.hrl ../include/logger.hrl $(EBIN)/logger_disk_log_h.beam: logger_h_common.hrl logger_internal.hrl ../include/logger.hrl ../include/file.hrl $(EBIN)/logger_filters.beam: logger_internal.hrl ../include/logger.hrl $(EBIN)/logger_formatter.beam: logger_internal.hrl ../include/logger.hrl +$(EBIN)/logger_olp.beam: logger_olp.hrl logger_internal.hrl +$(EBIN)/logger_proxy.beam: logger_internal.hrl $(EBIN)/logger_server.beam: logger_internal.hrl ../include/logger.hrl $(EBIN)/logger_simple_h.beam: logger_internal.hrl ../include/logger.hrl $(EBIN)/logger_std_h.beam: logger_h_common.hrl logger_internal.hrl ../include/logger.hrl ../include/file.hrl diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl index b7e8868911..7a14e2635c 100644 --- a/lib/kernel/src/erl_epmd.erl +++ b/lib/kernel/src/erl_epmd.erl @@ -77,8 +77,8 @@ stop() -> %% -spec port_please(Name, Host) -> {ok, Port, Version} | noport when - Name :: string(), - Host :: inet:ip_address(), + Name :: atom() | string(), + Host :: atom() | string() | inet:ip_address(), Port :: non_neg_integer(), Version :: non_neg_integer(). @@ -86,8 +86,8 @@ port_please(Node, Host) -> port_please(Node, Host, infinity). -spec port_please(Name, Host, Timeout) -> {ok, Port, Version} | noport when - Name :: string(), - Host :: inet:ip_address(), + Name :: atom() | string(), + Host :: atom() | string() | inet:ip_address(), Timeout :: non_neg_integer() | infinity, Port :: non_neg_integer(), Version :: non_neg_integer(). diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index 5dd68dc285..9f22eb6aaa 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -154,6 +154,15 @@ 'running' | 'multicast' | 'loopback']} | {'hwaddr', ether_address()}. +-type getifaddrs_ifopts() :: + [Ifopt :: {flags, Flags :: [up | broadcast | loopback | + pointtopoint | running | multicast]} | + {addr, Addr :: ip_address()} | + {netmask, Netmask :: ip_address()} | + {broadaddr, Broadaddr :: ip_address()} | + {dstaddr, Dstaddr :: ip_address()} | + {hwaddr, Hwaddr :: [byte()]}]. + -type address_family() :: 'inet' | 'inet6' | 'local'. -type socket_protocol() :: 'tcp' | 'udp' | 'sctp'. -type socket_type() :: 'stream' | 'dgram' | 'seqpacket'. @@ -321,32 +330,32 @@ getopts(Socket, Opts) -> Other end. --spec getifaddrs(Socket :: socket()) -> - {'ok', [string()]} | {'error', posix()}. - +-spec getifaddrs( + [Option :: {netns, Namespace :: file:filename_all()}] + | socket()) -> + {'ok', [{Ifname :: string(), + Ifopts :: getifaddrs_ifopts()}]} + | {'error', posix()}. +getifaddrs(Opts) when is_list(Opts) -> + withsocket(fun(S) -> prim_inet:getifaddrs(S) end, Opts); getifaddrs(Socket) -> prim_inet:getifaddrs(Socket). --spec getifaddrs() -> {ok, Iflist} | {error, posix()} when - Iflist :: [{Ifname,[Ifopt]}], - Ifname :: string(), - Ifopt :: {flags,[Flag]} | {addr,Addr} | {netmask,Netmask} - | {broadaddr,Broadaddr} | {dstaddr,Dstaddr} - | {hwaddr,Hwaddr}, - Flag :: up | broadcast | loopback | pointtopoint - | running | multicast, - Addr :: ip_address(), - Netmask :: ip_address(), - Broadaddr :: ip_address(), - Dstaddr :: ip_address(), - Hwaddr :: [byte()]. - +-spec getifaddrs() -> + {'ok', [{Ifname :: string(), + Ifopts :: getifaddrs_ifopts()}]} + | {'error', posix()}. getifaddrs() -> withsocket(fun(S) -> prim_inet:getifaddrs(S) end). --spec getiflist(Socket :: socket()) -> - {'ok', [string()]} | {'error', posix()}. +-spec getiflist( + [Option :: {netns, Namespace :: file:filename_all()}] + | socket()) -> + {'ok', [string()]} | {'error', posix()}. + +getiflist(Opts) when is_list(Opts) -> + withsocket(fun(S) -> prim_inet:getiflist(S) end, Opts); getiflist(Socket) -> prim_inet:getiflist(Socket). @@ -363,11 +372,19 @@ getiflist() -> ifget(Socket, Name, Opts) -> prim_inet:ifget(Socket, Name, Opts). --spec ifget(Name :: string() | atom(), Opts :: [if_getopt()]) -> +-spec ifget( + Name :: string() | atom(), + Opts :: [if_getopt() | + {netns, Namespace :: file:filename_all()}]) -> {'ok', [if_getopt_result()]} | {'error', posix()}. ifget(Name, Opts) -> - withsocket(fun(S) -> prim_inet:ifget(S, Name, Opts) end). + {NSOpts,IFOpts} = + lists:partition( + fun ({netns,_}) -> true; + (_) -> false + end, Opts), + withsocket(fun(S) -> prim_inet:ifget(S, Name, IFOpts) end, NSOpts). -spec ifset(Socket :: socket(), Name :: string() | atom(), @@ -377,11 +394,19 @@ ifget(Name, Opts) -> ifset(Socket, Name, Opts) -> prim_inet:ifset(Socket, Name, Opts). --spec ifset(Name :: string() | atom(), Opts :: [if_setopt()]) -> +-spec ifset( + Name :: string() | atom(), + Opts :: [if_setopt() | + {netns, Namespace :: file:filename_all()}]) -> 'ok' | {'error', posix()}. ifset(Name, Opts) -> - withsocket(fun(S) -> prim_inet:ifset(S, Name, Opts) end). + {NSOpts,IFOpts} = + lists:partition( + fun ({netns,_}) -> true; + (_) -> false + end, Opts), + withsocket(fun(S) -> prim_inet:ifset(S, Name, IFOpts) end, NSOpts). -spec getif() -> {'ok', [{ip_address(), ip_address() | 'undefined', ip_address()}]} | @@ -391,10 +416,14 @@ getif() -> withsocket(fun(S) -> getif(S) end). %% backwards compatible getif --spec getif(Socket :: socket()) -> +-spec getif( + [Option :: {netns, Namespace :: file:filename_all()}] + | socket()) -> {'ok', [{ip_address(), ip_address() | 'undefined', ip_address()}]} | {'error', posix()}. +getif(Opts) when is_list(Opts) -> + withsocket(fun(S) -> getif(S) end, Opts); getif(Socket) -> case prim_inet:getiflist(Socket) of {ok, IfList} -> @@ -415,7 +444,10 @@ getif(Socket) -> end. withsocket(Fun) -> - case inet_udp:open(0,[]) of + withsocket(Fun, []). +%% +withsocket(Fun, Opts) -> + case inet_udp:open(0, Opts) of {ok,Socket} -> Res = Fun(Socket), inet_udp:close(Socket), diff --git a/lib/kernel/src/inet_int.hrl b/lib/kernel/src/inet_int.hrl index c8e09d18ad..f6525d7261 100644 --- a/lib/kernel/src/inet_int.hrl +++ b/lib/kernel/src/inet_int.hrl @@ -162,6 +162,7 @@ -define(INET_OPT_PKTOPTIONS, 45). -define(INET_OPT_TTL, 46). -define(INET_OPT_RECVTTL, 47). +-define(TCP_OPT_NOPUSH, 48). % Specific SCTP options: separate range: -define(SCTP_OPT_RTOINFO, 100). -define(SCTP_OPT_ASSOCINFO, 101). diff --git a/lib/kernel/src/inet_tcp_dist.erl b/lib/kernel/src/inet_tcp_dist.erl index d1701afdaa..c37212b0f9 100644 --- a/lib/kernel/src/inet_tcp_dist.erl +++ b/lib/kernel/src/inet_tcp_dist.erl @@ -450,7 +450,7 @@ get_tcp_address(Driver, Socket) -> get_address_resolver(EpmdModule) -> case erlang:function_exported(EpmdModule, address_please, 3) of true -> {EpmdModule, address_please}; - _ -> {inet, getaddr} + _ -> {erl_epmd, address_please} end. %% ------------------------------------------------------------ diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src index 4933eae76f..a1d9e8e215 100644 --- a/lib/kernel/src/kernel.app.src +++ b/lib/kernel/src/kernel.app.src @@ -68,6 +68,8 @@ logger_formatter, logger_h_common, logger_handler_watcher, + logger_olp, + logger_proxy, logger_server, logger_simple_h, logger_std_h, @@ -146,6 +148,6 @@ {logger_sasl_compatible, false} ]}, {mod, {kernel, []}}, - {runtime_dependencies, ["erts-10.0", "stdlib-3.5", "sasl-3.0"]} + {runtime_dependencies, ["erts-10.1", "stdlib-3.5", "sasl-3.0"]} ] }. diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src index 305a1c788c..ccf0a82ced 100644 --- a/lib/kernel/src/kernel.appup.src +++ b/lib/kernel/src/kernel.appup.src @@ -16,13 +16,42 @@ %% limitations under the License. %% %% %CopyrightEnd% +%% +%% We allow upgrade from, and downgrade to all previous +%% versions from the following OTP releases: +%% - OTP 20 +%% - OTP 21 +%% +%% We also allow upgrade from, and downgrade to all +%% versions that have branched off from the above +%% stated previous versions. +%% {"%VSN%", - %% Up from - max one major revision back - [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0 - {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-21 - %% Down to - max one major revision back - [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0 - {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21 -}. + [{<<"^5\\.3$">>,[restart_new_emulator]}, + {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.0$">>,[restart_new_emulator]}, + {<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.1$">>,[restart_new_emulator]}, + {<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], + [{<<"^5\\.3$">>,[restart_new_emulator]}, + {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.0$">>,[restart_new_emulator]}, + {<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.1$">>,[restart_new_emulator]}, + {<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. diff --git a/lib/kernel/src/logger.erl b/lib/kernel/src/logger.erl index 752dd8d493..abdd9a9ceb 100644 --- a/lib/kernel/src/logger.erl +++ b/lib/kernel/src/logger.erl @@ -43,10 +43,14 @@ get_module_level/0, get_module_level/1, set_primary_config/1, set_primary_config/2, set_handler_config/2, set_handler_config/3, - update_primary_config/1, update_handler_config/2, + set_proxy_config/1, + update_primary_config/1, + update_handler_config/2, update_handler_config/3, + update_proxy_config/1, update_formatter_config/2, update_formatter_config/3, get_primary_config/0, get_handler_config/1, get_handler_config/0, get_handler_ids/0, get_config/0, + get_proxy_config/0, add_handlers/1]). %% Private configuration @@ -121,6 +125,18 @@ {filters,log | stop,[{filter_id(),filter()}]} | {module_level,level(),[module()]}]. +-type olp_config() :: #{sync_mode_qlen => non_neg_integer(), + drop_mode_qlen => pos_integer(), + flush_qlen => pos_integer(), + burst_limit_enable => boolean(), + burst_limit_max_count => pos_integer(), + burst_limit_window_time => pos_integer(), + overload_kill_enable => boolean(), + overload_kill_qlen => pos_integer(), + overload_kill_mem_size => pos_integer(), + overload_kill_restart_after => + non_neg_integer() | infinity}. + -export_type([log_event/0, level/0, report/0, @@ -136,7 +152,8 @@ filter_arg/0, filter_return/0, config_handler/0, - formatter_config/0]). + formatter_config/0, + olp_config/0]). %%%----------------------------------------------------------------- %%% API @@ -389,6 +406,7 @@ set_primary_config(Key,Value) -> set_primary_config(Config) -> logger_server:set_config(primary,Config). + -spec set_handler_config(HandlerId,level,Level) -> Return when HandlerId :: handler_id(), Level :: level() | all | none, @@ -418,17 +436,50 @@ set_handler_config(HandlerId,Key,Value) -> set_handler_config(HandlerId,Config) -> logger_server:set_config(HandlerId,Config). +-spec set_proxy_config(Config) -> ok | {error,term()} when + Config :: olp_config(). +set_proxy_config(Config) -> + logger_server:set_config(proxy,Config). + -spec update_primary_config(Config) -> ok | {error,term()} when Config :: primary_config(). update_primary_config(Config) -> logger_server:update_config(primary,Config). +-spec update_handler_config(HandlerId,level,Level) -> Return when + HandlerId :: handler_id(), + Level :: level() | all | none, + Return :: ok | {error,term()}; + (HandlerId,filter_default,FilterDefault) -> Return when + HandlerId :: handler_id(), + FilterDefault :: log | stop, + Return :: ok | {error,term()}; + (HandlerId,filters,Filters) -> Return when + HandlerId :: handler_id(), + Filters :: [{filter_id(),filter()}], + Return :: ok | {error,term()}; + (HandlerId,formatter,Formatter) -> Return when + HandlerId :: handler_id(), + Formatter :: {module(), formatter_config()}, + Return :: ok | {error,term()}; + (HandlerId,config,Config) -> Return when + HandlerId :: handler_id(), + Config :: term(), + Return :: ok | {error,term()}. +update_handler_config(HandlerId,Key,Value) -> + logger_server:update_config(HandlerId,Key,Value). + -spec update_handler_config(HandlerId,Config) -> ok | {error,term()} when HandlerId :: handler_id(), Config :: handler_config(). update_handler_config(HandlerId,Config) -> logger_server:update_config(HandlerId,Config). +-spec update_proxy_config(Config) -> ok | {error,term()} when + Config :: olp_config(). +update_proxy_config(Config) -> + logger_server:update_config(proxy,Config). + -spec get_primary_config() -> Config when Config :: primary_config(). get_primary_config() -> @@ -439,7 +490,14 @@ get_primary_config() -> HandlerId :: handler_id(), Config :: handler_config(). get_handler_config(HandlerId) -> - logger_config:get(?LOGGER_TABLE,HandlerId). + case logger_config:get(?LOGGER_TABLE,HandlerId) of + {ok,#{module:=Module}=Config} -> + {ok,try Module:filter_config(Config) + catch _:_ -> Config + end}; + Error -> + Error + end. -spec get_handler_config() -> [Config] when Config :: handler_config(). @@ -455,6 +513,12 @@ get_handler_ids() -> {ok,#{handlers:=HandlerIds}} = logger_config:get(?LOGGER_TABLE,primary), HandlerIds. +-spec get_proxy_config() -> Config when + Config :: olp_config(). +get_proxy_config() -> + {ok,Config} = logger_config:get(?LOGGER_TABLE,proxy), + Config. + -spec update_formatter_config(HandlerId,FormatterConfig) -> ok | {error,term()} when HandlerId :: handler_id(), @@ -575,10 +639,12 @@ unset_process_metadata() -> -spec get_config() -> #{primary=>primary_config(), handlers=>[handler_config()], + proxy=>olp_config(), module_levels=>[{module(),level() | all | none}]}. get_config() -> #{primary=>get_primary_config(), handlers=>get_handler_config(), + proxy=>get_proxy_config(), module_levels=>lists:keysort(1,get_module_level())}. -spec internal_init_logger() -> ok | {error,term()}. @@ -641,6 +707,17 @@ init_kernel_handlers(Env) -> %% This function is responsible for resolving the handler config %% and then starting the correct handlers. This is done after the %% kernel supervisor tree has been started as it needs the logger_sup. +add_handlers(kernel) -> + Env = get_logger_env(kernel), + case get_proxy_opts(Env) of + undefined -> + add_handlers(kernel,Env); + Opts -> + case set_proxy_config(Opts) of + ok -> add_handlers(kernel,Env); + {error, Reason} -> {error,{bad_proxy_config,Reason}} + end + end; add_handlers(App) when is_atom(App) -> add_handlers(App,get_logger_env(App)); add_handlers(HandlerConfig) -> @@ -698,6 +775,8 @@ check_logger_config(kernel,[{filters,_,_}|Env]) -> check_logger_config(kernel,Env); check_logger_config(kernel,[{module_level,_,_}|Env]) -> check_logger_config(kernel,Env); +check_logger_config(kernel,[{proxy,_}|Env]) -> + check_logger_config(kernel,Env); check_logger_config(_,Bad) -> throw(Bad). @@ -753,6 +832,13 @@ get_primary_filters(Env) -> _ -> throw({multiple_filters,Env}) end. +get_proxy_opts(Env) -> + case [P || P={proxy,_} <- Env] of + [{proxy,Opts}] -> Opts; + [] -> undefined; + _ -> throw({multiple_proxies,Env}) + end. + %% This function looks at the kernel logger environment %% and updates it so that the correct logger is configured init_default_config(Type,Env) when Type==standard_io; @@ -849,30 +935,30 @@ log_allowed(Location,Level,Msg,Meta0) when is_map(Meta0) -> maps:merge(Location,maps:merge(proc_meta(),Meta0))), case node(maps:get(gl,Meta)) of Node when Node=/=node() -> - log_remote(Node,Level,Msg,Meta), - do_log_allowed(Level,Msg,Meta); + log_remote(Node,Level,Msg,Meta); _ -> - do_log_allowed(Level,Msg,Meta) - end. + ok + end, + do_log_allowed(Level,Msg,Meta,tid()). -do_log_allowed(Level,{Format,Args}=Msg,Meta) +do_log_allowed(Level,{Format,Args}=Msg,Meta,Tid) when ?IS_LEVEL(Level), is_list(Format), is_list(Args), is_map(Meta) -> - logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},tid()); -do_log_allowed(Level,Report,Meta) + logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},Tid); +do_log_allowed(Level,Report,Meta,Tid) when ?IS_LEVEL(Level), ?IS_REPORT(Report), is_map(Meta) -> logger_backend:log_allowed(#{level=>Level,msg=>{report,Report},meta=>Meta}, - tid()); -do_log_allowed(Level,String,Meta) + Tid); +do_log_allowed(Level,String,Meta,Tid) when ?IS_LEVEL(Level), ?IS_STRING(String), is_map(Meta) -> logger_backend:log_allowed(#{level=>Level,msg=>{string,String},meta=>Meta}, - tid()). + Tid). tid() -> ets:whereis(?LOGGER_TABLE). @@ -882,7 +968,7 @@ log_remote(Node,Level,Msg,Meta) -> log_remote(Node,{log,Level,Msg,Meta}). log_remote(Node,Request) -> - {logger,Node} ! Request, + logger_proxy:log({remote,Node,Request}), ok. add_default_metadata(Meta) -> diff --git a/lib/kernel/src/logger_config.erl b/lib/kernel/src/logger_config.erl index 6bfe658552..5024d20cfe 100644 --- a/lib/kernel/src/logger_config.erl +++ b/lib/kernel/src/logger_config.erl @@ -31,7 +31,9 @@ -include("logger_internal.hrl"). new(Name) -> - _ = ets:new(Name,[set,protected,named_table,{write_concurrency,true}]), + _ = ets:new(Name,[set,protected,named_table, + {read_concurrency,true}, + {write_concurrency,true}]), ets:whereis(Name). delete(Tid,Id) -> @@ -64,6 +66,8 @@ get(Tid,What) -> case ets:lookup(Tid,table_key(What)) of [{_,_,Config}] -> {ok,Config}; + [{_,Config}] when What=:=proxy -> + {ok,Config}; [] -> {error,{not_found,What}} end. @@ -77,10 +81,15 @@ get(Tid,What,Level) -> [Data] -> {ok,Data} 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}). +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 @@ -146,5 +155,6 @@ int_to_level(?LOG_ALL) -> all. %%%----------------------------------------------------------------- %%% Internal +table_key(proxy) -> ?PROXY_KEY; table_key(primary) -> ?PRIMARY_KEY; table_key(HandlerId) -> {?HANDLER_KEY,HandlerId}. diff --git a/lib/kernel/src/logger_disk_log_h.erl b/lib/kernel/src/logger_disk_log_h.erl index a8f141f135..47b39da900 100644 --- a/lib/kernel/src/logger_disk_log_h.erl +++ b/lib/kernel/src/logger_disk_log_h.erl @@ -19,183 +19,117 @@ %% -module(logger_disk_log_h). --behaviour(gen_server). - -include("logger.hrl"). -include("logger_internal.hrl"). -include("logger_h_common.hrl"). %%% API --export([start_link/3, info/1, filesync/1, reset/1]). +-export([filesync/1]). -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). +%% logger_h_common callbacks +-export([init/2, check_config/4, reset_state/2, + filesync/3, write/4, handle_info/3, terminate/3]). %% logger callbacks --export([log/2, adding_handler/1, removing_handler/1, changing_config/2]). - -%% handler internal --export([log_handler_info/4]). +-export([log/2, adding_handler/1, removing_handler/1, changing_config/3, + filter_config/1]). %%%=================================================================== %%% API %%%=================================================================== %%%----------------------------------------------------------------- -%%% Start a disk_log handler process and link to caller. -%%% This function is called by the kernel supervisor when this -%%% handler process gets added (as a result of calling add/3). --spec start_link(Name, Config, HandlerState) -> {ok,Pid} | {error,Reason} when - Name :: atom(), - Config :: logger:handler_config(), - HandlerState :: map(), - Pid :: pid(), - Reason :: term(). - -start_link(Name, Config, HandlerState) -> - proc_lib:start_link(?MODULE,init,[[Name,Config,HandlerState]]). - -%%%----------------------------------------------------------------- %%% -spec filesync(Name) -> ok | {error,Reason} when Name :: atom(), Reason :: handler_busy | {badarg,term()}. -filesync(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - disk_log_sync, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; filesync(Name) -> - {error,{badarg,{filesync,[Name]}}}. + logger_h_common:filesync(?MODULE,Name). + +%%%=================================================================== +%%% logger callbacks +%%%=================================================================== %%%----------------------------------------------------------------- -%%% --spec info(Name) -> Info | {error,Reason} when - Name :: atom(), - Info :: term(), - Reason :: handler_busy | {badarg,term()}. +%%% Handler being added +adding_handler(Config) -> + logger_h_common:adding_handler(Config). + +%%%----------------------------------------------------------------- +%%% Updating handler config +changing_config(SetOrUpdate, OldConfig, NewConfig) -> + logger_h_common:changing_config(SetOrUpdate, OldConfig, NewConfig). -info(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - info, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; -info(Name) -> - {error,{badarg,{info,[Name]}}}. +%%%----------------------------------------------------------------- +%%% Handler being removed +removing_handler(Config) -> + logger_h_common:removing_handler(Config). %%%----------------------------------------------------------------- -%%% --spec reset(Name) -> ok | {error,Reason} when - Name :: atom(), - Reason :: handler_busy | {badarg,term()}. +%%% Log a string or report +-spec log(LogEvent, Config) -> ok when + LogEvent :: logger:log_event(), + Config :: logger:handler_config(). -reset(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - reset, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; -reset(Name) -> - {error,{badarg,{reset,[Name]}}}. +log(LogEvent, Config) -> + logger_h_common:log(LogEvent, Config). +%%%----------------------------------------------------------------- +%%% Remove internal fields from configuration +filter_config(Config) -> + logger_h_common:filter_config(Config). %%%=================================================================== -%%% logger callbacks +%%% logger_h_common callbacks %%%=================================================================== - -%%%----------------------------------------------------------------- -%%% Handler being added -adding_handler(#{id:=Name}=Config) -> - case check_config(adding, Config) of - {ok, Config1} -> - %% create initial handler state by merging defaults with config - HConfig = maps:get(config, Config1, #{}), - HState = maps:merge(get_init_state(), HConfig), - case logger_h_common:overload_levels_ok(HState) of - true -> - start(Name, Config1, HState); - false -> - #{sync_mode_qlen := SMQL, - drop_mode_qlen := DMQL, - flush_qlen := FQL} = HState, - {error,{invalid_levels,{SMQL,DMQL,FQL}}} - end; +init(Name, #{file:=File,type:=Type,max_no_bytes:=MNB,max_no_files:=MNF}) -> + case open_disk_log(Name, File, Type, MNB, MNF) of + ok -> + {ok,#{log_opts => #{file => File, + type => Type, + max_no_bytes => MNB, + max_no_files => MNF}, + prev_log_result => ok, + prev_sync_result => ok, + prev_disk_log_info => undefined}}; Error -> Error end. -%%%----------------------------------------------------------------- -%%% Updating handler config -changing_config(OldConfig = #{id:=Name, config:=OldHConfig}, - NewConfig = #{id:=Name, config:=NewHConfig}) -> - #{type:=Type, file:=File, max_no_files:=MaxFs, - max_no_bytes:=MaxBytes} = OldHConfig, - case NewHConfig of - #{type:=Type, file:=File, max_no_files:=MaxFs, - max_no_bytes:=MaxBytes} -> - changing_config1(OldConfig, NewConfig); - _ -> - {error,{illegal_config_change,OldConfig,NewConfig}} - end; -changing_config(OldConfig, NewConfig) -> - {error,{illegal_config_change,OldConfig,NewConfig}}. - -changing_config1(OldConfig=#{config:=OldHConfig}, NewConfig) -> - case check_config(changing, NewConfig) of - {ok,NewConfig1 = #{config:=NewHConfig}} -> - #{handler_pid:=HPid, - mode_tab:=ModeTab} = OldHConfig, - NewHConfig1 = NewHConfig#{handler_pid=>HPid, - mode_tab=>ModeTab}, - NewConfig2 = NewConfig1#{config=>NewHConfig1}, - try gen_server:call(HPid, {change_config,OldConfig,NewConfig2}, - ?DEFAULT_CALL_TIMEOUT) of - ok -> {ok,NewConfig2}; - HError -> HError - catch - _:{timeout,_} -> {error,handler_busy} - end; - Error -> - Error +check_config(Name,set,undefined,HConfig0) -> + HConfig=merge_default_logopts(Name,maps:merge(get_default_config(),HConfig0)), + check_config(HConfig); +check_config(_Name,SetOrUpdate,OldHConfig,NewHConfig0) -> + WriteOnce = maps:with([type,file,max_no_files,max_no_bytes],OldHConfig), + Default = + case SetOrUpdate of + set -> + %% Do not reset write-once fields to defaults + maps:merge(get_default_config(),WriteOnce); + update -> + OldHConfig + end, + + NewHConfig = maps:merge(Default,NewHConfig0), + + %% Fail if write-once fields are changed + case maps:with([type,file,max_no_files,max_no_bytes],NewHConfig) of + WriteOnce -> + check_config(NewHConfig); + Other -> + {Old,New} = logger_server:diff_maps(WriteOnce,Other), + {error,{illegal_config_change,?MODULE,Old,New}} end. -check_config(adding, #{id:=Name}=Config) -> - %% merge handler specific config data - HConfig = merge_default_logopts(Name, maps:get(config, Config, #{})), +check_config(HConfig) -> case check_h_config(maps:to_list(HConfig)) of ok -> - {ok,Config#{config=>HConfig}}; - Error -> - Error - end; -check_config(changing, Config) -> - HConfig = maps:get(config, Config, #{}), - case check_h_config(maps:to_list(HConfig)) of - ok -> {ok,Config}; - Error -> Error + {ok,HConfig}; + {error,{Key,Value}} -> + {error,{invalid_config,?MODULE,#{Key=>Value}}} end. -merge_default_logopts(Name, HConfig) -> - Type = maps:get(type, HConfig, wrap), - {DefaultNoFiles,DefaultNoBytes} = - case Type of - halt -> {undefined,infinity}; - _wrap -> {10,1048576} - end, - {ok,Dir} = file:get_cwd(), - Defaults = #{file => filename:join(Dir,Name), - max_no_files => DefaultNoFiles, - max_no_bytes => DefaultNoBytes, - type => Type}, - maps:merge(Defaults, HConfig). - check_h_config([{file,File}|Config]) when is_list(File) -> check_h_config(Config); check_h_config([{max_no_files,undefined}|Config]) -> @@ -208,447 +142,59 @@ check_h_config([{max_no_bytes,N}|Config]) when is_integer(N), N>0 -> check_h_config(Config); check_h_config([{type,Type}|Config]) when Type==wrap; Type==halt -> check_h_config(Config); -check_h_config([Other | Config]) -> - case logger_h_common:check_common_config(Other) of - valid -> - check_h_config(Config); - invalid -> - {error,{invalid_config,?MODULE,Other}} - end; +check_h_config([Other | _]) -> + {error,Other}; check_h_config([]) -> ok. -%%%----------------------------------------------------------------- -%%% Handler being removed -removing_handler(#{id:=Name}) -> - stop(Name). - -%%%----------------------------------------------------------------- -%%% Log a string or report --spec log(LogEvent, Config) -> ok when - LogEvent :: logger:log_event(), - Config :: logger:handler_config(). +get_default_config() -> + #{}. -log(LogEvent, Config = #{id := Name, - config := #{handler_pid := HPid, - mode_tab := ModeTab}}) -> - %% if the handler has crashed, we must drop this event - %% and hope the handler restarts so we can try again - true = is_process_alive(HPid), - Bin = logger_h_common:log_to_binary(LogEvent, Config), - logger_h_common:call_cast_or_drop(Name, HPid, ModeTab, Bin). +merge_default_logopts(Name, HConfig) -> + Type = maps:get(type, HConfig, wrap), + {DefaultNoFiles,DefaultNoBytes} = + case Type of + halt -> {undefined,infinity}; + _wrap -> {10,1048576} + end, + {ok,Dir} = file:get_cwd(), + Defaults = #{file => filename:join(Dir,Name), + max_no_files => DefaultNoFiles, + max_no_bytes => DefaultNoBytes, + type => Type}, + maps:merge(Defaults, HConfig). -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== +filesync(Name,_Mode,State) -> + Result = ?disk_log_sync(Name), + maybe_notify_error(Name, filesync, Result, prev_sync_result, State). -init([Name, - Config = #{config := HConfig = #{file:=File, - type:=Type, - max_no_bytes:=MNB, - max_no_files:=MNF}}, - State = #{dl_sync_int := DLSyncInt}]) -> - - RegName = ?name_to_reg_name(?MODULE,Name), - register(RegName, self()), - process_flag(trap_exit, true), - process_flag(message_queue_data, off_heap), - - ?init_test_hooks(), - ?start_observation(Name), - - LogOpts = #{file=>File, type=>Type, max_no_bytes=>MNB, max_no_files=>MNF}, - case open_disk_log(Name, File, Type, MNB, MNF) of - ok -> - try ets:new(Name, [public]) of - ModeTab -> - ?set_mode(ModeTab, async), - T0 = ?timestamp(), - State1 = - ?merge_with_stats(State#{ - id => Name, - mode_tab => ModeTab, - mode => async, - dl_sync => DLSyncInt, - log_opts => LogOpts, - last_qlen => 0, - last_log_ts => T0, - burst_win_ts => T0, - burst_msg_count => 0, - last_op => sync, - prev_log_result => ok, - prev_sync_result => ok, - prev_disk_log_info => undefined}), - Config1 = - Config#{config => HConfig#{handler_pid => self(), - mode_tab => ModeTab}}, - proc_lib:init_ack({ok,self(),Config1}), - gen_server:cast(self(), repeated_disk_log_sync), - case logger_h_common:unset_restart_flag(Name, ?MODULE) of - true -> - %% inform about restart - gen_server:cast(self(), {log_handler_info, - "Handler ~p restarted", - [Name]}); - false -> - %% initial start - ok - end, - gen_server:enter_loop(?MODULE, [], State1) - catch - _:Error -> - unregister(RegName), - logger_h_common:error_notify({open_disk_log,Name,Error}), - proc_lib:init_ack(Error) - end; - Error -> - unregister(RegName), - logger_h_common:error_notify({open_disk_log,Name,Error}), - proc_lib:init_ack(Error) - end. +write(Name, Mode, Bin, State) -> + Result = ?disk_log_write(Name, Mode, Bin), + maybe_notify_error(Name, log, Result, prev_log_result, State). -%% This is the synchronous log event. -handle_call({log, Bin}, _From, State) -> - {Result,State1} = do_log(Bin, call, State), - %% Result == ok | dropped - {reply, Result, State1}; - -handle_call(disk_log_sync, _From, State = #{id := Name}) -> - State1 = #{prev_sync_result := Result} = disk_log_sync(Name, State), - {reply, Result, State1}; - -handle_call({change_config,_OldConfig,NewConfig}, _From, - State = #{filesync_repeat_interval := FSyncInt0}) -> - HConfig = maps:get(config, NewConfig, #{}), - State1 = #{sync_mode_qlen := SMQL, - drop_mode_qlen := DMQL, - flush_qlen := FQL} = maps:merge(State, HConfig), - case logger_h_common:overload_levels_ok(State1) of - true -> - _ = - case maps:get(filesync_repeat_interval, HConfig, undefined) of - undefined -> - ok; - no_repeat -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, - State, - undefined)); - FSyncInt0 -> - ok; - _FSyncInt1 -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, - State, - undefined)), - _ = gen_server:cast(self(), repeated_disk_log_sync) - end, - {reply, ok, State1}; - false -> - {reply, {error,{invalid_levels,{SMQL,DMQL,FQL}}}, State} - end; - -handle_call(info, _From, State) -> - {reply, State, State}; - -handle_call(reset, _From, State) -> - State1 = ?merge_with_stats(State), - {reply, ok, State1#{last_qlen => 0, - last_log_ts => ?timestamp(), - prev_log_result => ok, - prev_sync_result => ok, - prev_disk_log_info => undefined}}; - -handle_call(stop, _From, State) -> - {stop, {shutdown,stopped}, ok, State}. - - -%% This is the asynchronous log event. -handle_cast({log, Bin}, State) -> - {_,State1} = do_log(Bin, cast, State), - {noreply, State1}; - -handle_cast({log_handler_info, Format, Args}, State = #{id:=Name}) -> - log_handler_info(Name, Format, Args, State), - {noreply, State}; - -%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this -%% clause gets called repeatedly by the handler. In order to -%% guarantee that a filesync *always* happens after the last log -%% event, the repeat operation must be active! -handle_cast(repeated_disk_log_sync, - State = #{id := Name, - filesync_repeat_interval := FSyncInt, - last_op := LastOp}) -> - State1 = - if is_integer(FSyncInt) -> - %% only do filesync if something has been - %% written since last time we checked - NewState = if LastOp == sync -> - State; - true -> - disk_log_sync(Name, State) - end, - {ok,TRef} = - timer:apply_after(FSyncInt, gen_server,cast, - [self(),repeated_disk_log_sync]), - NewState#{rep_sync_tref => TRef, last_op => sync}; - true -> - State - end, - {noreply,State1}. +reset_state(_Name, State) -> + State#{prev_log_result => ok, + prev_sync_result => ok, + prev_disk_log_info => undefined}. %% The disk log owner must handle status messages from disk_log. -handle_info({disk_log, _Node, _Log, {wrap,_NoLostItems}}, State) -> - {noreply, State}; -handle_info({disk_log, _Node, Log, Info = {truncated,_NoLostItems}}, - State = #{id := Name, prev_disk_log_info := PrevInfo}) -> - error_notify_new(Info, PrevInfo, {disk_log,Name,Log,Info}), - {noreply, State#{prev_disk_log_info => Info}}; -handle_info({disk_log, _Node, Log, Info = {blocked_log,_Items}}, - State = #{id := Name, prev_disk_log_info := PrevInfo}) -> - error_notify_new(Info, PrevInfo, {disk_log,Name,Log,Info}), - {noreply, State#{prev_disk_log_info => Info}}; -handle_info({disk_log, _Node, Log, full}, - State = #{id := Name, prev_disk_log_info := PrevInfo}) -> - error_notify_new(full, PrevInfo, {disk_log,Name,Log,full}), - {noreply, State#{prev_disk_log_info => full}}; -handle_info({disk_log, _Node, Log, Info = {error_status,_Status}}, - State = #{id := Name, prev_disk_log_info := PrevInfo}) -> - error_notify_new(Info, PrevInfo, {disk_log,Name,Log,Info}), - {noreply, State#{prev_disk_log_info => Info}}; - -handle_info({'EXIT',_Pid,_Why}, State = #{id := _Name}) -> - {noreply, State}; - -handle_info(_, State) -> - {noreply, State}. - -terminate(Reason, State = #{id := Name}) -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, State, - undefined)), +handle_info(Name, {disk_log, _Node, Log, Info={truncated,_NoLostItems}}, State) -> + maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); +handle_info(Name, {disk_log, _Node, Log, Info = {blocked_log,_Items}}, State) -> + maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); +handle_info(Name, {disk_log, _Node, Log, Info = full}, State) -> + maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); +handle_info(Name, {disk_log, _Node, Log, Info = {error_status,_Status}}, State) -> + maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); +handle_info(_, _, State) -> + State. + +terminate(Name, _Reason, _State) -> _ = close_disk_log(Name, normal), - ok = logger_h_common:stop_or_restart(Name, Reason, State), - unregister(?name_to_reg_name(?MODULE, Name)), ok. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - %%%----------------------------------------------------------------- %%% Internal functions - -%%%----------------------------------------------------------------- -%%% -get_init_state() -> - #{sync_mode_qlen => ?SYNC_MODE_QLEN, - drop_mode_qlen => ?DROP_MODE_QLEN, - flush_qlen => ?FLUSH_QLEN, - burst_limit_enable => ?BURST_LIMIT_ENABLE, - burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT, - burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME, - overload_kill_enable => ?OVERLOAD_KILL_ENABLE, - overload_kill_qlen => ?OVERLOAD_KILL_QLEN, - overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE, - overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER, - dl_sync_int => ?CONTROLLER_SYNC_INTERVAL, - filesync_ok_qlen => ?FILESYNC_OK_QLEN, - filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}. - -%%%----------------------------------------------------------------- -%%% Add a disk_log handler to the logger. -%%% This starts a dedicated handler process which should always -%%% exist if the handler is registered with logger (and should not -%%% exist if the handler is not registered). -%%% -%%% Config is the logger:handler_config() map. Handler specific parameters -%%% should be provided with a sub map associated with a key named -%%% 'config', e.g: -%%% -%%% Config = #{config => #{sync_mode_qlen => 50} -%%% -%%% The 'config' sub map will also contain parameters for configuring -%%% the disk_log: -%%% -%%% Config = #{config => #{file => file:filename(), -%%% max_no_bytes => integer(), -%%% max_no_files => integer(), -%%% type => wrap | halt}}. -%%% -%%% If type == halt, then max_no_files is ignored. -%%% -%%% The disk_log handler process is linked to logger_sup, which is -%%% part of the kernel application's supervision tree. -start(Name, Config, HandlerState) -> - LoggerDLH = - #{id => Name, - start => {?MODULE, start_link, [Name,Config,HandlerState]}, - restart => temporary, - shutdown => 2000, - type => worker, - modules => [?MODULE]}, - case supervisor:start_child(logger_sup, LoggerDLH) of - {ok,Pid,Config1} -> - ok = logger_handler_watcher:register_handler(Name,Pid), - {ok,Config1}; - Error -> - Error - end. - -%%%----------------------------------------------------------------- -%%% Stop and remove the handler. -stop(Name) -> - case whereis(?name_to_reg_name(?MODULE,Name)) of - undefined -> - ok; - Pid -> - %% We don't want to do supervisor:terminate_child here - %% since we need to distinguish this explicit stop from a - %% system termination in order to avoid circular attempts - %% at removing the handler (implying deadlocks and - %% timeouts). - %% And we don't need to do supervisor:delete_child, since - %% the restart type is temporary, which means that the - %% child specification is automatically removed from the - %% supervisor when the process dies. - _ = gen_server:call(Pid, stop), - ok - end. - -%%%----------------------------------------------------------------- -%%% Logging and overload control. --define(update_dl_sync(C, Interval), - if C == 0 -> Interval; - true -> C-1 end). - -%% check for overload between every event (and set Mode to async, -%% sync or drop accordingly), but never flush the whole mailbox -%% before LogWindowSize events have been handled -do_log(Bin, CallOrCast, State = #{id:=Name, mode := Mode0}) -> - T1 = ?timestamp(), - - %% check if the handler is getting overloaded, or if it's - %% recovering from overload (the check must be done for each - %% event to react quickly to large bursts of events and - %% to ensure that the handler can never end up in drop mode - %% with an empty mailbox, which would stop operation) - {Mode1,QLen,Mem,State1} = logger_h_common:check_load(State), - - if (Mode1 == drop) andalso (Mode0 =/= drop) -> - log_handler_info(Name, "Handler ~p switched to drop mode", - [Name], State); - (Mode0 == drop) andalso ((Mode1 == async) orelse (Mode1 == sync)) -> - log_handler_info(Name, "Handler ~p switched to ~w mode", - [Name,Mode1], State); - true -> - ok - end, - - %% kill the handler if it can't keep up with the load - logger_h_common:kill_if_choked(Name, QLen, Mem, ?MODULE, State), - - if Mode1 == flush -> - flush(Name, QLen, T1, State1); - true -> - write(Name, Mode1, T1, Bin, CallOrCast, State1) - end. - -%% this function is called by do_log/3 after an overload check -%% has been performed, where QLen > FlushQLen -flush(Name, _QLen0, T1, State=#{last_log_ts := _T0, mode_tab := ModeTab}) -> - %% flush messages in the mailbox (a limited number in - %% order to not cause long delays) - NewFlushed = logger_h_common:flush_log_events(?FLUSH_MAX_N), - - %% write info in log about flushed messages - log_handler_info(Name, "Handler ~p flushed ~w log events", - [Name,NewFlushed], State), - - %% because of the receive loop when flushing messages, the - %% handler will be scheduled out often and the mailbox could - %% grow very large, so we'd better check the queue again here - {_,_QLen1} = process_info(self(), message_queue_len), - ?observe(Name,{max_qlen,_QLen1}), - - %% Add 1 for the current log event - ?observe(Name,{flushed,NewFlushed+1}), - - State1 = ?update_max_time(?diff_time(T1,_T0),State), - {dropped,?update_other(flushed,FLUSHED,NewFlushed, - State1#{mode => ?set_mode(ModeTab,async), - last_qlen => 0, - last_log_ts => T1})}. - -%% this function is called to write to disk_log -write(Name, Mode, T1, Bin, _CallOrCast, - State = #{mode_tab := ModeTab, - dl_sync := DLSync, - dl_sync_int := DLSyncInt, - last_qlen := LastQLen, - last_log_ts := T0}) -> - %% check if we need to limit the number of writes - %% during a burst of log events - {DoWrite,BurstWinT,BurstMsgCount} = logger_h_common:limit_burst(State), - - %% only send a synhrounous event to the disk_log process - %% every DLSyncInt time, to give the handler time between - %% writes so it can keep up with incoming messages - {Status,LastQLen1,State1} = - if DoWrite, DLSync == 0 -> - ?observe(Name,{_CallOrCast,1}), - NewState = disk_log_write(Name, Bin, State), - {ok, element(2,process_info(self(),message_queue_len)), - NewState}; - DoWrite -> - ?observe(Name,{_CallOrCast,1}), - NewState = disk_log_write(Name, Bin, State), - {ok, LastQLen, NewState}; - not DoWrite -> - ?observe(Name,{flushed,1}), - {dropped, LastQLen, State} - end, - - %% Check if the time since the previous log event is long enough - - %% and the queue length small enough - to assume the mailbox has - %% been emptied, and if so, do filesync operation and reset mode to - %% async. Note that this is the best we can do to detect an idle - %% handler without setting a timer after each log call/cast. If the - %% time between two consecutive log events is fast and no new - %% event comes in after the last one, idle state won't be detected! - Time = ?diff_time(T1,T0), - {Mode1,BurstMsgCount1,State2} = - if (LastQLen1 < ?FILESYNC_OK_QLEN) andalso - (Time > ?IDLE_DETECT_TIME_USEC) -> - {?change_mode(ModeTab,Mode,async), 0, disk_log_sync(Name,State1)}; - true -> - {Mode, BurstMsgCount,State1} - end, - - State3 = - ?update_calls_or_casts(_CallOrCast,1,State2), - State4 = - ?update_max_time(Time, - State3#{mode => Mode1, - last_qlen := LastQLen1, - last_log_ts => T1, - burst_win_ts => BurstWinT, - burst_msg_count => BurstMsgCount1, - dl_sync => ?update_dl_sync(DLSync,DLSyncInt)}), - {Status,State4}. - - -log_handler_info(Name, Format, Args, State) -> - Config = - case logger:get_handler_config(Name) of - {ok,Conf} -> Conf; - _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}} - end, - Meta = #{time=>erlang:system_time(microsecond)}, - Bin = logger_h_common:log_to_binary(#{level => notice, - msg => {Format,Args}, - meta => Meta}, Config), - _ = disk_log_write(Name, Bin, State), - ok. - - open_disk_log(Name, File, Type, MaxNoBytes, MaxNoFiles) -> case filelib:ensure_dir(File) of ok -> @@ -681,43 +227,26 @@ close_disk_log(Name, _) -> _ = disk_log:lclose(Name), ok. -disk_log_write(Name, Bin, State) -> - case ?disk_log_blog(Name, Bin) of - ok -> - State#{prev_log_result => ok, last_op => write}; - LogError -> - _ = case maps:get(prev_log_result, State) of - LogError -> - %% don't report same error twice - ok; - _ -> - LogOpts = maps:get(log_opts, State), - logger_h_common:error_notify({Name,log, - LogOpts, - LogError}) - end, - State#{prev_log_result => LogError} - end. - -disk_log_sync(Name, State) -> - case ?disk_log_sync(Name) of - ok -> - State#{prev_sync_result => ok, last_op => sync}; - SyncError -> - _ = case maps:get(prev_sync_result, State) of - SyncError -> - %% don't report same error twice - ok; - _ -> - LogOpts = maps:get(log_opts, State), - logger_h_common:error_notify({Name,filesync, - LogOpts, - SyncError}) - end, - State#{prev_sync_result => SyncError} - end. +disk_log_write(Name, sync, Bin) -> + disk_log:blog(Name, Bin); +disk_log_write(Name, async, Bin) -> + disk_log:balog(Name, Bin). + +%%%----------------------------------------------------------------- +%%% Print error messages, but don't repeat the same message +maybe_notify_error(Name, Op, Result, Key, #{log_opts:=LogOpts}=State) -> + {Result,error_notify_new({Name, Op, LogOpts, Result}, Result, Key, State)}. -error_notify_new(Info,Info, _Term) -> +maybe_notify_status(Name, Log, Info, Key, State) -> + error_notify_new({disk_log, Name, Log, Info}, Info, Key, State). + +error_notify_new(Term, What, Key, State) -> + error_notify_new(What, maps:get(Key,State), Term), + State#{Key => What}. + +error_notify_new(ok,_Prev,_Term) -> + ok; +error_notify_new(Same,Same,_Term) -> ok; -error_notify_new(_Info0,_Info1, Term) -> +error_notify_new(_New,_Prev,Term) -> logger_h_common:error_notify(Term). diff --git a/lib/kernel/src/logger_h_common.erl b/lib/kernel/src/logger_h_common.erl index 38ac7d8ffc..e69f6de38d 100644 --- a/lib/kernel/src/logger_h_common.erl +++ b/lib/kernel/src/logger_h_common.erl @@ -18,26 +18,345 @@ %% %CopyrightEnd% %% -module(logger_h_common). +-behaviour(gen_server). -include("logger_h_common.hrl"). -include("logger_internal.hrl"). --export([log_to_binary/2, - check_common_config/1, - call_cast_or_drop/4, - check_load/1, - limit_burst/1, - kill_if_choked/5, - flush_log_events/0, - flush_log_events/1, - handler_exit/2, - set_restart_flag/2, - unset_restart_flag/2, - cancel_timer/1, - stop_or_restart/3, - overload_levels_ok/1, - error_notify/1, - info_notify/1]). +%% API +-export([filesync/2]). + +%% logger_olp callbacks +-export([init/1, handle_load/2, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3, notify/2, reset_state/1]). + +%% logger callbacks +-export([log/2, adding_handler/1, removing_handler/1, changing_config/3, + filter_config/1]). + +%% Library functions for handlers +-export([error_notify/1]). + +-define(OLP_KEYS,[sync_mode_qlen, + drop_mode_qlen, + flush_qlen, + burst_limit_enable, + burst_limit_max_count, + burst_limit_window_time, + overload_kill_enable, + overload_kill_qlen, + overload_kill_mem_size, + overload_kill_restart_after]). + +-define(COMMON_KEYS,[filesync_repeat_interval]). + +-define(READ_ONLY_KEYS,[olp]). + +%%%----------------------------------------------------------------- +%%% API + +%% This function is called by the logger_sup supervisor +filesync(Module, Name) -> + call(Module, Name, filesync). + +%%%----------------------------------------------------------------- +%%% Handler being added +adding_handler(#{id:=Name,module:=Module}=Config) -> + HConfig0 = maps:get(config, Config, #{}), + HandlerConfig0 = maps:without(?OLP_KEYS++?COMMON_KEYS,HConfig0), + case Module:check_config(Name,set,undefined,HandlerConfig0) of + {ok,HandlerConfig} -> + ModifiedCommon = maps:with(?COMMON_KEYS,HandlerConfig), + CommonConfig0 = maps:with(?COMMON_KEYS,HConfig0), + CommonConfig = maps:merge( + maps:merge(get_default_config(), CommonConfig0), + ModifiedCommon), + case check_config(CommonConfig) of + ok -> + HConfig = maps:merge(CommonConfig,HandlerConfig), + OlpOpts = maps:with(?OLP_KEYS,HConfig0), + start(OlpOpts, Config#{config => HConfig}); + {error,Faulty} -> + {error,{invalid_config,Module,Faulty}} + end; + Error -> + Error + end. + +%%%----------------------------------------------------------------- +%%% Handler being removed +removing_handler(#{id:=Name, module:=Module, config:=#{olp:=Olp}}) -> + case whereis(?name_to_reg_name(Module,Name)) of + undefined -> + ok; + _Pid -> + %% We don't want to do supervisor:terminate_child here + %% since we need to distinguish this explicit stop from a + %% system termination in order to avoid circular attempts + %% at removing the handler (implying deadlocks and + %% timeouts). + %% And we don't need to do supervisor:delete_child, since + %% the restart type is temporary, which means that the + %% child specification is automatically removed from the + %% supervisor when the process dies. + _ = logger_olp:stop(Olp), + ok + end. + +%%%----------------------------------------------------------------- +%%% Updating handler config +changing_config(SetOrUpdate, + #{id:=Name,config:=OldHConfig,module:=Module}, + NewConfig0) -> + NewHConfig0 = maps:get(config, NewConfig0, #{}), + NoHandlerKeys = ?OLP_KEYS++?COMMON_KEYS++?READ_ONLY_KEYS, + OldHandlerConfig = maps:without(NoHandlerKeys,OldHConfig), + NewHandlerConfig0 = maps:without(NoHandlerKeys,NewHConfig0), + case Module:check_config(Name, SetOrUpdate, + OldHandlerConfig,NewHandlerConfig0) of + {ok, NewHandlerConfig} -> + ModifiedCommon = maps:with(?COMMON_KEYS,NewHandlerConfig), + NewCommonConfig0 = maps:with(?COMMON_KEYS,NewHConfig0), + OldCommonConfig = maps:with(?COMMON_KEYS,OldHConfig), + CommonDefault = + case SetOrUpdate of + set -> + get_default_config(); + update -> + OldCommonConfig + end, + NewCommonConfig = maps:merge( + maps:merge(CommonDefault,NewCommonConfig0), + ModifiedCommon), + case check_config(NewCommonConfig) of + ok -> + OlpDefault = + case SetOrUpdate of + set -> + logger_olp:get_default_opts(); + update -> + maps:with(?OLP_KEYS,OldHConfig) + end, + Olp = maps:get(olp,OldHConfig), + NewOlpOpts = maps:merge(OlpDefault, + maps:with(?OLP_KEYS,NewHConfig0)), + case logger_olp:set_opts(Olp,NewOlpOpts) of + ok -> + maybe_set_repeated_filesync(Olp,OldCommonConfig, + NewCommonConfig), + ReadOnly = maps:with(?READ_ONLY_KEYS,OldHConfig), + NewHConfig = + maps:merge( + maps:merge( + maps:merge(NewCommonConfig,NewHandlerConfig), + ReadOnly), + NewOlpOpts), + NewConfig = NewConfig0#{config=>NewHConfig}, + {ok,NewConfig}; + Error -> + Error + end; + {error,Faulty} -> + {error,{invalid_config,Module,Faulty}} + end; + Error -> + Error + end. + +%%%----------------------------------------------------------------- +%%% Log a string or report +-spec log(LogEvent, Config) -> ok when + LogEvent :: logger:log_event(), + Config :: logger:handler_config(). + +log(LogEvent, Config = #{config := #{olp:=Olp}}) -> + %% if the handler has crashed, we must drop this event + %% and hope the handler restarts so we can try again + true = is_process_alive(logger_olp:get_pid(Olp)), + Bin = log_to_binary(LogEvent, Config), + logger_olp:load(Olp,Bin). + +%%%----------------------------------------------------------------- +%%% Remove internal fields from configuration +filter_config(#{config:=HConfig}=Config) -> + Config#{config=>maps:without(?READ_ONLY_KEYS,HConfig)}. + +%%%----------------------------------------------------------------- +%%% Start the handler process +%%% +%%% The process must always exist if the handler is registered with +%%% logger (and must not exist if the handler is not registered). +%%% +%%% The handler process is linked to logger_sup, which is part of the +%%% kernel application's supervision tree. +start(OlpOpts0, #{id := Name, module:=Module, config:=HConfig} = Config0) -> + RegName = ?name_to_reg_name(Module,Name), + ChildSpec = + #{id => Name, + start => {logger_olp, start_link, [RegName,?MODULE, + Config0, OlpOpts0]}, + restart => temporary, + shutdown => 2000, + type => worker, + modules => [?MODULE]}, + case supervisor:start_child(logger_sup, ChildSpec) of + {ok,Pid,Olp} -> + ok = logger_handler_watcher:register_handler(Name,Pid), + OlpOpts = logger_olp:get_opts(Olp), + {ok,Config0#{config=>(maps:merge(HConfig,OlpOpts))#{olp=>Olp}}}; + {error,{Reason,Ch}} when is_tuple(Ch), element(1,Ch)==child -> + {error,Reason}; + Error -> + Error + end. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init(#{id := Name, module := Module, config := HConfig}) -> + process_flag(trap_exit, true), + + ?init_test_hooks(), + + case Module:init(Name, HConfig) of + {ok,HState} -> + %% Storing common config in state to avoid copying + %% (sending) the config data for each log message + CommonConfig = maps:with(?COMMON_KEYS,HConfig), + State = CommonConfig#{id => Name, + module => Module, + ctrl_sync_count => + ?CONTROLLER_SYNC_INTERVAL, + last_op => sync, + handler_state => HState}, + State1 = set_repeated_filesync(State), + {ok,State1}; + Error -> + Error + end. + +%% This is the log event. +handle_load(Bin, #{id:=Name, + module:=Module, + handler_state:=HandlerState, + ctrl_sync_count := CtrlSync}=State) -> + if CtrlSync==0 -> + {_,HS1} = Module:write(Name, sync, Bin, HandlerState), + State#{handler_state => HS1, + ctrl_sync_count => ?CONTROLLER_SYNC_INTERVAL, + last_op=>write}; + true -> + {_,HS1} = Module:write(Name, async, Bin, HandlerState), + State#{handler_state => HS1, + ctrl_sync_count => CtrlSync-1, + last_op=>write} + end. + +handle_call(filesync, _From, State = #{id := Name, + module := Module, + handler_state := HandlerState}) -> + {Result,HandlerState1} = Module:filesync(Name,sync,HandlerState), + {reply, Result, State#{handler_state=>HandlerState1, last_op=>sync}}. + +%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this +%% clause gets called repeatedly by the handler. In order to +%% guarantee that a filesync *always* happens after the last log +%% event, the repeat operation must be active! +handle_cast(repeated_filesync,State = #{filesync_repeat_interval := no_repeat}) -> + %% This clause handles a race condition which may occur when + %% config changes filesync_repeat_interval from an integer value + %% to no_repeat. + {noreply,State}; +handle_cast(repeated_filesync, + State = #{id := Name, + module := Module, + handler_state := HandlerState, + last_op := LastOp}) -> + State1 = + if LastOp == sync -> + State; + true -> + {_,HS} = Module:filesync(Name, async, HandlerState), + State#{handler_state => HS, last_op => sync} + end, + {noreply,set_repeated_filesync(State1)}; + +handle_cast({set_repeated_filesync,FSyncInt},State) -> + State1 = State#{filesync_repeat_interval=>FSyncInt}, + State2 = set_repeated_filesync(cancel_repeated_filesync(State1)), + {noreply, State2}. + +handle_info(Info, #{id := Name, module := Module, + handler_state := HandlerState} = State) -> + {noreply,State#{handler_state => Module:handle_info(Name,Info,HandlerState)}}. + +terminate(overloaded=Reason, #{id:=Name}=State) -> + _ = log_handler_info(Name,"Handler ~p overloaded and stopping",[Name],State), + do_terminate(Reason,State), + ConfigResult = logger:get_handler_config(Name), + case ConfigResult of + {ok,#{module:=Module}=HConfig0} -> + spawn(fun() -> logger:remove_handler(Name) end), + HConfig = try Module:filter_config(HConfig0) + catch _:_ -> HConfig0 + end, + {ok,fun() -> logger:add_handler(Name,Module,HConfig) end}; + Error -> + error_notify({Name,restart_impossible,Error}), + Error + end; +terminate(Reason, State) -> + do_terminate(Reason, State). + +do_terminate(Reason, State = #{id := Name, + module := Module, + handler_state := HandlerState}) -> + _ = cancel_repeated_filesync(State), + _ = Module:terminate(Name, Reason, HandlerState), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +reset_state(#{id:=Name, module:=Module, handler_state:=HandlerState} = State) -> + State#{handler_state=>Module:reset_state(Name, HandlerState)}. + +%%%----------------------------------------------------------------- +%%% Internal functions +call(Module, Name, Op) when is_atom(Name) -> + case logger_olp:call(?name_to_reg_name(Module,Name), Op) of + {error,busy} -> {error,handler_busy}; + Other -> Other + end; +call(_, Name, Op) -> + {error,{badarg,{Op,[Name]}}}. + +notify({mode_change,Mode0,Mode1},#{id:=Name}=State) -> + log_handler_info(Name,"Handler ~p switched from ~p to ~p mode", + [Name,Mode0,Mode1], State); +notify({flushed,Flushed},#{id:=Name}=State) -> + log_handler_info(Name, "Handler ~p flushed ~w log events", + [Name,Flushed], State); +notify(restart,#{id:=Name}=State) -> + log_handler_info(Name, "Handler ~p restarted", [Name], State); +notify(idle,#{id:=Name,module:=Module,handler_state:=HandlerState}=State) -> + {_,HS} = Module:filesync(Name,async,HandlerState), + State#{handler_state=>HS, last_op=>sync}. + +log_handler_info(Name, Format, Args, #{module:=Module, + handler_state:=HandlerState}=State) -> + Config = + case logger:get_handler_config(Name) of + {ok,Conf} -> Conf; + _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}} + end, + Meta = #{time=>erlang:system_time(microsecond)}, + Bin = log_to_binary(#{level => notice, + msg => {Format,Args}, + meta => Meta}, Config), + {_,HS} = Module:write(Name, async, Bin, HandlerState), + State#{handler_state=>HS, last_op=>write}. %%%----------------------------------------------------------------- %%% Convert log data on any form to binary @@ -94,243 +413,44 @@ string_to_binary(String) -> throw(Error) end. - %%%----------------------------------------------------------------- %%% Check that the configuration term is valid -check_common_config({mode_tab,_Tid}) -> - valid; -check_common_config({handler_pid,Pid}) when is_pid(Pid) -> - valid; - -check_common_config({sync_mode_qlen,N}) when is_integer(N) -> - valid; -check_common_config({drop_mode_qlen,N}) when is_integer(N) -> - valid; -check_common_config({flush_qlen,N}) when is_integer(N) -> - valid; - -check_common_config({burst_limit_enable,Bool}) when Bool == true; - Bool == false -> - valid; -check_common_config({burst_limit_max_count,N}) when is_integer(N) -> - valid; -check_common_config({burst_limit_window_time,N}) when is_integer(N) -> - valid; - -check_common_config({overload_kill_enable,Bool}) when Bool == true; - Bool == false -> - valid; -check_common_config({overload_kill_qlen,N}) when is_integer(N) -> - valid; -check_common_config({overload_kill_mem_size,N}) when is_integer(N) -> - valid; -check_common_config({overload_kill_restart_after,NorA}) when is_integer(NorA); - NorA == infinity -> - valid; - -check_common_config({filesync_repeat_interval,NorA}) when is_integer(NorA); - NorA == no_repeat -> - valid; -check_common_config(_) -> - invalid. - +check_config(Config) when is_map(Config) -> + check_common_config(maps:to_list(Config)). -%%%----------------------------------------------------------------- -%%% Overload Protection -call_cast_or_drop(_Name, HandlerPid, ModeTab, Bin) -> - %% If the handler process is getting overloaded, the log event - %% will be synchronous instead of asynchronous (slows down the - %% logging tempo of a process doing lots of logging. If the - %% handler is choked, drop mode is set and no event will be sent. - try ?get_mode(ModeTab) of - async -> - gen_server:cast(HandlerPid, {log,Bin}); - sync -> - try gen_server:call(HandlerPid, {log,Bin}, ?DEFAULT_CALL_TIMEOUT) of - %% if return value from call == dropped, the - %% message has been flushed by handler and should - %% therefore not be counted as dropped in stats - ok -> ok; - dropped -> ok - catch - _:{timeout,_} -> - ?observe(_Name,{dropped,1}) - end; - drop -> - ?observe(_Name,{dropped,1}) - catch - %% if the ETS table doesn't exist (maybe because of a - %% handler restart), we can only drop the event - _:_ -> ?observe(_Name,{dropped,1}) - end, +check_common_config([{filesync_repeat_interval,NorA}|Config]) + when is_integer(NorA); NorA == no_repeat -> + check_common_config(Config); +check_common_config([{Key,Value}|_]) -> + {error,#{Key=>Value}}; +check_common_config([]) -> ok. -handler_exit(_Name, Reason) -> - exit(Reason). +get_default_config() -> + #{filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}. -set_restart_flag(Name, Module) -> - Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])), - spawn(fun() -> - register(Flag, self()), - timer:sleep(infinity) - end), - ok. +set_repeated_filesync(#{filesync_repeat_interval:=FSyncInt} = State) + when is_integer(FSyncInt) -> + {ok,TRef} = timer:apply_after(FSyncInt, gen_server, cast, + [self(),repeated_filesync]), + State#{rep_sync_tref=>TRef}; +set_repeated_filesync(State) -> + State. -unset_restart_flag(Name, Module) -> - Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])), - case whereis(Flag) of - undefined -> - false; - Pid -> - exit(Pid, kill), - true +cancel_repeated_filesync(State) -> + case maps:take(rep_sync_tref,State) of + {TRef,State1} -> + _ = timer:cancel(TRef), + State1; + error -> + State end. - -check_load(State = #{id:=_Name, mode_tab := ModeTab, mode := Mode, - sync_mode_qlen := SyncModeQLen, - drop_mode_qlen := DropModeQLen, - flush_qlen := FlushQLen}) -> - {_,Mem} = process_info(self(), memory), - ?observe(_Name,{max_mem,Mem}), - {_,QLen} = process_info(self(), message_queue_len), - ?observe(_Name,{max_qlen,QLen}), - %% When the handler process gets scheduled in, it's impossible - %% to predict the QLen. We could jump "up" arbitrarily from say - %% async to sync, async to drop, sync to flush, etc. However, when - %% the handler process manages the log events (without flushing), - %% one after the other, we will move "down" from drop to sync and - %% from sync to async. This way we don't risk getting stuck in - %% drop or sync mode with an empty mailbox. - {Mode1,_NewDrops,_NewFlushes} = - if - QLen >= FlushQLen -> - {flush, 0,1}; - QLen >= DropModeQLen -> - %% Note that drop mode will force log events to - %% be dropped on the client side (never sent get to - %% the handler). - IncDrops = if Mode == drop -> 0; true -> 1 end, - {?change_mode(ModeTab, Mode, drop), IncDrops,0}; - QLen >= SyncModeQLen -> - {?change_mode(ModeTab, Mode, sync), 0,0}; - true -> - {?change_mode(ModeTab, Mode, async), 0,0} - end, - State1 = ?update_other(drops,DROPS,_NewDrops,State), - {Mode1, QLen, Mem, - ?update_other(flushes,FLUSHES,_NewFlushes, - State1#{last_qlen => QLen})}. - -limit_burst(#{burst_limit_enable := false}) -> - {true,0,0}; -limit_burst(#{burst_win_ts := BurstWinT0, - burst_msg_count := BurstMsgCount, - burst_limit_window_time := BurstLimitWinTime, - burst_limit_max_count := BurstLimitMaxCnt}) -> - if (BurstMsgCount >= BurstLimitMaxCnt) -> - %% the limit for allowed messages has been reached - BurstWinT1 = ?timestamp(), - case ?diff_time(BurstWinT1,BurstWinT0) of - BurstCheckTime when BurstCheckTime < (BurstLimitWinTime*1000) -> - %% we're still within the burst time frame - {false,BurstWinT0,BurstMsgCount}; - _BurstCheckTime -> - %% burst time frame passed, reset counters - {true,BurstWinT1,0} - end; - true -> - %% the limit for allowed messages not yet reached - {true,BurstWinT0,BurstMsgCount+1} - end. - -kill_if_choked(Name, QLen, Mem, HandlerMod, - State = #{overload_kill_enable := KillIfOL, - overload_kill_qlen := OLKillQLen, - overload_kill_mem_size := OLKillMem}) -> - if KillIfOL andalso - ((QLen > OLKillQLen) orelse (Mem > OLKillMem)) -> - HandlerMod:log_handler_info(Name, - "Handler ~p overloaded and stopping", - [Name], State), - set_restart_flag(Name, HandlerMod), - handler_exit(Name, {shutdown,{overloaded,Name,QLen,Mem}}); - true -> - ok - end. - -flush_log_events() -> - flush_log_events(-1). - -flush_log_events(Limit) -> - process_flag(priority, high), - Flushed = flush_log_events(0, Limit), - process_flag(priority, normal), - Flushed. - -flush_log_events(Limit, Limit) -> - Limit; -flush_log_events(N, Limit) -> - %% flush log events but leave other events, such as - %% filesync, info and change_config, so that these - %% have a chance to be processed even under heavy load - receive - {'$gen_cast',{log,_}} -> - flush_log_events(N+1, Limit); - {'$gen_call',{Pid,MRef},{log,_}} -> - Pid ! {MRef, dropped}, - flush_log_events(N+1, Limit) - after - 0 -> N - end. - -cancel_timer(TRef) when is_atom(TRef) -> ok; -cancel_timer(TRef) -> timer:cancel(TRef). - - -stop_or_restart(Name, {shutdown,Reason={overloaded,_Name,_QLen,_Mem}}, - #{overload_kill_restart_after := RestartAfter}) -> - %% If we're terminating because of an overload situation (see - %% logger_h_common:kill_if_choked/4), we need to remove the handler - %% and set a restart timer. A separate process must perform this - %% in order to avoid deadlock. - HandlerPid = self(), - ConfigResult = logger:get_handler_config(Name), - RemoveAndRestart = - fun() -> - MRef = erlang:monitor(process, HandlerPid), - receive - {'DOWN',MRef,_,_,_} -> - ok - after 30000 -> - error_notify(Reason), - exit(HandlerPid, kill) - end, - case ConfigResult of - {ok,#{module:=HMod}=HConfig} when is_integer(RestartAfter) -> - _ = logger:remove_handler(Name), - _ = timer:apply_after(RestartAfter, logger, add_handler, - [Name,HMod,HConfig]); - {ok,_} -> - _ = logger:remove_handler(Name); - {error,CfgReason} when is_integer(RestartAfter) -> - error_notify({Name,restart_impossible,CfgReason}); - {error,_} -> - ok - end - end, - spawn(RemoveAndRestart), - ok; -stop_or_restart(_Name, _Reason, _State) -> - ok. - -overload_levels_ok(HandlerConfig) -> - SMQL = maps:get(sync_mode_qlen, HandlerConfig, ?SYNC_MODE_QLEN), - DMQL = maps:get(drop_mode_qlen, HandlerConfig, ?DROP_MODE_QLEN), - FQL = maps:get(flush_qlen, HandlerConfig, ?FLUSH_QLEN), - (DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL). - error_notify(Term) -> ?internal_log(error, Term). -info_notify(Term) -> - ?internal_log(info, Term). +maybe_set_repeated_filesync(_Olp, + #{filesync_repeat_interval:=FSyncInt}, + #{filesync_repeat_interval:=FSyncInt}) -> + ok; +maybe_set_repeated_filesync(Olp,_,#{filesync_repeat_interval:=FSyncInt}) -> + logger_olp:cast(Olp,{set_repeated_filesync,FSyncInt}). diff --git a/lib/kernel/src/logger_h_common.hrl b/lib/kernel/src/logger_h_common.hrl index e0a7b6e3ca..004a61d9d9 100644 --- a/lib/kernel/src/logger_h_common.hrl +++ b/lib/kernel/src/logger_h_common.hrl @@ -1,50 +1,22 @@ - -%%%----------------------------------------------------------------- -%%% Overload protection configuration - -%%! *** NOTE *** -%%! It's important that: -%%! SYNC_MODE_QLEN =< DROP_MODE_QLEN =< FLUSH_QLEN -%%! and that DROP_MODE_QLEN >= 2. -%%! Otherwise the handler could end up in drop mode with no new -%%! log requests to process. This would cause all future requests -%%! to be dropped (no switch to async mode would ever take place). - -%% This specifies the message_queue_len value where the log -%% requests switch from asynchronous casts to synchronous calls. --define(SYNC_MODE_QLEN, 10). -%% Above this message_queue_len, log requests will be dropped, -%% i.e. no log requests get sent to the handler process. --define(DROP_MODE_QLEN, 200). -%% Above this message_queue_len, the handler process will flush -%% its mailbox and only leave this number of messages in it. --define(FLUSH_QLEN, 1000). - -%% Never flush more than this number of messages in one go, -%% or the handler will be unresponsive for seconds (keep this -%% number as large as possible or the mailbox could grow large). --define(FLUSH_MAX_N, 5000). - -%% BURST_LIMIT_MAX_COUNT is the max number of log requests allowed -%% to be written within a BURST_LIMIT_WINDOW_TIME time frame. --define(BURST_LIMIT_ENABLE, true). --define(BURST_LIMIT_MAX_COUNT, 500). --define(BURST_LIMIT_WINDOW_TIME, 1000). - -%% This enables/disables the feature to automatically get the -%% handler terminated if it gets too loaded (and can't keep up). --define(OVERLOAD_KILL_ENABLE, false). -%% If the message_queue_len goes above this size even after -%% flushing has been performed, the handler is terminated. --define(OVERLOAD_KILL_QLEN, 20000). -%% If the memory usage exceeds this level --define(OVERLOAD_KILL_MEM_SIZE, 3000000). - -%% This is the default time that the handler will wait before -%% restarting and accepting new requests. The value 'infinity' -%% disables restarts. --define(OVERLOAD_KILL_RESTART_AFTER, 5000). -%%-define(OVERLOAD_KILL_RESTART_AFTER, infinity). +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2015. 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% +%% %% The handler sends asynchronous write requests to the process %% controlling the i/o device, but every once in this interval @@ -65,12 +37,6 @@ -define(FILESYNC_REPEAT_INTERVAL, 5000). %%-define(FILESYNC_REPEAT_INTERVAL, no_repeat). -%% This is the time after last message received that we think/hope -%% that the handler has an empty mailbox (no new log request has -%% come in). --define(IDLE_DETECT_TIME_MSEC, 100). --define(IDLE_DETECT_TIME_USEC, 100000). - %% Default disk log option values -define(DISK_LOG_TYPE, wrap). -define(DISK_LOG_MAX_NO_FILES, 10). @@ -83,43 +49,6 @@ list_to_atom(lists:concat([MODULE,"_",Name]))). %%%----------------------------------------------------------------- -%%% Overload protection macros - --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(min(X1, X2), - if X2 == undefined -> X1; - X2 < X1 -> X2; - true -> X1 - end). - --define(max(X1, X2), - if - X2 == undefined -> X1; - X2 > X1 -> X2; - true -> X1 - end). - --define(diff_time(OS_T1, OS_T0), OS_T1-OS_T0). - -%%%----------------------------------------------------------------- %%% The test hook macros make it possible to observe and manipulate %%% internal handler functionality. When enabled, these macros will %%% slow down execution and therefore should not be include in code @@ -137,7 +66,7 @@ ets:insert(?TEST_HOOKS_TAB, {internal_log,{logger,internal_log}}), ets:insert(?TEST_HOOKS_TAB, {file_write,ok}), ets:insert(?TEST_HOOKS_TAB, {file_datasync,ok}), - ets:insert(?TEST_HOOKS_TAB, {disk_log_blog,ok}), + ets:insert(?TEST_HOOKS_TAB, {disk_log_write,ok}), ets:insert(?TEST_HOOKS_TAB, {disk_log_sync,ok})). -define(set_internal_log(MOD_FUNC), @@ -150,7 +79,7 @@ ets:insert(?TEST_HOOKS_TAB, {internal_log,{logger,internal_log}}), ets:insert(?TEST_HOOKS_TAB, {file_write,ok}), ets:insert(?TEST_HOOKS_TAB, {file_datasync,ok}), - ets:insert(?TEST_HOOKS_TAB, {disk_log_blog,ok}), + ets:insert(?TEST_HOOKS_TAB, {disk_log_write,ok}), ets:insert(?TEST_HOOKS_TAB, {disk_log_sync,ok})). -define(internal_log(TYPE, TERM), @@ -171,11 +100,11 @@ [{_,ERROR}] -> ERROR catch _:_ -> file:datasync(DEVICE) end). - -define(disk_log_blog(LOG, DATA), - try ets:lookup(?TEST_HOOKS_TAB, disk_log_blog) of - [{_,ok}] -> disk_log:blog(LOG, DATA); + -define(disk_log_write(LOG, MODE, DATA), + try ets:lookup(?TEST_HOOKS_TAB, disk_log_write) of + [{_,ok}] -> disk_log_write(LOG, MODE, DATA); [{_,ERROR}] -> ERROR - catch _:_ -> disk_log:blog(LOG, DATA) end). + catch _:_ -> disk_log_write(LOG, MODE, DATA) end). -define(disk_log_sync(LOG), try ets:lookup(?TEST_HOOKS_TAB, disk_log_sync) of @@ -183,7 +112,6 @@ [{_,ERROR}] -> ERROR catch _:_ -> disk_log:sync(LOG) end). - -define(DEFAULT_CALL_TIMEOUT, 5000). -else. % DEFAULTS! -define(TEST_HOOKS_TAB, undefined). @@ -194,70 +122,6 @@ -define(internal_log(TYPE, TERM), logger:internal_log(TYPE, TERM)). -define(file_write(DEVICE, DATA), file:write(DEVICE, DATA)). -define(file_datasync(DEVICE), file:datasync(DEVICE)). - -define(disk_log_blog(LOG, DATA), disk_log:blog(LOG, DATA)). + -define(disk_log_write(LOG, MODE, DATA), disk_log_write(LOG, MODE, DATA)). -define(disk_log_sync(LOG), disk_log:sync(LOG)). - -define(DEFAULT_CALL_TIMEOUT, 10000). --endif. - -%%%----------------------------------------------------------------- -%%% These macros enable statistics counters in the state of the -%%% handler which is useful for analysing the overload protection -%%% behaviour. These counters should not be included in code to be -%%% officially released (as some counters will grow very large -%%% over time). - -%%-define(SAVE_STATS, true). --ifdef(SAVE_STATS). - -define(merge_with_stats(STATE), - STATE#{flushes => 0, flushed => 0, drops => 0, - casts => 0, calls => 0, - max_qlen => 0, max_time => 0}). - - -define(update_max_qlen(QLEN, STATE), - begin #{max_qlen := QLEN0} = STATE, - STATE#{max_qlen => ?max(QLEN0,QLEN)} end). - - -define(update_calls_or_casts(CALL_OR_CAST, INC, STATE), - case CALL_OR_CAST of - cast -> - #{casts := CASTS0} = STATE, - STATE#{casts => CASTS0+INC}; - call -> - #{calls := CALLS0} = STATE, - STATE#{calls => CALLS0+INC} - end). - - -define(update_max_time(TIME, STATE), - begin #{max_time := TIME0} = STATE, - STATE#{max_time => ?max(TIME0,TIME)} end). - - -define(update_other(OTHER, VAR, INCVAL, STATE), - begin #{OTHER := VAR} = STATE, - STATE#{OTHER => VAR+INCVAL} end). - --else. % DEFAULT! - -define(merge_with_stats(STATE), STATE). - -define(update_max_qlen(_QLEN, STATE), STATE). - -define(update_calls_or_casts(_CALL_OR_CAST, _INC, STATE), STATE). - -define(update_max_time(_TIME, STATE), STATE). - -define(update_other(_OTHER, _VAR, _INCVAL, STATE), STATE). --endif. - -%%%----------------------------------------------------------------- -%%% These macros enable callbacks that make it possible to analyse -%%% the overload protection behaviour from outside the handler -%%% process (including dropped requests on the client side). -%%% An external callback module (?OBSERVER_MOD) is required which -%%% is not part of the kernel application. For this reason, these -%%% callbacks should not be included in code to be officially released. - -%%-define(OBSERVER_MOD, logger_test). --ifdef(OBSERVER_MOD). - -define(start_observation(NAME), ?OBSERVER:start_observation(NAME)). - -define(observe(NAME,EVENT), ?OBSERVER:observe(NAME,EVENT)). - --else. % DEFAULT! - -define(start_observation(_NAME), ok). - -define(observe(_NAME,_EVENT), ok). -endif. -%%! <--- diff --git a/lib/kernel/src/logger_internal.hrl b/lib/kernel/src/logger_internal.hrl index d96a4ac78b..e53922e5d3 100644 --- a/lib/kernel/src/logger_internal.hrl +++ b/lib/kernel/src/logger_internal.hrl @@ -19,6 +19,7 @@ %% -include_lib("kernel/include/logger.hrl"). -define(LOGGER_TABLE,logger). +-define(PROXY_KEY,'$proxy_config$'). -define(PRIMARY_KEY,'$primary_config$'). -define(HANDLER_KEY,'$handler_config$'). -define(LOGGER_META_KEY,'$logger_metadata$'). @@ -40,12 +41,14 @@ -define(DEFAULT_LOGGER_CALL_TIMEOUT, infinity). --define(LOG_INTERNAL(Level,Report), +-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), case logger:allow(Level,?MODULE) of true -> %% Spawn this to avoid deadlocks - _ = spawn(logger,macro_log,[?LOCATION,Level,Report, - logger:add_default_metadata(#{})]), + _ = spawn(logger,macro_log,[?LOCATION,Level|Data]++ + [logger:add_default_metadata(#{})]), ok; false -> ok diff --git a/lib/kernel/src/logger_olp.erl b/lib/kernel/src/logger_olp.erl new file mode 100644 index 0000000000..009280a9c9 --- /dev/null +++ b/lib/kernel/src/logger_olp.erl @@ -0,0 +1,626 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017-2018. 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(logger_olp). +-behaviour(gen_server). + +-include("logger_olp.hrl"). +-include("logger_internal.hrl"). + +%% API +-export([start_link/4, load/2, info/1, reset/1, stop/1, restart/1, + set_opts/2, get_opts/1, get_default_opts/0, get_pid/1, + call/2, cast/2, get_ref/0, get_ref/1]). + +%% gen_server and proc_lib callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(OPT_KEYS,[sync_mode_qlen, + drop_mode_qlen, + flush_qlen, + burst_limit_enable, + burst_limit_max_count, + burst_limit_window_time, + overload_kill_enable, + overload_kill_qlen, + overload_kill_mem_size, + overload_kill_restart_after]). + +-export_type([olp_ref/0, options/0]). + +-opaque olp_ref() :: {atom(),pid(),ets:tid()}. + +-type options() :: logger:olp_config(). + +%%%----------------------------------------------------------------- +%%% API + +-spec start_link(Name,Module,Args,Options) -> {ok,Pid,Olp} | {error,Reason} when + Name :: atom(), + Module :: module(), + Args :: term(), + Options :: options(), + Pid :: pid(), + Olp :: olp_ref(), + Reason :: term(). +start_link(Name,Module,Args,Options0) when is_map(Options0) -> + Options = maps:merge(get_default_opts(),Options0), + case check_opts(Options) of + ok -> + proc_lib:start_link(?MODULE,init,[[Name,Module,Args,Options]]); + Error -> + Error + end. + +-spec load(Olp, Msg) -> ok when + Olp :: olp_ref(), + Msg :: term(). +load({_Name,Pid,ModeRef},Msg) -> + %% If the process is getting overloaded, the message will be + %% 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 + async -> + gen_server:cast(Pid, {'$olp_load',Msg}); + sync -> + case call(Pid, {'$olp_load',Msg}) of + ok -> + ok; + _Other -> + %% dropped or {error,busy} + ?observe(_Name,{dropped,1}), + ok + 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. + +-spec info(Olp) -> map() | {error, busy} when + Olp :: atom() | pid() | olp_ref(). +info(Olp) -> + call(Olp, info). + +-spec reset(Olp) -> ok | {error, busy} when + Olp :: atom() | pid() | olp_ref(). +reset(Olp) -> + call(Olp, reset). + +-spec stop(Olp) -> ok when + Olp :: atom() | pid() | olp_ref(). +stop({_Name,Pid,_ModRef}) -> + stop(Pid); +stop(Pid) -> + _ = gen_server:call(Pid, stop), + ok. + +-spec set_opts(Olp, Opts) -> ok | {error,term()} | {error, busy} when + Olp :: atom() | pid() | olp_ref(), + Opts :: options(). +set_opts(Olp, Opts) -> + call(Olp, {set_opts,Opts}). + +-spec get_opts(Olp) -> options() | {error, busy} when + Olp :: atom() | pid() | olp_ref(). +get_opts(Olp) -> + call(Olp, get_opts). + +-spec get_default_opts() -> options(). +get_default_opts() -> + #{sync_mode_qlen => ?SYNC_MODE_QLEN, + drop_mode_qlen => ?DROP_MODE_QLEN, + flush_qlen => ?FLUSH_QLEN, + burst_limit_enable => ?BURST_LIMIT_ENABLE, + burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT, + burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME, + overload_kill_enable => ?OVERLOAD_KILL_ENABLE, + overload_kill_qlen => ?OVERLOAD_KILL_QLEN, + overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE, + overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER}. + +-spec restart(fun(() -> any())) -> ok. +restart(Fun) -> + Result = + try Fun() + catch C:R:S -> + {error,{restart_failed,Fun,C,R,S}} + end, + ?LOG_INTERNAL(debug,[{logger_olp,restart}, + {result,Result}]), + ok. + +-spec get_ref() -> olp_ref(). +get_ref() -> + get(olp_ref). + +-spec get_ref(PidOrName) -> olp_ref() | {error, busy} when + PidOrName :: pid() | atom(). +get_ref(PidOrName) -> + call(PidOrName,get_ref). + +-spec get_pid(olp_ref()) -> pid(). +get_pid({_Name,Pid,_ModeRef}) -> + Pid. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init([Name,Module,Args,Options]) -> + register(Name, self()), + process_flag(message_queue_data, off_heap), + + ?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 + catch + _:Error -> + unregister(Name), + proc_lib:init_ack(Error) + end. + +%% This is the synchronous load event. +handle_call({'$olp_load', Msg}, _From, State) -> + {Result,State1} = do_load(Msg, call, State#{idle=>false}), + %% Result == ok | dropped + reply_return(Result,State1); + +handle_call(get_ref,_From,#{id:=Name,mode_ref:=ModeRef}=State) -> + reply_return({Name,self(),ModeRef},State); + +handle_call({set_opts,Opts0},_From,State) -> + Opts = maps:merge(maps:with(?OPT_KEYS,State),Opts0), + case check_opts(Opts) of + ok -> + reply_return(ok, maps:merge(State,Opts)); + Error -> + reply_return(Error, State) + end; + +handle_call(get_opts,_From,State) -> + reply_return(maps:with(?OPT_KEYS,State), State); + +handle_call(info, _From, State) -> + reply_return(State, State); + +handle_call(reset, _From, #{module:=Module,cb_state:=CBState}=State) -> + State1 = ?merge_with_stats(State), + CBState1 = try_callback_call(Module,reset_state,[CBState],CBState), + reply_return(ok, State1#{idle => true, + last_qlen => 0, + last_load_ts => ?timestamp(), + cb_state => CBState1}); + +handle_call(stop, _From, State) -> + {stop, {shutdown,stopped}, ok, State}; + +handle_call(Msg, From, #{module:=Module,cb_state:=CBState}=State) -> + case try_callback_call(Module,handle_call,[Msg, From, CBState]) of + {reply,Reply,CBState1} -> + reply_return(Reply,State#{cb_state=>CBState1}); + {noreply,CBState1} -> + noreply_return(State#{cb_state=>CBState1}); + {stop, Reason, Reply, CBState1} -> + {stop, Reason, Reply, State#{cb_state=>CBState1}}; + {stop, Reason, CBState1} -> + {stop, Reason, State#{cb_state=>CBState1}} + end. + +%% This is the asynchronous load event. +handle_cast({'$olp_load', Msg}, State) -> + {_Result,State1} = do_load(Msg, cast, State#{idle=>false}), + noreply_return(State1); + +handle_cast(Msg, #{module:=Module, cb_state:=CBState} = State) -> + case try_callback_call(Module,handle_cast,[Msg, CBState]) of + {noreply,CBState1} -> + noreply_return(State#{cb_state=>CBState1}); + {stop, Reason, CBState1} -> + {stop, Reason, State#{cb_state=>CBState1}} + end. + +handle_info(timeout, #{mode_ref:=_ModeRef, mode:=Mode} = State) -> + State1 = notify(idle,State), + State2 = maybe_notify_mode_change(async,State1), + {noreply, State2#{idle => true, + mode => ?change_mode(_ModeRef, Mode, async), + burst_msg_count => 0}}; +handle_info(Msg, #{module := Module, cb_state := CBState} = State) -> + case try_callback_call(Module,handle_info,[Msg, CBState]) of + {noreply,CBState1} -> + noreply_return(State#{cb_state=>CBState1}); + {stop, Reason, CBState1} -> + {stop, Reason, State#{cb_state=>CBState1}}; + {load,CBState1} -> + {_,State1} = do_load(Msg, cast, State#{idle=>false, + cb_state=>CBState1}), + noreply_return(State1) + end. + +terminate({shutdown,{overloaded,_QLen,_Mem}}, + #{id:=Name, module := Module, cb_state := CBState, + overload_kill_restart_after := RestartAfter} = State) -> + %% We're terminating because of an overload situation (see + %% kill_if_choked/3). + unregister(Name), %%!!!! to avoid error printout of callback crashed on stop + case try_callback_call(Module,terminate,[overloaded,CBState],ok) of + {ok,Fun} when is_function(Fun,0), is_integer(RestartAfter) -> + set_restart_flag(State), + _ = timer:apply_after(RestartAfter,?MODULE,restart,[Fun]), + ok; + _ -> + ok + end; +terminate(Reason, #{id:=Name, module:=Module, cb_state:=CBState}) -> + _ = try_callback_call(Module,terminate,[Reason,CBState],ok), + unregister(Name), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +%%%----------------------------------------------------------------- +%%% Internal functions +-spec call(Olp, term()) -> term() | {error,busy} when + Olp :: atom() | pid() | olp_ref(). +call({_Name, Pid, _ModeRef},Msg) -> + call(Pid, Msg); +call(Server, Msg) -> + try + gen_server:call(Server, Msg) + catch + _:{timeout,_} -> {error,busy} + end. + +-spec cast(olp_ref(),term()) -> ok. +cast({_Name, Pid, _ModeRef},Msg) -> + gen_server:cast(Pid, Msg). + +%% check for overload between every event (and set Mode to async, +%% sync or drop accordingly), but never flush the whole mailbox +%% before LogWindowSize events have been handled +do_load(Msg, CallOrCast, State) -> + T1 = ?timestamp(), + State1 = ?update_time(T1,State), + + %% check if the process is getting overloaded, or if it's + %% recovering from overload (the check must be done for each + %% event to react quickly to large bursts of events and + %% to ensure that the handler can never end up in drop mode + %% with an empty mailbox, which would stop operation) + {Mode1,QLen,Mem,State2} = check_load(State1), + + %% kill the handler if it can't keep up with the load + kill_if_choked(QLen, Mem, State2), + + if Mode1 == flush -> + flush(T1, State2); + true -> + handle_load(Mode1, T1, Msg, CallOrCast, State2) + end. + +%% 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 load messages in the mailbox (a limited number in order + %% to not cause long delays) + NewFlushed = flush_load(?FLUSH_MAX_N), + + %% write info in log about flushed messages + State1=notify({flushed,NewFlushed},State), + + %% because of the receive loop when flushing messages, the + %% handler will be scheduled out often and the mailbox could + %% grow very large, so we'd better check the queue again here + {_,QLen1} = process_info(self(), message_queue_len), + ?observe(_Name,{max_qlen,QLen1}), + + %% Add 1 for the current log event + ?observe(_Name,{flushed,NewFlushed+1}), + + State2 = ?update_max_time(?diff_time(T1,_T0),State1), + 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), + last_qlen => QLen1, + last_load_ts => T1})}. + +%% this function is called to actually handle the message +handle_load(Mode, T1, Msg, _CallOrCast, + State = #{id := _Name, + module := Module, + cb_state := CBState, + last_qlen := LastQLen, + last_load_ts := _T0}) -> + %% check if we need to limit the number of writes + %% during a burst of log events + {DoWrite,State1} = limit_burst(State), + + {Result,LastQLen1,CBState1} = + if DoWrite -> + ?observe(_Name,{_CallOrCast,1}), + CBS = try_callback_call(Module,handle_load,[Msg,CBState]), + {ok,element(2, process_info(self(), message_queue_len)),CBS}; + true -> + ?observe(_Name,{flushed,1}), + {dropped,LastQLen,CBState} + end, + State2 = State1#{cb_state=>CBState1}, + + State3 = State2#{mode => Mode}, + State4 = ?update_calls_or_casts(_CallOrCast,1,State3), + State5 = ?update_max_qlen(LastQLen1,State4), + State6 = + ?update_max_time(?diff_time(T1,_T0), + State5#{last_qlen := LastQLen1, + last_load_ts => T1}), + State7 = case Result of + ok -> + S = ?update_freq(T1,State6), + ?update_other(writes,WRITES,1,S); + _ -> + State6 + end, + {Result,State7}. + + +%%%----------------------------------------------------------------- +%%% Check that the options are valid +check_opts(Options) when is_map(Options) -> + case do_check_opts(maps:to_list(Options)) of + ok -> + case overload_levels_ok(Options) of + true -> + ok; + false -> + Faulty = maps:with([sync_mode_qlen, + drop_mode_qlen, + flush_qlen],Options), + {error,{invalid_olp_levels,Faulty}} + end; + {error,Key,Value} -> + {error,{invalid_olp_config,#{Key=>Value}}} + end. + +do_check_opts([{sync_mode_qlen,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{drop_mode_qlen,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{flush_qlen,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{burst_limit_enable,Bool}|Options]) when is_boolean(Bool) -> + do_check_opts(Options); +do_check_opts([{burst_limit_max_count,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{burst_limit_window_time,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{overload_kill_enable,Bool}|Options]) when is_boolean(Bool) -> + do_check_opts(Options); +do_check_opts([{overload_kill_qlen,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{overload_kill_mem_size,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{overload_kill_restart_after,NorA}|Options]) + when is_integer(NorA); NorA == infinity -> + do_check_opts(Options); +do_check_opts([{Key,Value}|_]) -> + {error,Key,Value}; +do_check_opts([]) -> + ok. + +set_restart_flag(#{id := Name, module := Module}) -> + Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])), + spawn(fun() -> + register(Flag, self()), + timer:sleep(infinity) + end), + ok. + +reset_restart_flag(#{id := Name, module := Module} = State) -> + Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])), + case whereis(Flag) of + undefined -> + State; + Pid -> + exit(Pid, kill), + notify(restart,State) + end. + +check_load(State = #{id:=_Name, mode_ref := ModeRef, mode := Mode, + sync_mode_qlen := SyncModeQLen, + drop_mode_qlen := DropModeQLen, + flush_qlen := FlushQLen}) -> + {_,Mem} = process_info(self(), memory), + ?observe(_Name,{max_mem,Mem}), + {_,QLen} = process_info(self(), message_queue_len), + ?observe(_Name,{max_qlen,QLen}), + %% When the handler process gets scheduled in, it's impossible + %% to predict the QLen. We could jump "up" arbitrarily from say + %% async to sync, async to drop, sync to flush, etc. However, when + %% the handler process manages the log events (without flushing), + %% one after the other, we will move "down" from drop to sync and + %% from sync to async. This way we don't risk getting stuck in + %% drop or sync mode with an empty mailbox. + {Mode1,_NewDrops,_NewFlushes} = + if + QLen >= FlushQLen -> + {flush, 0,1}; + QLen >= DropModeQLen -> + %% Note that drop mode will force load messages to + %% 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}; + QLen >= SyncModeQLen -> + {?change_mode(ModeRef, Mode, sync), 0,0}; + true -> + {?change_mode(ModeRef, Mode, async), 0,0} + end, + State1 = ?update_other(drops,DROPS,_NewDrops,State), + State2 = ?update_max_qlen(QLen,State1), + State3 = maybe_notify_mode_change(Mode1,State2), + {Mode1, QLen, Mem, + ?update_other(flushes,FLUSHES,_NewFlushes, + State3#{last_qlen => QLen})}. + +limit_burst(#{burst_limit_enable := false}=State) -> + {true,State}; +limit_burst(#{burst_win_ts := BurstWinT0, + burst_msg_count := BurstMsgCount, + burst_limit_window_time := BurstLimitWinTime, + burst_limit_max_count := BurstLimitMaxCnt} = State) -> + if (BurstMsgCount >= BurstLimitMaxCnt) -> + %% the limit for allowed messages has been reached + BurstWinT1 = ?timestamp(), + case ?diff_time(BurstWinT1,BurstWinT0) of + BurstCheckTime when BurstCheckTime < (BurstLimitWinTime*1000) -> + %% we're still within the burst time frame + {false,?update_other(burst_drops,BURSTS,1,State)}; + _BurstCheckTime -> + %% burst time frame passed, reset counters + {true,State#{burst_win_ts => BurstWinT1, + burst_msg_count => 0}} + end; + true -> + %% the limit for allowed messages not yet reached + {true,State#{burst_win_ts => BurstWinT0, + burst_msg_count => BurstMsgCount+1}} + end. + +kill_if_choked(QLen, Mem, #{overload_kill_enable := KillIfOL, + overload_kill_qlen := OLKillQLen, + overload_kill_mem_size := OLKillMem}) -> + if KillIfOL andalso + ((QLen > OLKillQLen) orelse (Mem > OLKillMem)) -> + exit({shutdown,{overloaded,QLen,Mem}}); + true -> + ok + end. + +flush_load(Limit) -> + process_flag(priority, high), + Flushed = flush_load(0, Limit), + process_flag(priority, normal), + Flushed. + +flush_load(Limit, Limit) -> + Limit; +flush_load(N, Limit) -> + %% flush log events but leave other events, such as info, reset + %% and stop, so that these have a chance to be processed even + %% under heavy load + receive + {'$gen_cast',{'$olp_load',_}} -> + flush_load(N+1, Limit); + {'$gen_call',{Pid,MRef},{'$olp_load',_}} -> + Pid ! {MRef, dropped}, + flush_load(N+1, Limit); + {log,_,_,_,_} -> + flush_load(N+1, Limit); + {log,_,_,_} -> + flush_load(N+1, Limit) + after + 0 -> N + end. + +overload_levels_ok(Options) -> + SMQL = maps:get(sync_mode_qlen, Options, ?SYNC_MODE_QLEN), + DMQL = maps:get(drop_mode_qlen, Options, ?DROP_MODE_QLEN), + FQL = maps:get(flush_qlen, Options, ?FLUSH_QLEN), + (DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL). + +maybe_notify_mode_change(drop,#{mode:=Mode0}=State) + when Mode0=/=drop -> + notify({mode_change,Mode0,drop},State); +maybe_notify_mode_change(Mode1,#{mode:=drop}=State) + when Mode1==async; Mode1==sync -> + notify({mode_change,drop,Mode1},State); +maybe_notify_mode_change(_,State) -> + State. + +notify(Note,#{module:=Module,cb_state:=CBState}=State) -> + CBState1 = try_callback_call(Module,notify,[Note,CBState],CBState), + State#{cb_state=>CBState1}. + +try_callback_call(Module, Function, Args) -> + try_callback_call(Module, Function, Args, '$no_default_return'). + +try_callback_call(Module, Function, Args, DefRet) -> + try apply(Module, Function, Args) + catch + throw:R -> R; + error:undef:S when DefRet=/='$no_default_return' -> + case S of + [{Module,Function,Args,_}|_] -> + DefRet; + _ -> + erlang:raise(error,undef,S) + end + end. + +noreply_return(#{idle:=true}=State) -> + {noreply,State}; +noreply_return(#{idle:=false}=State) -> + {noreply,State,?IDLE_DETECT_TIME}. + +reply_return(Reply,#{idle:=true}=State) -> + {reply,Reply,State}; +reply_return(Reply,#{idle:=false}=State) -> + {reply,Reply,State,?IDLE_DETECT_TIME}. diff --git a/lib/kernel/src/logger_olp.hrl b/lib/kernel/src/logger_olp.hrl new file mode 100644 index 0000000000..9b4f5ebf27 --- /dev/null +++ b/lib/kernel/src/logger_olp.hrl @@ -0,0 +1,180 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2015. 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% +%% + +%%%----------------------------------------------------------------- +%%% Overload protection configuration + +%%! *** NOTE *** +%%! It's important that: +%%! SYNC_MODE_QLEN =< DROP_MODE_QLEN =< FLUSH_QLEN +%%! and that DROP_MODE_QLEN >= 2. +%%! Otherwise the process could end up in drop mode with no new +%%! log requests to process. This would cause all future requests +%%! to be dropped (no switch to async mode would ever take place). + +%% This specifies the message_queue_len value where the log +%% requests switch from asynchronous casts to synchronous calls. +-define(SYNC_MODE_QLEN, 10). +%% Above this message_queue_len, log requests will be dropped, +%% i.e. no log requests get sent to the process. +-define(DROP_MODE_QLEN, 200). +%% Above this message_queue_len, the process will flush its mailbox +%% and only leave this number of messages in it. +-define(FLUSH_QLEN, 1000). + +%% Never flush more than this number of messages in one go, or the +%% process will be unresponsive for seconds (keep this number as large +%% as possible or the mailbox could grow large). +-define(FLUSH_MAX_N, 5000). + +%% BURST_LIMIT_MAX_COUNT is the max number of log requests allowed +%% to be written within a BURST_LIMIT_WINDOW_TIME time frame. +-define(BURST_LIMIT_ENABLE, true). +-define(BURST_LIMIT_MAX_COUNT, 500). +-define(BURST_LIMIT_WINDOW_TIME, 1000). + +%% This enables/disables the feature to automatically terminate the +%% process if it gets too loaded (and can't keep up). +-define(OVERLOAD_KILL_ENABLE, false). +%% If the message_queue_len goes above this size even after +%% flushing has been performed, the process is terminated. +-define(OVERLOAD_KILL_QLEN, 20000). +%% If the memory usage exceeds this level, the process is terminated. +-define(OVERLOAD_KILL_MEM_SIZE, 3000000). + +%% This is the default time to wait before restarting and accepting +%% new requests. The value 'infinity' disables restarts. +-define(OVERLOAD_KILL_RESTART_AFTER, 5000). + +%% This is the time in milliseconds after last load message received +%% that we notify the callback about being idle. +-define(IDLE_DETECT_TIME, 100). + +%%%----------------------------------------------------------------- +%%% Overload protection macros + +-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 + X2 == undefined -> X1; + X2 > X1 -> X2; + true -> X1 + end). + +-define(diff_time(OS_T1, OS_T0), OS_T1-OS_T0). + +%%%----------------------------------------------------------------- +%%% These macros enable statistics counters in the state of the +%%% process, which is useful for analysing the overload protection +%%% behaviour. These counters should not be included in code to be +%%% officially released (as some counters will grow very large over +%%% time). + +%% -define(SAVE_STATS, true). +-ifdef(SAVE_STATS). + -define(merge_with_stats(STATE), + begin + TIME = ?timestamp(), + STATE#{start => TIME, time => {TIME,0}, + flushes => 0, flushed => 0, drops => 0, + burst_drops => 0, casts => 0, calls => 0, + writes => 0, max_qlen => 0, max_time => 0, + freq => {TIME,0,0}} end). + + -define(update_max_qlen(QLEN, STATE), + begin #{max_qlen := QLEN0} = STATE, + STATE#{max_qlen => ?max(QLEN0,QLEN)} end). + + -define(update_calls_or_casts(CALL_OR_CAST, INC, STATE), + case CALL_OR_CAST of + cast -> + #{casts := CASTS0} = STATE, + STATE#{casts => CASTS0+INC}; + call -> + #{calls := CALLS0} = STATE, + STATE#{calls => CALLS0+INC} + end). + + -define(update_max_time(TIME, STATE), + begin #{max_time := TIME0} = STATE, + STATE#{max_time => ?max(TIME0,TIME)} end). + + -define(update_other(OTHER, VAR, INCVAL, STATE), + begin #{OTHER := VAR} = STATE, + STATE#{OTHER => VAR+INCVAL} end). + + -define(update_freq(TIME,STATE), + begin + case STATE of + #{freq := {START, 49, _}} -> + STATE#{freq => {TIME, 0, trunc(1000000*50/(?diff_time(TIME,START)))}}; + #{freq := {START, N, FREQ}} -> + STATE#{freq => {START, N+1, FREQ}} + end end). + + -define(update_time(TIME,STATE), + begin #{start := START} = STATE, + STATE#{time => {TIME,trunc((?diff_time(TIME,START))/1000000)}} end). + +-else. % DEFAULT! + -define(merge_with_stats(STATE), STATE). + -define(update_max_qlen(_QLEN, STATE), STATE). + -define(update_calls_or_casts(_CALL_OR_CAST, _INC, STATE), STATE). + -define(update_max_time(_TIME, STATE), STATE). + -define(update_other(_OTHER, _VAR, _INCVAL, STATE), STATE). + -define(update_freq(_TIME, STATE), STATE). + -define(update_time(_TIME, STATE), STATE). +-endif. + +%%%----------------------------------------------------------------- +%%% These macros enable callbacks that make it possible to analyse the +%%% overload protection behaviour from outside the process (including +%%% dropped requests on the client side). An external callback module +%%% (?OBSERVER_MOD) is required which is not part of the kernel +%%% application. For this reason, these callbacks should not be +%%% included in code to be officially released. + +%%-define(OBSERVER_MOD, logger_test). +-ifdef(OBSERVER_MOD). + -define(start_observation(NAME), ?OBSERVER:start_observation(NAME)). + -define(observe(NAME,EVENT), ?OBSERVER:observe(NAME,EVENT)). + +-else. % DEFAULT! + -define(start_observation(_NAME), ok). + -define(observe(_NAME,_EVENT), ok). +-endif. diff --git a/lib/kernel/src/logger_proxy.erl b/lib/kernel/src/logger_proxy.erl new file mode 100644 index 0000000000..24b293805c --- /dev/null +++ b/lib/kernel/src/logger_proxy.erl @@ -0,0 +1,165 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017-2018. 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(logger_proxy). + +%% API +-export([start_link/0, restart/0, log/1, child_spec/0, get_default_config/0]). + +%% logger_olp callbacks +-export([init/1, handle_load/2, handle_info/2, terminate/2, + notify/2]). + +-include("logger_internal.hrl"). + +-define(SERVER,?MODULE). + +%%%----------------------------------------------------------------- +%%% API +-spec log(RemoteLog) -> ok when + RemoteLog :: {remote,node(),LogEvent}, + LogEvent :: {log,Level,Format,Args,Meta} | + {log,Level,StringOrReport,Meta}, + Level :: logger:level(), + Format :: io:format(), + Args :: list(term()), + StringOrReport :: unicode:chardata() | logger:report(), + Meta :: logger:metadata(). +log(RemoteLog) -> + Olp = persistent_term:get(?MODULE), + case logger_olp:get_pid(Olp) =:= self() of + true -> + %% This happens when the log event comes from the + %% emulator, and the group leader is on a remote node. + _ = handle_load(RemoteLog, no_state), + ok; + false -> + logger_olp:load(Olp, RemoteLog) + end. + +%% Called by supervisor +-spec start_link() -> {ok,pid(),logger_olp:olp_ref()} | {error,term()}. +start_link() -> + %% Notice that sync_mode is only used when logging to remote node, + %% i.e. when the log/2 API function is called. + %% + %% When receiving log events from the emulator or from a remote + %% node, the log event is sent as a message to this process, and + %% thus received directly in handle_info/2. This means that the + %% mode (async/sync/drop) is not read before the message is + %% sent. Thus sync mode is never entered, and drop mode is + %% implemented by setting the system_logger flag to undefined (see + %% notify/2) + %% + %% Burst limit is disabled, since this is only a proxy and we + %% don't want to limit bursts twice (here and in the handler). + logger_olp:start_link(?SERVER,?MODULE,[],logger:get_proxy_config()). + +%% Fun used for restarting this process after it has been killed due +%% to overload (must set overload_kill_enable=>true in opts) +restart() -> + case supervisor:start_child(logger_sup, child_spec()) of + {ok,_Pid,Olp} -> + {ok,Olp}; + {error,{Reason,Ch}} when is_tuple(Ch), element(1,Ch)==child -> + {error,Reason}; + Error -> + Error + end. + +%% Called internally and by logger_sup +child_spec() -> + Name = ?SERVER, + #{id => Name, + start => {?MODULE, start_link, []}, + restart => temporary, + shutdown => 2000, + type => worker, + modules => [?MODULE]}. + +get_default_config() -> + OlpDefault = logger_olp:get_default_opts(), + OlpDefault#{sync_mode_qlen=>500, + drop_mode_qlen=>1000, + flush_qlen=>5000, + burst_limit_enable=>false}. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init([]) -> + process_flag(trap_exit, true), + _ = erlang:system_flag(system_logger,self()), + persistent_term:put(?MODULE,logger_olp:get_ref()), + {ok,no_state}. + +%% Log event to send to the node where the group leader of it's client resides +handle_load({remote,Node,Log},State) -> + %% If the connection is overloaded (send_nosuspend returns false), + %% we drop the message. + _ = erlang:send_nosuspend({?SERVER,Node},Log), + State; +%% Log event to log on this node +handle_load({log,Level,Format,Args,Meta},State) -> + try_log([Level,Format,Args,Meta]), + State; +handle_load({log,Level,Report,Meta},State) -> + try_log([Level,Report,Meta]), + State. + +%% Log event sent to this process e.g. from the emulator - it is really load +handle_info(Log,State) when is_tuple(Log), element(1,Log)==log -> + {load,State}. + +terminate(overloaded, _State) -> + _ = erlang:system_flag(system_logger,undefined), + {ok,fun ?MODULE:restart/0}; +terminate(_Reason, _State) -> + _ = erlang:system_flag(system_logger,whereis(logger)), + ok. + +notify({mode_change,Mode0,Mode1},State) -> + _ = if Mode1=:=drop -> % entering drop mode + erlang:system_flag(system_logger,undefined); + Mode0=:=drop -> % leaving drop mode + erlang:system_flag(system_logger,self()); + true -> + ok + end, + ?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]), + State; +notify(restart,State) -> + ?LOG_INTERNAL(notice, "~w restarted", [?MODULE]), + State; +notify(_Note,State) -> + State. + +%%%----------------------------------------------------------------- +%%% Internal functions +try_log(Args) -> + try apply(logger,log,Args) + catch 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 a1d40f1123..722246e82c 100644 --- a/lib/kernel/src/logger_server.erl +++ b/lib/kernel/src/logger_server.erl @@ -22,14 +22,17 @@ -behaviour(gen_server). %% API --export([start_link/0, - add_handler/3, remove_handler/1, +-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, - set_config/2, set_config/3, update_config/2, + set_config/2, set_config/3, + update_config/2, update_config/3, update_formatter_config/2]). +%% Helper +-export([diff_maps/2]). + %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). @@ -39,7 +42,7 @@ -define(SERVER, logger). -define(LOGGER_SERVER_TAG, '$logger_cb_process'). --record(state, {tid, async_req, async_req_queue}). +-record(state, {tid, async_req, async_req_queue, remote_logger}). %%%=================================================================== %%% API @@ -105,12 +108,25 @@ cache_module_level(Module) -> gen_server:cast(?SERVER,{cache_module_level,Module}). set_config(Owner,Key,Value) -> - update_config(Owner,#{Key=>Value}). + case sanity_check(Owner,Key,Value) of + ok -> + call({change_config,set,Owner,Key,Value}); + Error -> + Error + end. set_config(Owner,Config) -> case sanity_check(Owner,Config) of ok -> - call({set_config,Owner,Config}); + call({change_config,set,Owner,Config}); + Error -> + Error + end. + +update_config(Owner,Key,Value) -> + case sanity_check(Owner,Key,Value) of + ok -> + call({change_config,update,Owner,Key,Value}); Error -> Error end. @@ -118,7 +134,7 @@ set_config(Owner,Config) -> update_config(Owner, Config) -> case sanity_check(Owner,Config) of ok -> - call({update_config,Owner,Config}); + call({change_config,update,Owner,Config}); Error -> Error end. @@ -138,6 +154,8 @@ init([]) -> process_flag(trap_exit, true), put(?LOGGER_SERVER_TAG,true), Tid = logger_config:new(?LOGGER_TABLE), + %% Store initial proxy config. logger_proxy reads config from here at startup. + logger_config:create(Tid,proxy,logger_proxy:get_default_config()), PrimaryConfig = maps:merge(default_config(primary), #{handlers=>[simple]}), logger_config:create(Tid,primary,PrimaryConfig), @@ -204,46 +222,90 @@ handle_call({add_filter,Id,Filter}, _From,#state{tid=Tid}=State) -> handle_call({remove_filter,Id,FilterId}, _From, #state{tid=Tid}=State) -> Reply = do_remove_filter(Tid,Id,FilterId), {reply,Reply,State}; -handle_call({update_config,primary,NewConfig}, _From, #state{tid=Tid}=State) -> +handle_call({change_config,SetOrUpd,proxy,Config0},_From,#state{tid=Tid}=State) -> + Default = + case SetOrUpd of + set -> + logger_proxy:get_default_config(); + update -> + {ok,OldConfig} = logger_config:get(Tid,proxy), + OldConfig + end, + Config = maps:merge(Default,Config0), + Reply = + case logger_olp:set_opts(logger_proxy,Config) of + ok -> + logger_config:set(Tid,proxy,Config); + Error -> + Error + end, + {reply,Reply,State}; +handle_call({change_config,SetOrUpd,primary,Config0}, _From, + #state{tid=Tid}=State) -> + {ok,#{handlers:=Handlers}=OldConfig} = logger_config:get(Tid,primary), + Default = + case SetOrUpd of + set -> default_config(primary); + update -> OldConfig + end, + Config = maps:merge(Default,Config0), + Reply = logger_config:set(Tid,primary,Config#{handlers=>Handlers}), + {reply,Reply,State}; +handle_call({change_config,_SetOrUpd,primary,Key,Value}, _From, + #state{tid=Tid}=State) -> {ok,OldConfig} = logger_config:get(Tid,primary), - Config = maps:merge(OldConfig,NewConfig), - {reply,logger_config:set(Tid,primary,Config),State}; -handle_call({update_config,HandlerId,NewConfig}, From, #state{tid=Tid}=State) -> + Reply = logger_config:set(Tid,primary,OldConfig#{Key=>Value}), + {reply,Reply,State}; +handle_call({change_config,SetOrUpd,HandlerId,Config0}, From, + #state{tid=Tid}=State) -> case logger_config:get(Tid,HandlerId) of {ok,#{module:=Module}=OldConfig} -> - Config = maps:merge(OldConfig,NewConfig), - call_h_async( - fun() -> - call_h(Module,changing_config,[OldConfig,Config], - {ok,Config}) - end, - fun({ok,Config1}) -> - logger_config:set(Tid,HandlerId,Config1); - (Error) -> - Error - end,From,State); - Error -> - {reply,Error,State} + Default = + case SetOrUpd of + set -> default_config(HandlerId,Module); + update -> OldConfig + end, + Config = maps:merge(Default,Config0), + case check_config_change(OldConfig,Config) of + ok -> + call_h_async( + fun() -> + call_h(Module,changing_config, + [SetOrUpd,OldConfig,Config], + {ok,Config}) + end, + fun({ok,Config1}) -> + logger_config:set(Tid,HandlerId,Config1); + (Error) -> + Error + end,From,State); + Error -> + {reply,Error,State} + end; + _ -> + {reply,{error,{not_found,HandlerId}},State} end; -handle_call({set_config,primary,Config0}, _From, #state{tid=Tid}=State) -> - Config = maps:merge(default_config(primary),Config0), - {ok,#{handlers:=Handlers}} = logger_config:get(Tid,primary), - Reply = logger_config:set(Tid,primary,Config#{handlers=>Handlers}), - {reply,Reply,State}; -handle_call({set_config,HandlerId,Config0}, From, #state{tid=Tid}=State) -> +handle_call({change_config,SetOrUpd,HandlerId,Key,Value}, From, + #state{tid=Tid}=State) -> case logger_config:get(Tid,HandlerId) of {ok,#{module:=Module}=OldConfig} -> - Config = maps:merge(default_config(HandlerId,Module),Config0), - call_h_async( - fun() -> - call_h(Module,changing_config,[OldConfig,Config], - {ok,Config}) - end, - fun({ok,Config1}) -> - logger_config:set(Tid,HandlerId,Config1); - (Error) -> - Error - end,From,State); + Config = OldConfig#{Key=>Value}, + case check_config_change(OldConfig,Config) of + ok -> + call_h_async( + fun() -> + call_h(Module,changing_config, + [SetOrUpd,OldConfig,Config], + {ok,Config}) + end, + fun({ok,Config1}) -> + logger_config:set(Tid,HandlerId,Config1); + (Error) -> + Error + end,From,State); + Error -> + {reply,Error,State} + end; _ -> {reply,{error,{not_found,HandlerId}},State} end; @@ -314,18 +376,19 @@ terminate(_Reason, _State) -> %%%=================================================================== %%% Internal functions %%%=================================================================== -call(Request) -> +call(Request) when is_tuple(Request) -> Action = element(1,Request), case get(?LOGGER_SERVER_TAG) of true when Action == add_handler; Action == remove_handler; Action == add_filter; Action == remove_filter; - Action == update_config; Action == set_config -> + Action == change_config -> {error,{attempting_syncronous_call_to_self,Request}}; _ -> gen_server:call(?SERVER,Request,?DEFAULT_LOGGER_CALL_TIMEOUT) end. + do_add_filter(Tid,Id,{FId,_} = Filter) -> case logger_config:get(Tid,Id) of {ok,Config} -> @@ -370,11 +433,13 @@ default_config(Id,Module) -> sanity_check(Owner,Key,Value) -> sanity_check_1(Owner,[{Key,Value}]). -sanity_check(HandlerId,Config) when is_map(Config) -> - sanity_check_1(HandlerId,maps:to_list(Config)); +sanity_check(Owner,Config) when is_map(Config) -> + sanity_check_1(Owner,maps:to_list(Config)); sanity_check(_,Config) -> {error,{invalid_config,Config}}. +sanity_check_1(proxy,_Config) -> + ok; % Details are checked by logger_olp:set_opts/2 sanity_check_1(Owner,Config) when is_list(Config) -> try Type = get_type(Owner), @@ -458,6 +523,15 @@ check_formatter({Mod,Config}) -> check_formatter(Formatter) -> throw({invalid_formatter,Formatter}). +%% When changing configuration for a handler, the id and module fields +%% can not be changed. +check_config_change(#{id:=Id,module:=Module},#{id:=Id,module:=Module}) -> + ok; +check_config_change(OldConfig,NewConfig) -> + {Old,New} = logger_server:diff_maps(maps:with([id,module],OldConfig), + maps:with([id,module],NewConfig)), + {error,{illegal_config_change,Old,New}}. + call_h(Module, Function, Args, DefRet) -> %% Not calling code:ensure_loaded + erlang:function_exported here, %% since in some rare terminal cases, the code_server might not @@ -466,6 +540,11 @@ call_h(Module, Function, Args, DefRet) -> catch C:R:S -> case {C,R,S} of + {error,undef,[{Module,Function=changing_config,Args,_}|_]} + when length(Args)=:=3 -> + %% Backwards compatible call, if changing_config/3 + %% did not exist. + call_h(Module, Function, tl(Args), DefRet); {error,undef,[{Module,Function,Args,_}|_]} -> DefRet; _ -> @@ -525,3 +604,14 @@ call_h_reply(Unexpected,State) -> {process,?SERVER}, {message,Unexpected}]), {noreply,State}. + +%% Return two maps containing only the fields that differ. +diff_maps(M1,M2) -> + diffs(lists:sort(maps:to_list(M1)),lists:sort(maps:to_list(M2)),#{},#{}). + +diffs([H|T1],[H|T2],D1,D2) -> + diffs(T1,T2,D1,D2); +diffs([{K,V1}|T1],[{K,V2}|T2],D1,D2) -> + diffs(T1,T2,D1#{K=>V1},D2#{K=>V2}); +diffs([],[],D1,D2) -> + {D1,D2}. diff --git a/lib/kernel/src/logger_simple_h.erl b/lib/kernel/src/logger_simple_h.erl index 8b51dd8569..fe181722f3 100644 --- a/lib/kernel/src/logger_simple_h.erl +++ b/lib/kernel/src/logger_simple_h.erl @@ -50,7 +50,6 @@ removing_handler(#{id:=simple}) -> ok; Pid -> Ref = erlang:monitor(process,Pid), - unlink(Pid), Pid ! stop, receive {'DOWN',Ref,process,Pid,_} -> ok @@ -99,7 +98,11 @@ loop(Buffer) -> replay_buffer(Buffer); _ -> ok - end; + end, + %% Before stopping, we unlink the logger process to avoid + %% an unexpected EXIT message + unlink(whereis(logger)), + ok; {log,#{msg:=_,meta:=#{time:=_}}=Log} -> do_log(Log), loop(update_buffer(Buffer,Log)); diff --git a/lib/kernel/src/logger_std_h.erl b/lib/kernel/src/logger_std_h.erl index 66fa6b6ab6..0669164bb6 100644 --- a/lib/kernel/src/logger_std_h.erl +++ b/lib/kernel/src/logger_std_h.erl @@ -19,8 +19,6 @@ %% -module(logger_std_h). --behaviour(gen_server). - -include("logger.hrl"). -include("logger_internal.hrl"). -include("logger_h_common.hrl"). @@ -28,185 +26,64 @@ -include_lib("kernel/include/file.hrl"). %% API --export([start_link/3, info/1, filesync/1, reset/1]). +-export([filesync/1]). -%% gen_server and proc_lib callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). +%% logger_h_common callbacks +-export([init/2, check_config/4, reset_state/2, + filesync/3, write/4, handle_info/3, terminate/3]). %% logger callbacks --export([log/2, adding_handler/1, removing_handler/1, changing_config/2]). +-export([log/2, adding_handler/1, removing_handler/1, changing_config/3, + filter_config/1]). -%% handler internal --export([log_handler_info/4]). +-define(DEFAULT_CALL_TIMEOUT, 5000). %%%=================================================================== %%% API %%%=================================================================== %%%----------------------------------------------------------------- -%%% Start a standard handler process and link to caller. -%%% This function is called by the kernel supervisor when this -%%% handler process gets added --spec start_link(Name, Config, HandlerState) -> {ok,Pid} | {error,Reason} when - Name :: atom(), - Config :: logger:handler_config(), - HandlerState :: map(), - Pid :: pid(), - Reason :: term(). - -start_link(Name, Config, HandlerState) -> - proc_lib:start_link(?MODULE,init,[[Name,Config,HandlerState]]). - -%%%----------------------------------------------------------------- %%% -spec filesync(Name) -> ok | {error,Reason} when Name :: atom(), Reason :: handler_busy | {badarg,term()}. -filesync(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - filesync, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; filesync(Name) -> - {error,{badarg,{filesync,[Name]}}}. - -%%%----------------------------------------------------------------- -%%% --spec info(Name) -> Info | {error,Reason} when - Name :: atom(), - Info :: term(), - Reason :: handler_busy | {badarg,term()}. - -info(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - info, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; -info(Name) -> - {error,{badarg,{info,[Name]}}}. - -%%%----------------------------------------------------------------- -%%% --spec reset(Name) -> ok | {error,Reason} when - Name :: atom(), - Reason :: handler_busy | {badarg,term()}. - -reset(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - reset, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; -reset(Name) -> - {error,{badarg,{reset,[Name]}}}. - + logger_h_common:filesync(?MODULE,Name). %%%=================================================================== -%%% logger callbacks +%%% logger callbacks - just forward to logger_h_common %%%=================================================================== %%%----------------------------------------------------------------- %%% Handler being added -adding_handler(#{id:=Name}=Config) -> - case check_config(adding, Config) of - {ok, Config1} -> - %% create initial handler state by merging defaults with config - HConfig = maps:get(config, Config1, #{}), - HState = maps:merge(get_init_state(), HConfig), - case logger_h_common:overload_levels_ok(HState) of - true -> - start(Name, Config1, HState); - false -> - #{sync_mode_qlen := SMQL, - drop_mode_qlen := DMQL, - flush_qlen := FQL} = HState, - {error,{invalid_levels,{SMQL,DMQL,FQL}}} - end; - Error -> - Error - end. +-spec adding_handler(Config) -> {ok,Config} | {error,Reason} when + Config :: logger:handler_config(), + Reason :: term(). + +adding_handler(Config) -> + logger_h_common:adding_handler(Config). %%%----------------------------------------------------------------- %%% Updating handler config -changing_config(OldConfig=#{id:=Name, config:=OldHConfig}, - NewConfig=#{id:=Name}) -> - #{type:=Type, handler_pid:=HPid, mode_tab:=ModeTab} = OldHConfig, - NewHConfig = maps:get(config, NewConfig, #{}), - case maps:get(type, NewHConfig, Type) of - Type -> - NewHConfig1 = NewHConfig#{type=>Type, - handler_pid=>HPid, - mode_tab=>ModeTab}, - changing_config1(HPid, OldConfig, - NewConfig#{config=>NewHConfig1}); - _ -> - {error,{illegal_config_change,OldConfig,NewConfig}} - end; -changing_config(OldConfig, NewConfig) -> - {error,{illegal_config_change,OldConfig,NewConfig}}. - -changing_config1(HPid, OldConfig, NewConfig) -> - case check_config(changing, NewConfig) of - Result = {ok,NewConfig1} -> - try gen_server:call(HPid, {change_config,OldConfig,NewConfig1}, - ?DEFAULT_CALL_TIMEOUT) of - ok -> Result; - HError -> HError - catch - _:{timeout,_} -> {error,handler_busy} - end; - Error -> - Error - end. - -check_config(adding, Config) -> - %% Merge in defaults on handler level - HConfig0 = maps:get(config, Config, #{}), - HConfig = maps:merge(#{type => standard_io}, - HConfig0), - case check_h_config(maps:to_list(HConfig)) of - ok -> - {ok,Config#{config=>HConfig}}; - Error -> - Error - end; -check_config(changing, Config) -> - HConfig = maps:get(config, Config, #{}), - case check_h_config(maps:to_list(HConfig)) of - ok -> {ok,Config}; - Error -> Error - end. - -check_h_config([{type,Type} | Config]) when Type == standard_io; - Type == standard_error -> - check_h_config(Config); -check_h_config([{type,{file,File}} | Config]) when is_list(File) -> - check_h_config(Config); -check_h_config([{type,{file,File,Modes}} | Config]) when is_list(File), - is_list(Modes) -> - check_h_config(Config); -check_h_config([Other | Config]) -> - case logger_h_common:check_common_config(Other) of - valid -> - check_h_config(Config); - invalid -> - {error,{invalid_config,?MODULE,Other}} - end; -check_h_config([]) -> - ok. +-spec changing_config(SetOrUpdate, OldConfig, NewConfig) -> + {ok,Config} | {error,Reason} when + SetOrUpdate :: set | update, + OldConfig :: logger:handler_config(), + NewConfig :: logger:handler_config(), + Config :: logger:handler_config(), + Reason :: term(). +changing_config(SetOrUpdate, OldConfig, NewConfig) -> + logger_h_common:changing_config(SetOrUpdate, OldConfig, NewConfig). %%%----------------------------------------------------------------- %%% Handler being removed -removing_handler(#{id:=Name}) -> - stop(Name). +-spec removing_handler(Config) -> ok when + Config :: logger:handler_config(). + +removing_handler(Config) -> + logger_h_common:removing_handler(Config). %%%----------------------------------------------------------------- %%% Log a string or report @@ -214,192 +91,103 @@ removing_handler(#{id:=Name}) -> LogEvent :: logger:log_event(), Config :: logger:handler_config(). -log(LogEvent, Config = #{id := Name, - config := #{handler_pid := HPid, - mode_tab := ModeTab}}) -> - %% if the handler has crashed, we must drop this event - %% and hope the handler restarts so we can try again - true = is_process_alive(HPid), - Bin = logger_h_common:log_to_binary(LogEvent, Config), - logger_h_common:call_cast_or_drop(Name, HPid, ModeTab, Bin). +log(LogEvent, Config) -> + logger_h_common:log(LogEvent, Config). -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== +%%%----------------------------------------------------------------- +%%% Remove internal fields from configuration +-spec filter_config(Config) -> Config when + Config :: logger:handler_config(). -init([Name, Config = #{config := HConfig}, - State0 = #{type := Type, file_ctrl_sync_int := FileCtrlSyncInt}]) -> - RegName = ?name_to_reg_name(?MODULE,Name), - register(RegName, self()), - process_flag(trap_exit, true), - process_flag(message_queue_data, off_heap), +filter_config(Config) -> + logger_h_common:filter_config(Config). - ?init_test_hooks(), - ?start_observation(Name), - - case do_init(Name, Type) of - {ok,InitState} -> - try ets:new(Name, [public]) of - ModeTab -> - ?set_mode(ModeTab, async), - State = maps:merge(State0, InitState), - T0 = ?timestamp(), - State1 = - ?merge_with_stats(State#{ - mode_tab => ModeTab, - mode => async, - file_ctrl_sync => FileCtrlSyncInt, - last_qlen => 0, - last_log_ts => T0, - last_op => sync, - burst_win_ts => T0, - burst_msg_count => 0}), - Config1 = - Config#{config => HConfig#{handler_pid => self(), - mode_tab => ModeTab}}, - proc_lib:init_ack({ok,self(),Config1}), - gen_server:cast(self(), repeated_filesync), - gen_server:enter_loop(?MODULE, [], State1) - catch - _:Error -> - unregister(RegName), - logger_h_common:error_notify({init_handler,Name,Error}), - proc_lib:init_ack(Error) - end; - Error -> - unregister(RegName), - logger_h_common:error_notify({init_handler,Name,Error}), - proc_lib:init_ack(Error) - end. - -do_init(Name, Type) -> +%%%=================================================================== +%%% logger_h_common callbacks +%%%=================================================================== +init(Name, #{type := Type}) -> case open_log_file(Name, Type) of {ok,FileCtrlPid} -> - case logger_h_common:unset_restart_flag(Name, ?MODULE) of - true -> - %% inform about restart - gen_server:cast(self(), {log_handler_info, - "Handler ~p restarted", - [Name]}); - false -> - %% initial start - ok - end, - {ok,#{id=>Name,type=>Type,file_ctrl_pid=>FileCtrlPid}}; + {ok,#{type=>Type,file_ctrl_pid=>FileCtrlPid}}; Error -> Error end. -%% This is the synchronous log event. -handle_call({log, Bin}, _From, State) -> - {Result,State1} = do_log(Bin, call, State), - %% Result == ok | dropped - {reply,Result, State1}; - -handle_call(filesync, _From, State = #{type := Type, - file_ctrl_pid := FileCtrlPid}) -> - if is_atom(Type) -> - {reply, ok, State}; - true -> - {reply, file_ctrl_filesync_sync(FileCtrlPid), State#{last_op=>sync}} - end; +check_config(_Name,set,undefined,NewHConfig) -> + check_config(maps:merge(get_default_config(),NewHConfig)); +check_config(_Name,SetOrUpdate,OldHConfig,NewHConfig0) -> + WriteOnce = maps:with([type],OldHConfig), + Default = + case SetOrUpdate of + set -> + %% Do not reset write-once fields to defaults + maps:merge(get_default_config(),WriteOnce); + update -> + OldHConfig + end, -handle_call({change_config,_OldConfig,NewConfig}, _From, - State = #{filesync_repeat_interval := FSyncInt0}) -> - HConfig = maps:get(config, NewConfig, #{}), - State1 = maps:merge(State, HConfig), - case logger_h_common:overload_levels_ok(State1) of - true -> - _ = - case maps:get(filesync_repeat_interval, HConfig, undefined) of - undefined -> - ok; - no_repeat -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, - State, - undefined)); - FSyncInt0 -> - ok; - _FSyncInt1 -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, - State, - undefined)), - gen_server:cast(self(), repeated_filesync) - end, - {reply, ok, State1}; - false -> - #{sync_mode_qlen := SMQL, - drop_mode_qlen := DMQL, - flush_qlen := FQL} = State1, - {reply, {error,{invalid_levels,{SMQL,DMQL,FQL}}}, State} - end; + NewHConfig = maps:merge(Default, NewHConfig0), -handle_call(info, _From, State) -> - {reply, State, State}; - -handle_call(reset, _From, State) -> - State1 = ?merge_with_stats(State), - {reply, ok, State1#{last_qlen => 0, - last_log_ts => ?timestamp()}}; - -handle_call(stop, _From, State) -> - {stop, {shutdown,stopped}, ok, State}. - -%% This is the asynchronous log event. -handle_cast({log, Bin}, State) -> - {_,State1} = do_log(Bin, cast, State), - {noreply, State1}; - -handle_cast({log_handler_info, Format, Args}, State = #{id:=Name}) -> - log_handler_info(Name, Format, Args, State), - {noreply, State}; - -%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this -%% clause gets called repeatedly by the handler. In order to -%% guarantee that a filesync *always* happens after the last log -%% event, the repeat operation must be active! -handle_cast(repeated_filesync, - State = #{type := Type, - file_ctrl_pid := FileCtrlPid, - filesync_repeat_interval := FSyncInt, - last_op := LastOp}) -> - State1 = - if not is_atom(Type), is_integer(FSyncInt) -> - %% only do filesync if something has been - %% written since last time we checked - if LastOp == sync -> - ok; - true -> - file_ctrl_filesync_async(FileCtrlPid) - end, - {ok,TRef} = - timer:apply_after(FSyncInt, gen_server,cast, - [self(),repeated_filesync]), - State#{rep_sync_tref => TRef, last_op => sync}; - true -> - State - end, - {noreply,State1}. - -handle_info({'EXIT',Pid,Why}, State = #{id := Name, type := FileInfo}) -> - case maps:get(file_ctrl_pid, State, undefined) of - Pid -> - %% file error, terminate handler - logger_h_common:handler_exit(Name, - {error,{write_failed,FileInfo,Why}}); - _Other -> - %% ignore EXIT - ok - end, - {noreply, State}; + %% Fail if write-once fields are changed + case maps:with([type],NewHConfig) of + WriteOnce -> + check_config(NewHConfig); + Other -> + {error,{illegal_config_change,?MODULE,WriteOnce,Other}} + end. -handle_info(_Info, State) -> - {noreply, State}. +check_config(#{type:=Type}=HConfig) -> + case check_h_config(maps:to_list(HConfig)) of + ok when is_atom(Type) -> + {ok,HConfig#{filesync_repeat_interval=>no_repeat}}; + ok -> + {ok,HConfig}; + {error,{Key,Value}} -> + {error,{invalid_config,?MODULE,#{Key=>Value}}} + end. -terminate(Reason, State = #{id:=Name, file_ctrl_pid:=FWPid, - type:=_FileInfo}) -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, State, - undefined)), +check_h_config([{type,Type} | Config]) when Type == standard_io; + Type == standard_error -> + check_h_config(Config); +check_h_config([{type,{file,File}} | Config]) when is_list(File) -> + check_h_config(Config); +check_h_config([{type,{file,File,Modes}} | Config]) when is_list(File), + is_list(Modes) -> + check_h_config(Config); +check_h_config([Other | _]) -> + {error,Other}; +check_h_config([]) -> + ok. + +get_default_config() -> + #{type => standard_io}. + +filesync(_Name, _Mode, #{type := Type}=State) when is_atom(Type) -> + {ok,State}; +filesync(_Name, async, #{file_ctrl_pid := FileCtrlPid} = State) -> + ok = file_ctrl_filesync_async(FileCtrlPid), + {ok,State}; +filesync(_Name, sync, #{file_ctrl_pid := FileCtrlPid} = State) -> + Result = file_ctrl_filesync_sync(FileCtrlPid), + {Result,State}. + +write(_Name, async, Bin, #{file_ctrl_pid:=FileCtrlPid} = State) -> + ok = file_write_async(FileCtrlPid, Bin), + {ok,State}; +write(_Name, sync, Bin, #{file_ctrl_pid:=FileCtrlPid} = State) -> + Result = file_write_sync(FileCtrlPid, Bin), + {Result,State}. + +reset_state(_Name, State) -> + State. + +handle_info(_Name, {'EXIT',Pid,Why}, #{type := FileInfo, file_ctrl_pid := Pid}) -> + %% file_ctrl_pid died, file error, terminate handler + exit({error,{write_failed,FileInfo,Why}}); +handle_info(_, _, State) -> + State. + +terminate(_Name, _Reason, #{file_ctrl_pid:=FWPid}) -> case is_process_alive(FWPid) of true -> unlink(FWPid), @@ -410,17 +198,12 @@ terminate(Reason, State = #{id:=Name, file_ctrl_pid:=FWPid, ok after ?DEFAULT_CALL_TIMEOUT -> - exit(FWPid, kill) + exit(FWPid, kill), + ok end; false -> ok - end, - ok = logger_h_common:stop_or_restart(Name, Reason, State), - unregister(?name_to_reg_name(?MODULE, Name)), - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. + end. %%%=================================================================== %%% Internal functions @@ -428,200 +211,6 @@ code_change(_OldVsn, State, _Extra) -> %%%----------------------------------------------------------------- %%% -get_init_state() -> - #{sync_mode_qlen => ?SYNC_MODE_QLEN, - drop_mode_qlen => ?DROP_MODE_QLEN, - flush_qlen => ?FLUSH_QLEN, - burst_limit_enable => ?BURST_LIMIT_ENABLE, - burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT, - burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME, - overload_kill_enable => ?OVERLOAD_KILL_ENABLE, - overload_kill_qlen => ?OVERLOAD_KILL_QLEN, - overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE, - overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER, - file_ctrl_sync_int => ?CONTROLLER_SYNC_INTERVAL, - filesync_ok_qlen => ?FILESYNC_OK_QLEN, - filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}. - -%%%----------------------------------------------------------------- -%%% Add a standard handler to the logger. -%%% This starts a dedicated handler process which should always -%%% exist if the handler is registered with logger (and should not -%%% exist if the handler is not registered). -%%% -%%% Handler specific config should be provided with a sub map associated -%%% with a key named 'config', e.g: -%%% -%%% Config = #{config => #{sync_mode_qlen => 50} -%%% -%%% The standard handler process is linked to logger_sup, which is -%%% part of the kernel application's supervision tree. -start(Name, Config, HandlerState) -> - LoggerStdH = - #{id => Name, - start => {?MODULE, start_link, [Name,Config,HandlerState]}, - restart => temporary, - shutdown => 2000, - type => worker, - modules => [?MODULE]}, - case supervisor:start_child(logger_sup, LoggerStdH) of - {ok,Pid,Config1} -> - ok = logger_handler_watcher:register_handler(Name,Pid), - {ok,Config1}; - Error -> - Error - end. - -%%%----------------------------------------------------------------- -%%% Stop and remove the handler. -stop(Name) -> - case whereis(?name_to_reg_name(?MODULE,Name)) of - undefined -> - ok; - Pid -> - %% We don't want to do supervisor:terminate_child here - %% since we need to distinguish this explicit stop from a - %% system termination in order to avoid circular attempts - %% at removing the handler (implying deadlocks and - %% timeouts). - %% And we don't need to do supervisor:delete_child, since - %% the restart type is temporary, which means that the - %% child specification is automatically removed from the - %% supervisor when the process dies. - _ = gen_server:call(Pid, stop), - ok - end. - -%%%----------------------------------------------------------------- -%%% Logging and overload control. --define(update_file_ctrl_sync(C, Interval), - if C == 0 -> Interval; - true -> C-1 end). - -%% check for overload between every event (and set Mode to async, -%% sync or drop accordingly), but never flush the whole mailbox -%% before LogWindowSize events have been handled -do_log(Bin, CallOrCast, State = #{id:=Name, mode:=Mode0}) -> - T1 = ?timestamp(), - - %% check if the handler is getting overloaded, or if it's - %% recovering from overload (the check must be done for each - %% event to react quickly to large bursts of events and - %% to ensure that the handler can never end up in drop mode - %% with an empty mailbox, which would stop operation) - {Mode1,QLen,Mem,State1} = logger_h_common:check_load(State), - - if (Mode1 == drop) andalso (Mode0 =/= drop) -> - log_handler_info(Name, "Handler ~p switched to drop mode", - [Name], State); - (Mode0 == drop) andalso ((Mode1 == async) orelse (Mode1 == sync)) -> - log_handler_info(Name, "Handler ~p switched to ~w mode", - [Name,Mode1], State); - true -> - ok - end, - - %% kill the handler if it can't keep up with the load - logger_h_common:kill_if_choked(Name, QLen, Mem, ?MODULE, State), - - if Mode1 == flush -> - flush(Name, QLen, T1, State1); - true -> - write(Name, Mode1, T1, Bin, CallOrCast, State1) - end. - -%% this clause is called by do_log/3 after an overload check -%% has been performed, where QLen > FlushQLen -flush(Name, _QLen0, T1, State=#{last_log_ts := _T0, mode_tab := ModeTab}) -> - %% flush messages in the mailbox (a limited number in - %% order to not cause long delays) - NewFlushed = logger_h_common:flush_log_events(?FLUSH_MAX_N), - - %% write info in log about flushed messages - log_handler_info(Name, "Handler ~p flushed ~w log events", - [Name,NewFlushed], State), - - %% because of the receive loop when flushing messages, the - %% handler will be scheduled out often and the mailbox could - %% grow very large, so we'd better check the queue again here - {_,_QLen1} = process_info(self(), message_queue_len), - ?observe(Name,{max_qlen,_QLen1}), - - %% Add 1 for the current log event - ?observe(Name,{flushed,NewFlushed+1}), - - State1 = ?update_max_time(?diff_time(T1,_T0),State), - {dropped,?update_other(flushed,FLUSHED,NewFlushed, - State1#{mode => ?set_mode(ModeTab,async), - last_qlen => 0, - last_log_ts => T1})}. - -%% this clause is called to write to file -write(_Name, Mode, T1, Bin, _CallOrCast, - State = #{mode_tab := ModeTab, - file_ctrl_pid := FileCtrlPid, - file_ctrl_sync := FileCtrlSync, - last_qlen := LastQLen, - last_log_ts := T0, - file_ctrl_sync_int := FileCtrlSyncInt}) -> - %% check if we need to limit the number of writes - %% during a burst of log events - {DoWrite,BurstWinT,BurstMsgCount} = logger_h_common:limit_burst(State), - - %% only send a synhrounous event to the file controller process - %% every FileCtrlSyncInt time, to give the handler time between - %% file writes so it can keep up with incoming messages - {Result,LastQLen1} = - if DoWrite, FileCtrlSync == 0 -> - ?observe(_Name,{_CallOrCast,1}), - file_write_sync(FileCtrlPid, Bin, false), - {ok,element(2, process_info(self(), message_queue_len))}; - DoWrite -> - ?observe(_Name,{_CallOrCast,1}), - file_write_async(FileCtrlPid, Bin), - {ok,LastQLen}; - not DoWrite -> - ?observe(_Name,{flushed,1}), - {dropped,LastQLen} - end, - - %% Check if the time since the previous log event is long enough - - %% and the queue length small enough - to assume the mailbox has - %% been emptied, and if so, do filesync operation and reset mode to - %% async. Note that this is the best we can do to detect an idle - %% handler without setting a timer after each log call/cast. If the - %% time between two consecutive log events is fast and no new - %% event comes in after the last one, idle state won't be detected! - Time = ?diff_time(T1,T0), - {Mode1,BurstMsgCount1} = - if (LastQLen1 < ?FILESYNC_OK_QLEN) andalso - (Time > ?IDLE_DETECT_TIME_USEC) -> - %% do filesync if necessary - case maps:get(type, State) of - Std when is_atom(Std) -> - ok; - _File -> - file_ctrl_filesync_async(FileCtrlPid) - end, - {?change_mode(ModeTab, Mode, async),0}; - true -> - {Mode,BurstMsgCount} - end, - State1 = - ?update_calls_or_casts(_CallOrCast,1,State), - State2 = - ?update_max_time(Time, - State1#{mode => Mode1, - last_qlen := LastQLen1, - last_log_ts => T1, - last_op => write, - burst_win_ts => BurstWinT, - burst_msg_count => BurstMsgCount1, - file_ctrl_sync => - ?update_file_ctrl_sync(FileCtrlSync, - FileCtrlSyncInt)}), - {Result,State2}. - open_log_file(HandlerName, FileInfo) -> case file_ctrl_start(HandlerName, FileInfo) of OK = {ok,_FileCtrlPid} -> OK; @@ -653,19 +242,6 @@ close_log_file(Fd) -> _ = file:close(Fd). -log_handler_info(Name, Format, Args, #{file_ctrl_pid := FileCtrlPid}) -> - Config = - case logger:get_handler_config(Name) of - {ok,Conf} -> Conf; - _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}} - end, - Meta = #{time=>erlang:system_time(microsecond)}, - Bin = logger_h_common:log_to_binary(#{level => notice, - msg => {Format,Args}, - meta => Meta}, Config), - _ = file_write_async(FileCtrlPid, Bin), - ok. - %%%----------------------------------------------------------------- %%% File control process @@ -692,24 +268,19 @@ file_write_async(Pid, Bin) -> Pid ! {log,Bin}, ok. -file_write_sync(Pid, Bin, FileSync) -> - case file_ctrl_call(Pid, {log,self(),Bin,FileSync}) of - {error,Reason} -> - {error,{write_failed,Bin,Reason}}; - Result -> - Result - end. +file_write_sync(Pid, Bin) -> + file_ctrl_call(Pid, {log,Bin}). file_ctrl_filesync_async(Pid) -> Pid ! filesync, ok. file_ctrl_filesync_sync(Pid) -> - file_ctrl_call(Pid, {filesync,self()}). + file_ctrl_call(Pid, filesync). file_ctrl_call(Pid, Msg) -> MRef = monitor(process, Pid), - Pid ! {Msg,MRef}, + Pid ! {Msg,{self(),MRef}}, receive {MRef,Result} -> demonitor(MRef, [flush]), @@ -727,74 +298,43 @@ file_ctrl_init(HandlerName, FileInfo, Starter) when is_tuple(FileInfo) -> case do_open_log_file(FileInfo) of {ok,Fd} -> Starter ! {self(),ok}, - file_ctrl_loop(Fd, file, FileName, false, ok, ok, HandlerName); + file_ctrl_loop(Fd, FileName, false, ok, ok, HandlerName); {error,Reason} -> Starter ! {self(),{error,{open_failed,FileName,Reason}}} end; file_ctrl_init(HandlerName, StdDev, Starter) -> Starter ! {self(),ok}, - file_ctrl_loop(StdDev, standard_io, StdDev, false, ok, ok, HandlerName). + file_ctrl_loop(StdDev, StdDev, false, ok, ok, HandlerName). -file_ctrl_loop(Fd, Type, DevName, Synced, +file_ctrl_loop(Fd, DevName, Synced, PrevWriteResult, PrevSyncResult, HandlerName) -> receive %% asynchronous event {log,Bin} -> - Result = if Type == file -> - write_to_dev(Fd, Bin, DevName, - PrevWriteResult, HandlerName); - true -> - io:put_chars(Fd, Bin) - end, - file_ctrl_loop(Fd, Type, DevName, false, + Fd1 = ensure(Fd, DevName), + Result = write_to_dev(Fd1, Bin, DevName, PrevWriteResult, HandlerName), + file_ctrl_loop(Fd1, DevName, false, Result, PrevSyncResult, HandlerName); %% synchronous event - {{log,From,Bin,FileSync},MRef} -> - if Type == file -> - %% check that file hasn't been deleted - CheckFile = - fun() -> {ok,_} = file:read_file_info(DevName) end, - spawn_link(CheckFile), - WResult = write_to_dev(Fd, Bin, DevName, - PrevWriteResult, HandlerName), - {Synced1,SResult} = - if not FileSync -> - {false,PrevSyncResult}; - true -> - case sync_dev(Fd, DevName, - PrevSyncResult, HandlerName) of - ok -> {true,ok}; - Error -> {false,Error} - end - end, - From ! {MRef,ok}, - file_ctrl_loop(Fd, Type, DevName, Synced1, - WResult, SResult, HandlerName); - true -> - _ = io:put_chars(Fd, Bin), - From ! {MRef,ok}, - file_ctrl_loop(Fd, Type, DevName, false, - ok, PrevSyncResult, HandlerName) - end; + {{log,Bin},{From,MRef}} -> + Fd1 = ensure(Fd, DevName), + Result = write_to_dev(Fd1, Bin, DevName, PrevWriteResult, HandlerName), + From ! {MRef,ok}, + file_ctrl_loop(Fd1, DevName, false, + Result, PrevSyncResult, HandlerName); - filesync when not Synced -> - Result = sync_dev(Fd, DevName, PrevSyncResult, HandlerName), - file_ctrl_loop(Fd, Type, DevName, true, + filesync -> + Fd1 = ensure(Fd, DevName), + Result = sync_dev(Fd1, DevName, Synced, PrevSyncResult, HandlerName), + file_ctrl_loop(Fd1, DevName, true, PrevWriteResult, Result, HandlerName); - filesync -> - file_ctrl_loop(Fd, Type, DevName, true, - PrevWriteResult, PrevSyncResult, HandlerName); - - {{filesync,From},MRef} -> - Result = if not Synced -> - sync_dev(Fd, DevName, PrevSyncResult, HandlerName); - true -> - ok - end, + {filesync,{From,MRef}} -> + Fd1 = ensure(Fd, DevName), + Result = sync_dev(Fd1, DevName, Synced, PrevSyncResult, HandlerName), From ! {MRef,ok}, - file_ctrl_loop(Fd, Type, DevName, true, + file_ctrl_loop(Fd1, DevName, true, PrevWriteResult, Result, HandlerName); stop -> @@ -802,27 +342,44 @@ file_ctrl_loop(Fd, Type, DevName, Synced, stopped end. -write_to_dev(Fd, Bin, FileName, PrevWriteResult, HandlerName) -> - case ?file_write(Fd, Bin) of - ok -> - ok; - PrevWriteResult -> - %% don't report same error twice - PrevWriteResult; - Error -> - logger_h_common:error_notify({HandlerName,write,FileName,Error}), - Error +%% In order to play well with tools like logrotate, we need to be able +%% to re-create the file if it has disappeared (e.g. if rotated by +%% logrotate) +ensure(Fd,DevName) when is_atom(DevName) -> + Fd; +ensure(Fd,FileName) -> + case file:read_file_info(FileName) of + {ok,_} -> + Fd; + _ -> + _ = file:close(Fd), + _ = file:close(Fd), % delayed_write cause close not to close + case do_open_log_file({file,FileName}) of + {ok,Fd1} -> + Fd1; + Error -> + exit({could_not_reopen_file,Error}) + end end. -sync_dev(Fd, DevName, PrevSyncResult, HandlerName) -> - case ?file_datasync(Fd) of - ok -> - ok; - PrevSyncResult -> - %% don't report same error twice - PrevSyncResult; - Error -> - logger_h_common:error_notify({HandlerName,filesync,DevName,Error}), - Error - end. +write_to_dev(DevName, Bin, _DevName, _PrevWriteResult, _HandlerName) + when is_atom(DevName) -> + io:put_chars(DevName, Bin); +write_to_dev(Fd, Bin, FileName, PrevWriteResult, HandlerName) -> + Result = ?file_write(Fd, Bin), + maybe_notify_error(write,Result,PrevWriteResult,FileName,HandlerName). +sync_dev(_Fd, _FileName, true, PrevSyncResult, _HandlerName) -> + PrevSyncResult; +sync_dev(Fd, FileName, false, PrevSyncResult, HandlerName) -> + Result = ?file_datasync(Fd), + maybe_notify_error(filesync,Result,PrevSyncResult,FileName,HandlerName). + +maybe_notify_error(_Op, ok, _PrevResult, _FileName, _HandlerName) -> + ok; +maybe_notify_error(_Op, PrevResult, PrevResult, _FileName, _HandlerName) -> + %% don't report same error twice + PrevResult; +maybe_notify_error(Op, Error, _PrevResult, FileName, HandlerName) -> + logger_h_common:error_notify({HandlerName,Op,FileName,Error}), + Error. diff --git a/lib/kernel/src/logger_sup.erl b/lib/kernel/src/logger_sup.erl index 3d6f482e20..9ea8558a16 100644 --- a/lib/kernel/src/logger_sup.erl +++ b/lib/kernel/src/logger_sup.erl @@ -50,7 +50,9 @@ init([]) -> start => {logger_handler_watcher, start_link, []}, shutdown => brutal_kill}, - {ok, {SupFlags, [Watcher]}}. + Proxy = logger_proxy:child_spec(), + + {ok, {SupFlags, [Watcher,Proxy]}}. %%%=================================================================== %%% Internal functions diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl index ef92f9f4d1..4915193196 100644 --- a/lib/kernel/src/net_kernel.erl +++ b/lib/kernel/src/net_kernel.erl @@ -369,11 +369,11 @@ do_auto_connect_1(Node, ConnId, From, State) -> end. do_auto_connect_2(Node, passive_cnct, From, State, ConnLookup) -> - case (catch erts_internal:new_connection(Node)) of - {Nr,_DHandle}=ConnId when is_integer(Nr) -> - do_auto_connect_2(Node, ConnId, From, State, ConnLookup); - - _Error -> + try erts_internal:new_connection(Node) of + ConnId -> + do_auto_connect_2(Node, ConnId, From, State, ConnLookup) + catch + _:_ -> error_logger:error_msg("~n** Cannot get connection id for node ~w~n", [Node]), {reply, false, State} @@ -406,7 +406,7 @@ do_auto_connect_2(Node, ConnId, From, State, ConnLookup) -> erts_internal:abort_connection(Node, ConnId), {reply, false, State}; _ -> - case setup(ConnLookup, Node,ConnId,normal,From,State) of + case setup(Node, ConnId, normal, From, State) of {ok, SetupPid} -> Owners = [{SetupPid, Node} | State#state.conn_owners], {noreply,State#state{conn_owners=Owners}}; @@ -430,8 +430,8 @@ do_explicit_connect([#connection{conn_id = ConnId}=Conn], _, _, ConnId, From, St do_explicit_connect([#barred_connection{}], Type, Node, ConnId, From , State) -> %% Barred connection only affects auto_connect, ignore it. do_explicit_connect([], Type, Node, ConnId, From , State); -do_explicit_connect(ConnLookup, Type, Node, ConnId, From , State) -> - case setup(ConnLookup, Node,ConnId,Type,From,State) of +do_explicit_connect(_ConnLookup, Type, Node, ConnId, From , State) -> + case setup(Node,ConnId,Type,From,State) of {ok, SetupPid} -> Owners = [{SetupPid, Node} | State#state.conn_owners], {noreply,State#state{conn_owners=Owners}}; @@ -440,18 +440,6 @@ do_explicit_connect(ConnLookup, Type, Node, ConnId, From , State) -> {reply, false, State} end. --define(ERTS_DIST_CON_ID_MASK, 16#ffffff). % also in external.h - -verify_new_conn_id([], {Nr,_DHandle}) - when (Nr band (bnot ?ERTS_DIST_CON_ID_MASK)) =:= 0 -> - true; -verify_new_conn_id([#connection{conn_id = {Old,_}}], {New,_}) - when New =:= ((Old+1) band ?ERTS_DIST_CON_ID_MASK) -> - true; -verify_new_conn_id(_, _) -> - false. - - %% ------------------------------------------------------------ %% handle_call. @@ -477,8 +465,8 @@ handle_call({connect, _, Node}, From, State) when Node =:= node() -> handle_call({connect, Type, Node}, From, State) -> verbose({connect, Type, Node}, 1, State), ConnLookup = ets:lookup(sys_dist, Node), - R = case (catch erts_internal:new_connection(Node)) of - {Nr,_DHandle}=ConnId when is_integer(Nr) -> + R = try erts_internal:new_connection(Node) of + ConnId -> R1 = do_explicit_connect(ConnLookup, Type, Node, ConnId, From, State), case R1 of {reply, true, _S} -> %% already connected @@ -488,9 +476,10 @@ handle_call({connect, Type, Node}, From, State) -> {reply, false, _S} -> %% connection refused erts_internal:abort_connection(Node, ConnId) end, - R1; + R1 - _Error -> + catch + _:_ -> error_logger:error_msg("~n** Cannot get connection id for node ~w~n", [Node]), {reply, false, State} @@ -708,9 +697,9 @@ terminate(_Reason, State) -> %% %% Asynchronous auto connect request %% -handle_info({auto_connect,Node, Nr, DHandle}, State) -> - verbose({auto_connect, Node, Nr, DHandle}, 1, State), - ConnId = {Nr, DHandle}, +handle_info({auto_connect,Node, DHandle}, State) -> + verbose({auto_connect, Node, DHandle}, 1, State), + ConnId = DHandle, NewState = case do_auto_connect_1(Node, ConnId, noreply, State) of {noreply, S} -> %% Pending connection @@ -804,8 +793,8 @@ handle_info({AcceptPid, {accept_pending,MyNode,Node,Address,Type}}, State) -> AcceptPid ! {self(), {accept_pending, already_pending}}, {noreply, State}; _ -> - case (catch erts_internal:new_connection(Node)) of - {Nr,_DHandle}=ConnId when is_integer(Nr) -> + try erts_internal:new_connection(Node) of + ConnId -> ets:insert(sys_dist, #connection{node = Node, conn_id = ConnId, state = pending, @@ -814,12 +803,13 @@ handle_info({AcceptPid, {accept_pending,MyNode,Node,Address,Type}}, State) -> type = Type}), AcceptPid ! {self(),{accept_pending,ok}}, Owners = [{AcceptPid,Node} | State#state.conn_owners], - {noreply, State#state{conn_owners = Owners}}; - - _ -> + {noreply, State#state{conn_owners = Owners}} + catch + _:_ -> error_logger:error_msg("~n** Cannot get connection id for node ~w~n", [Node]), - AcceptPid ! {self(),{accept_pending,nok_pending}} + AcceptPid ! {self(),{accept_pending,nok_pending}}, + {noreply, State} end end; @@ -1283,8 +1273,8 @@ spawn_func(_,{From,Tag},M,F,A,Gleader) -> %% Set up connection to a new node. %% ----------------------------------------------------------- -setup(ConnLookup, Node,ConnId,Type,From,State) -> - case setup_check(ConnLookup, Node, ConnId, State) of +setup(Node, ConnId, Type, From, State) -> + case setup_check(Node, State) of {ok, L} -> Mod = L#listen.module, LAddr = L#listen.address, @@ -1313,7 +1303,7 @@ setup(ConnLookup, Node,ConnId,Type,From,State) -> Error end. -setup_check(ConnLookup, Node, ConnId, State) -> +setup_check(Node, State) -> Allowed = State#state.allowed, case lists:member(Node, Allowed) of false when Allowed =/= [] -> @@ -1321,16 +1311,9 @@ setup_check(ConnLookup, Node, ConnId, State) -> "disallowed node ~w ** ~n", [Node]), {error, bad_node}; _ -> - case verify_new_conn_id(ConnLookup, ConnId) of - false -> - error_msg("** Connection attempt to ~w with " - "bad connection id ~w ** ~n", [Node, ConnId]), - {error, bad_conn_id}; - true -> - case select_mod(Node, State#state.listen) of - {ok, _L}=OK -> OK; - Error -> Error - end + case select_mod(Node, State#state.listen) of + {ok, _L}=OK -> OK; + Error -> Error end end. @@ -1450,7 +1433,7 @@ validate_hostname([$@|HostPart] = Host) -> end. valid_name_head(Head) -> - {ok, MP} = re:compile("^[0-9A-Za-z_\\-]*$", [unicode]), + {ok, MP} = re:compile("^[0-9A-Za-z_\\-]+$", [unicode]), case re:run(Head, MP) of {match, _} -> true; diff --git a/lib/kernel/src/seq_trace.erl b/lib/kernel/src/seq_trace.erl index 14fe21e9de..4f9d7b3e5c 100644 --- a/lib/kernel/src/seq_trace.erl +++ b/lib/kernel/src/seq_trace.erl @@ -98,7 +98,7 @@ print(Label, Term) -> -spec reset_trace() -> 'true'. reset_trace() -> - erlang:system_flag(1, 0). + erlang:system_flag(reset_seq_trace, true). %% reset_trace(Pid) -> % this might be a useful function too diff --git a/lib/kernel/src/standard_error.erl b/lib/kernel/src/standard_error.erl index 5d649e5f94..ef5b532960 100644 --- a/lib/kernel/src/standard_error.erl +++ b/lib/kernel/src/standard_error.erl @@ -27,7 +27,8 @@ -define(PROCNAME_SUP, standard_error_sup). %% Defines for control ops --define(CTRL_OP_GET_WINSIZE,100). +-define(ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER, 16#018b0900). +-define(CTRL_OP_GET_WINSIZE, (100 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)). %% %% The basic server and start-up. diff --git a/lib/kernel/src/user.erl b/lib/kernel/src/user.erl index 872e63ab53..0c9e1ea303 100644 --- a/lib/kernel/src/user.erl +++ b/lib/kernel/src/user.erl @@ -28,7 +28,8 @@ -define(NAME, user). %% Defines for control ops --define(CTRL_OP_GET_WINSIZE,100). +-define(ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER, 16#018b0900). +-define(CTRL_OP_GET_WINSIZE, (100 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)). %% %% The basic server and start-up. diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl index 9f914aa222..08286dd476 100644 --- a/lib/kernel/src/user_drv.erl +++ b/lib/kernel/src/user_drv.erl @@ -32,9 +32,10 @@ -define(OP_BEEP,4). -define(OP_PUTC_SYNC,5). % Control op --define(CTRL_OP_GET_WINSIZE,100). --define(CTRL_OP_GET_UNICODE_STATE,101). --define(CTRL_OP_SET_UNICODE_STATE,102). +-define(ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER, 16#018b0900). +-define(CTRL_OP_GET_WINSIZE, (100 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)). +-define(CTRL_OP_GET_UNICODE_STATE, (101 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)). +-define(CTRL_OP_SET_UNICODE_STATE, (102 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)). %% start() %% start(ArgumentList) diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index 8b16e83707..d203597fc2 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -76,8 +76,11 @@ MODULES= \ logger_filters_SUITE \ logger_formatter_SUITE \ logger_legacy_SUITE \ + logger_olp_SUITE \ + logger_proxy_SUITE \ logger_simple_h_SUITE \ logger_std_h_SUITE \ + logger_stress_SUITE \ logger_test_lib \ os_SUITE \ pg2_SUITE \ diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl index 1314316c13..64e0b9d8dd 100644 --- a/lib/kernel/test/code_SUITE.erl +++ b/lib/kernel/test/code_SUITE.erl @@ -525,7 +525,7 @@ upgrade(Config) -> T = [beam, hipe], [upgrade_do(DataDir, Client, T) || Client <- T], - case hipe:llvm_support_available() of + case hipe:erllvm_is_supported() of false -> ok; true -> T2 = [beam, hipe_llvm], @@ -1021,6 +1021,13 @@ mult_lib_remove_prefix([H|T1], [H|T2]) -> mult_lib_remove_prefix([$/|T], []) -> T. bad_erl_libs(Config) when is_list(Config) -> + %% Preserve ERL_LIBS if set. + BadLibs0 = "/no/such/dir", + BadLibs = + case os:getenv("ERL_LIBS") of + false -> BadLibs0; + Libs -> BadLibs0 ++ ":" ++ Libs + end, {ok,Node} = test_server:start_node(bad_erl_libs, slave, []), Code = rpc:call(Node,code,get_path,[]), @@ -1028,10 +1035,9 @@ bad_erl_libs(Config) when is_list(Config) -> {ok,Node2} = test_server:start_node(bad_erl_libs, slave, - [{args,"-env ERL_LIBS /no/such/dir"}]), + [{args,"-env ERL_LIBS " ++ BadLibs}]), Code2 = rpc:call(Node,code,get_path,[]), test_server:stop_node(Node2), - %% Test that code path is not affected by the faulty ERL_LIBS Code = Code2, diff --git a/lib/kernel/test/erl_distribution_SUITE.erl b/lib/kernel/test/erl_distribution_SUITE.erl index 0e13f0383e..5a8bbd56c4 100644 --- a/lib/kernel/test/erl_distribution_SUITE.erl +++ b/lib/kernel/test/erl_distribution_SUITE.erl @@ -89,6 +89,7 @@ init_per_suite(Config) -> Config. end_per_suite(_Config) -> + [slave:stop(N) || N <- nodes()], ok. init_per_group(_GroupName, Config) -> diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl index e784c06865..a51025cba6 100644 --- a/lib/kernel/test/file_SUITE.erl +++ b/lib/kernel/test/file_SUITE.erl @@ -100,7 +100,7 @@ -export([unicode_mode/1]). --export([volume_relative_paths/1]). +-export([volume_relative_paths/1,unc_paths/1]). -export([tiny_writes/1, tiny_writes_delayed/1, large_writes/1, large_writes_delayed/1, @@ -129,7 +129,7 @@ suite() -> all() -> [unicode, altname, read_write_file, {group, dirs}, - {group, files}, delete, rename, names, volume_relative_paths, + {group, files}, delete, rename, names, volume_relative_paths, unc_paths, {group, errors}, {group, compression}, {group, links}, copy, delayed_write, read_ahead, segment_read, segment_write, ipread, pid2name, interleaved_read_write, otp_5814, otp_10852, @@ -2182,6 +2182,30 @@ volume_relative_paths(Config) when is_list(Config) -> {skip, "This test is Windows-specific."} end. +unc_paths(Config) when is_list(Config) -> + case os:type() of + {win32, _} -> + %% We assume administrative shares are set up and reachable, and we + %% settle for testing presence as some of the returned data is + %% different. + {ok, _} = file:read_file_info("C:\\Windows\\explorer.exe"), + {ok, _} = file:read_file_info("\\\\localhost\\c$\\Windows\\explorer.exe"), + + {ok, Cwd} = file:get_cwd(), + + try + ok = file:set_cwd("\\\\localhost\\c$\\Windows\\"), + {ok, _} = file:read_file_info("explorer.exe") + after + file:set_cwd(Cwd) + end, + + [] = flush(), + ok; + _ -> + {skip, "This test is Windows-specific."} + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2210,7 +2234,8 @@ e_delete(Config) when is_list(Config) -> case os:type() of {win32, _} -> %% Remove a character device. - {error, eacces} = ?FILE_MODULE:delete("nul"); + expect({error, eacces}, {error, einval}, + ?FILE_MODULE:delete("nul")); _ -> ?FILE_MODULE:write_file_info( Base, #file_info {mode=0}), diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl index d532537eb9..52edfaee29 100644 --- a/lib/kernel/test/gen_tcp_misc_SUITE.erl +++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl @@ -52,7 +52,8 @@ several_accepts_in_one_go/1, accept_system_limit/1, active_once_closed/1, send_timeout/1, send_timeout_active/1, otp_7731/1, zombie_sockets/1, otp_7816/1, otp_8102/1, - wrapping_oct/0, wrapping_oct/1, otp_9389/1, otp_13939/1]). + wrapping_oct/0, wrapping_oct/1, otp_9389/1, otp_13939/1, + otp_12242/1, delay_send_error/1]). %% Internal exports. -export([sender/3, not_owner/1, passive_sockets_server/2, priority_server/1, @@ -95,7 +96,8 @@ all() -> killing_multi_acceptors2, several_accepts_in_one_go, accept_system_limit, active_once_closed, send_timeout, send_timeout_active, otp_7731, wrapping_oct, - zombie_sockets, otp_7816, otp_8102, otp_9389]. + zombie_sockets, otp_7816, otp_8102, otp_9389, + otp_12242, delay_send_error]. groups() -> []. @@ -1919,7 +1921,17 @@ so_priority(Config) when is_list(Config) -> %% IP_RECVTOS and IP_RECVTCLASS for IP_PKTOPTIONS -%% does not seem to be implemented in Linux until kernel 3.0 +%% does not seem to be implemented in Linux until kernel 3.1 +%% +%% It seems pktoptions does not return valid values +%% for IPv4 connect sockets. On the accept socket +%% we get valid values, but on the connect socket we get +%% the default values for TOS and TTL. +%% +%% Therefore the argument CheckConnect that enables +%% checking the returned values for the connect socket. +%% It is only used for recvtclass that is an IPv6 option +%% and there we get valid values from both socket ends. recvtos(_Config) -> test_pktoptions( @@ -1957,33 +1969,47 @@ recvtclass(_Config) -> {skip,{ipv6_not_supported,IFs}} end. -%% These version numbers are the highest noted in daily tests -%% where the test fails for a plausible reason, so -%% skip on that platform. +%% These version numbers are above the highest noted +%% in daily tests where the test fails for a plausible reason, +%% so skip on platforms of lower version, i.e they are future +%% versions where it is possible that it might not fail. %% -%% On newer versions it might be fixed, but we'll see about that -%% when machines with newer versions gets installed... -%% If the test still fails for a plausible reason these +%% When machines with newer versions gets installed, +%% if the test still fails for a plausible reason these %% version numbers simply should be increased. +%% Or maybe we should change to only test on known good +%% platforms - change {unix,_} to false? +%% pktoptions is not supported for IPv4 +recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); +recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); %% Using the option returns einval, so it is not implemented. -recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0}); +recvtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0}); +recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); %% Does not return any value - not implemented for pktoptions recvtos_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {3,1,0}); %% recvtos_ok({unix,_}, _) -> true; recvtos_ok(_, _) -> false. -recvttl_ok({unix,linux}, _) -> true; +%% pktoptions is not supported for IPv4 +recvttl_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); +recvttl_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); +%% Using the option returns einval, so it is not implemented. +recvttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0}); +recvttl_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); +%% Does not return any value - not implemented for pktoptions +recvttl_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {2,7,0}); +%% recvttl_ok({unix,_}, _) -> true; recvttl_ok(_, _) -> false. -%% Using the option returns einval, so it is not implemented. +%% pktoptions is not supported for IPv6 recvtclass_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); +recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); +recvtclass_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); +%% Using the option returns einval, so it is not implemented. recvtclass_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0}); -%% Using the option returns einval up to 0.9.0, so it is not implemented. -%% Does not return any value - not implemented for pktoptions -recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0}); %% Does not return any value - not implemented for pktoptions recvtclass_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {3,1,0}); %% @@ -2002,18 +2028,18 @@ semver_lt({X1,Y1,Z1}, {X2,Y2,Z2}) -> end; semver_lt(_, {_,_,_}) -> false. -test_pktoptions(Family, Spec, OSFilter, CheckAccept) -> +test_pktoptions(Family, Spec, OSFilter, CheckConnect) -> OSType = os:type(), OSVer = os:version(), case OSFilter(OSType, OSVer) of true -> io:format("Os: ~p, ~p~n", [OSType,OSVer]), - test_pktoptions(Family, Spec, CheckAccept, OSType, OSVer); + test_pktoptions(Family, Spec, CheckConnect, OSType, OSVer); false -> {skip,{not_supported_for_os_version,{OSType,OSVer}}} end. %% -test_pktoptions(Family, Spec, CheckAccept, OSType, OSVer) -> +test_pktoptions(Family, Spec, CheckConnect, OSType, OSVer) -> Timeout = 5000, RecvOpts = [RecvOpt || {RecvOpt,_,_} <- Spec], TrueRecvOpts = [{RecvOpt,true} || {RecvOpt,_,_} <- Spec], @@ -2105,7 +2131,7 @@ test_pktoptions(Family, Spec, CheckAccept, OSType, OSVer) -> ok = gen_tcp:close(S4), ok = gen_tcp:close(S3), ok = gen_tcp:close(L), - (Result1 and ((not CheckAccept) or (Result2 and Result3))) + (Result1 and ((not CheckConnect) or (Result2 and Result3))) orelse exit({failed, [{OptsVals1,OptsVals4,OptsVals}, @@ -3261,3 +3287,172 @@ otp_13939(Config) when is_list(Config) -> exit(Pid, normal), ct:fail("Server process blocked on send.") end. + +otp_12242(Config) when is_list(Config) -> + case os:type() of + {win32,_} -> + %% Even if we set sndbuf and recbuf to small sizes + %% Windows either happily accepts to send GBytes of data + %% in no time, so the second send below that is supposed + %% to time out just succedes, or the first send that + %% is supposed to fill the inet_drv I/O queue and + %% start waiting for when more data can be sent + %% instead sends all data but suffers a send + %% failure that closes the socket + {skipped,backpressure_broken_on_win32}; + _ -> + %% Find the IPv4 address of an up and running interface + %% that is not loopback nor pointtopoint + {ok,IFList} = inet:getifaddrs(), + ct:pal("IFList ~p~n", [IFList]), + case + lists:flatten( + [lists:filtermap( + fun ({addr,Addr}) when tuple_size(Addr) =:= 4 -> + {true,Addr}; + (_) -> + false + end, Opts) + || {_,Opts} <- IFList, + case lists:keyfind(flags, 1, Opts) of + {_,Flags} -> + lists:member(up, Flags) + andalso + lists:member(running, Flags) + andalso + not lists:member(loopback, Flags) + andalso + not lists:member(pointtopoint, Flags); + false -> + false + end]) + of + [Addr|_] -> + otp_12242(Addr); + Other -> + {skipped,{no_external_address,Other}} + end + end; +%% +otp_12242(Addr) when tuple_size(Addr) =:= 4 -> + ct:timetrap(30000), + ct:pal("Using address ~p~n", [Addr]), + Bufsize = 16 * 1024, + Datasize = 128 * 1024 * 1024, % At least 1 s on GBit interface + Blob = binary:copy(<<$x>>, Datasize), + LOpts = + [{backlog,4},{reuseaddr,true},{ip,Addr}, + binary,{active,false}, + {recbuf,Bufsize},{sndbuf,Bufsize},{buffer,Bufsize}], + COpts = + [binary,{active,false},{ip,Addr}, + {linger,{true,1}}, % 1 s + {send_timeout,500}, + {recbuf,Bufsize},{sndbuf,Bufsize},{buffer,Bufsize}], + Dir = filename:dirname(code:which(?MODULE)), + {ok,ListenerNode} = + test_server:start_node( + ?UNIQ_NODE_NAME, slave, [{args,"-pa " ++ Dir}]), + Tester = self(), + Listener = + spawn( + ListenerNode, + fun () -> + {ok,L} = gen_tcp:listen(0, LOpts), + {ok,LPort} = inet:port(L), + Tester ! {self(),port,LPort}, + {ok,A} = gen_tcp:accept(L), + ok = gen_tcp:close(L), + receive + {Tester,stop} -> + ok = gen_tcp:close(A) + end + end), + ListenerMref = monitor(process, Listener), + LPort = receive {Listener,port,P} -> P end, + {ok,C} = gen_tcp:connect(Addr, LPort, COpts, infinity), + {ok,ReadCOpts} = inet:getopts(C, [recbuf,sndbuf,buffer]), + ct:pal("ReadCOpts ~p~n", [ReadCOpts]), + %% + %% Fill the buffers + ct:pal("Sending ~p bytes~n", [Datasize]), + ok = gen_tcp:send(C, Blob), + ct:pal("Sent ~p bytes~n", [Datasize]), + %% Spawn the Closer, + %% try to ensure that the close call is in progress + %% before the owner proceeds with sending + Owner = self(), + {_Closer,CloserMref} = + spawn_opt( + fun () -> + Owner ! {tref, erlang:start_timer(50, Owner, closing)}, + ct:pal("Calling gen_tcp:close(C)~n"), + try gen_tcp:close(C) of + Result -> + ct:pal("gen_tcp:close(C) -> ~p~n", [Result]), + ok = Result + catch + Class:Reason:Stacktrace -> + ct:pal( + "gen_tcp:close(C) >< ~p:~p~n ~p~n", + [Class,Reason,Stacktrace]), + erlang:raise(Class, Reason, Stacktrace) + end + end, [link,monitor]), + receive + {tref,Tref} -> + receive {timeout,Tref,_} -> ok end, + ct:pal("Sending ~p bytes again~n", [Datasize]), + %% Now should the close be in progress... + %% All buffers are full, remote end is not reading, + %% and the send timeout is 1 s so this will timeout: + {error,timeout} = gen_tcp:send(C, Blob), + ct:pal("Sending ~p bytes again timed out~n", [Datasize]), + ok = inet:setopts(C, [{send_timeout,10000}]), + %% There is a hidden timeout here. Port close is sampled + %% every 5 s by prim_inet:send_recv_reply. + %% Linger is 3 s so the Closer will finish this send: + ct:pal("Sending ~p bytes with 10 s timeout~n", [Datasize]), + {error,closed} = gen_tcp:send(C, Blob), + ct:pal("Sending ~p bytes with 10 s timeout was closed~n", + [Datasize]), + normal = wait(CloserMref), + ct:pal("The Closer has exited~n"), + Listener ! {Tester,stop}, + receive {'DOWN',ListenerMref,_,_,_} -> ok end, + ct:pal("The Listener has exited~n"), + test_server:stop_node(ListenerNode), + ok + end. + +wait(Mref) -> + receive {'DOWN',Mref,_,_,Reason} -> Reason end. + +%% OTP-15536 +%% Test that send error works correctly for delay_send +delay_send_error(Config) -> + {ok, LS} = gen_tcp:listen(0, [{reuseaddr, true}, {packet, 1}, {active, false}]), + {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS), + P = spawn_link( + fun() -> + {ok, S} = gen_tcp:accept(LS), + receive die -> gen_tcp:close(S) end + end), + erlang:monitor(process, P), + {ok, S} = gen_tcp:connect("localhost", PortNum, + [{packet, 1}, {active, false}, {delay_send, true}]), + + %% Do a couple of sends first to see that it works + ok = gen_tcp:send(S, "hello"), + ok = gen_tcp:send(S, "hello"), + ok = gen_tcp:send(S, "hello"), + + %% Make the receiver close + P ! die, + receive _Down -> ok end, + + ok = gen_tcp:send(S, "hello"), + timer:sleep(500), %% Sleep in order for delay_send to have time to trigger + + %% This used to result in a double free + {error, closed} = gen_tcp:send(S, "hello"). diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl index 878e48786c..af9985de45 100644 --- a/lib/kernel/test/gen_udp_SUITE.erl +++ b/lib/kernel/test/gen_udp_SUITE.erl @@ -616,13 +616,14 @@ recvtclass(_Config) -> %% when machines with newer versions gets installed... %% If the test still fails for a plausible reason these %% version numbers simply should be increased. +%% Or maybe we should change to only test on known good platforms? %% Using the option returns einval, so it is not implemented. recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0}); %% Using the option returns einval, so it is not implemented. recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); %% Using the option returns einval, so it is not implemented. -recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,11,0}); +recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); %% recvtos_ok({unix,_}, _) -> true; recvtos_ok(_, _) -> false. diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl index 713de8c9a8..8b33f4a679 100644 --- a/lib/kernel/test/inet_SUITE.erl +++ b/lib/kernel/test/inet_SUITE.erl @@ -1060,28 +1060,26 @@ getservbyname_overflow(Config) when is_list(Config) -> getifaddrs(Config) when is_list (Config) -> {ok,IfAddrs} = inet:getifaddrs(), io:format("IfAddrs = ~p.~n", [IfAddrs]), - case - {os:type(), - [If || - {If,Opts} <- IfAddrs, - lists:keymember(hwaddr, 1, Opts)]} of - {{unix,sunos},[]} -> ok; - {OT,[]} -> - ct:fail({should_have_hwaddr,OT}); - _ -> ok + case [If || {If,Opts} <- IfAddrs, lists:keymember(hwaddr, 1, Opts)] of + [] -> + case os:type() of + {unix,sunos} -> ok; + OT -> + ct:fail({should_have_hwaddr,OT}) + end; + [_|_] -> ok end, - Addrs = - [element(1, A) || A <- ifaddrs(IfAddrs)], + Addrs = ifaddrs(IfAddrs), io:format("Addrs = ~p.~n", [Addrs]), [check_addr(Addr) || Addr <- Addrs], ok. -check_addr({addr,Addr}) +check_addr(Addr) when tuple_size(Addr) =:= 8, element(1, Addr) band 16#FFC0 =:= 16#FE80 -> io:format("Addr: ~p link local; SKIPPED!~n", [Addr]), ok; -check_addr({addr,Addr}) -> +check_addr(Addr) -> io:format("Addr: ~p.~n", [Addr]), Ping = "ping", Pong = "pong", @@ -1097,78 +1095,86 @@ check_addr({addr,Addr}) -> ok = gen_tcp:close(S2), ok = gen_tcp:close(L). --record(ifopts, {name,flags,addrs=[],hwaddr}). - -ifaddrs([]) -> []; -ifaddrs([{If,Opts}|IOs]) -> - #ifopts{flags=F} = Ifopts = check_ifopts(Opts, #ifopts{name=If}), - case F of - {flags,Flags} -> - case lists:member(running, Flags) of - true -> Ifopts#ifopts.addrs; - false -> [] - end ++ ifaddrs(IOs); - undefined -> - ifaddrs(IOs) +ifaddrs(IfOpts) -> + IfMap = collect_ifopts(IfOpts), + ChkFun = + fun Self({{_,Flags} = Key, Opts}, ok) -> + Broadcast = lists:member(broadcast, Flags), + P2P = lists:member(pointtopoint, Flags), + case Opts of + [{addr,_},{netmask,_},{broadaddr,_}|Os] + when Broadcast -> + Self({Key, Os}, ok); + [{addr,_},{netmask,_},{dstaddr,_}|Os] + when P2P -> + Self({Key, Os}, ok); + [{addr,_},{netmask,_}|Os] -> + Self({Key, Os}, ok); + [{hwaddr,_}|Os] -> + Self({Key, Os}, ok); + [] -> + ok + end + end, + fold_ifopts(ChkFun, ok, IfMap), + AddrsFun = + fun ({{_,Flags}, Opts}, Acc) -> + case + lists:member(running, Flags) + andalso (not lists:member(pointtopoint, Flags)) + of + true -> + lists:reverse( + [Addr || {addr,Addr} <- Opts], + Acc); + false -> + Acc + end + end, + fold_ifopts(AddrsFun, [], IfMap). + +collect_ifopts(IfOpts) -> + collect_ifopts(IfOpts, #{}). +%% +collect_ifopts(IfOpts, IfMap) -> + case IfOpts of + [{If,[{flags,Flags}|Opts]}|IfOs] -> + Key = {If,Flags}, + case maps:is_key(Key, IfMap) of + true -> + ct:fail({unexpected_ifopts,IfOpts,IfMap}); + false -> + collect_ifopts(IfOs, IfMap, Opts, Key, []) + end; + [] -> + IfMap; + _ -> + ct:fail({unexpected_ifopts,IfOpts,IfMap}) + end. +%% +collect_ifopts(IfOpts, IfMap, Opts, Key, R) -> + case Opts of + [{flags,_}|_] -> + {If,_} = Key, + collect_ifopts( + [{If,Opts}|IfOpts], maps:put(Key, lists:reverse(R), IfMap)); + [OptVal|Os] -> + collect_ifopts(IfOpts, IfMap, Os, Key, [OptVal|R]); + [] -> + collect_ifopts(IfOpts, maps:put(Key, lists:reverse(R), IfMap)) end. -check_ifopts([], #ifopts{flags=F,addrs=Raddrs}=Ifopts) -> - Addrs = lists:reverse(Raddrs), - R = Ifopts#ifopts{addrs=Addrs}, - io:format("~p.~n", [R]), - %% See how we did... - {flags,Flags} = F, - case lists:member(broadcast, Flags) of - true -> - [case A of - {{addr,_},{netmask,_},{broadaddr,_}} -> - A; - {{addr,T},{netmask,_}} when tuple_size(T) =:= 8 -> - A - end || A <- Addrs]; - false -> - case lists:member(pointtopoint, Flags) of - true -> - [case A of - {{addr,_},{netmask,_},{dstaddr,_}} -> - A - end || A <- Addrs]; - false -> - [case A of - {{addr,_},{netmask,_}} -> - A - end || A <- Addrs] - end - end, - R; -check_ifopts([{flags,_}=F|Opts], #ifopts{flags=undefined}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{flags=F}); -check_ifopts([{flags,_}=F|Opts], #ifopts{flags=Flags}=Ifopts) -> - case F of - Flags -> - check_ifopts(Opts, Ifopts); - _ -> - ct:fail({multiple_flags,F,Ifopts}) - end; -check_ifopts( - [{addr,_}=A,{netmask,_}=N,{dstaddr,_}=D|Opts], - #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A,N,D}|Addrs]}); -check_ifopts( - [{addr,_}=A,{netmask,_}=N,{broadaddr,_}=B|Opts], - #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A,N,B}|Addrs]}); -check_ifopts( - [{addr,_}=A,{netmask,_}=N|Opts], - #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A,N}|Addrs]}); -check_ifopts([{addr,_}=A|Opts], #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A}|Addrs]}); -check_ifopts([{hwaddr,Hwaddr}=H|Opts], #ifopts{hwaddr=undefined}=Ifopts) - when is_list(Hwaddr) -> - check_ifopts(Opts, Ifopts#ifopts{hwaddr=H}); -check_ifopts([{hwaddr,_}=H|_], #ifopts{}=Ifopts) -> - ct:fail({multiple_hwaddrs,H,Ifopts}). +fold_ifopts(Fun, Acc, IfMap) -> + fold_ifopts(Fun, Acc, IfMap, maps:keys(IfMap)). +%% +fold_ifopts(Fun, Acc, IfMap, Keys) -> + case Keys of + [Key|Ks] -> + Opts = maps:get(Key, IfMap), + fold_ifopts(Fun, Fun({Key,Opts}, Acc), IfMap, Ks); + [] -> + Acc + end. %% Works just like lists:member/2, except that any {127,_,_,_} tuple %% matches any other {127,_,_,_}. We do this to handle Linux systems diff --git a/lib/kernel/test/inet_sockopt_SUITE.erl b/lib/kernel/test/inet_sockopt_SUITE.erl index ada9c2689c..27ff74e309 100644 --- a/lib/kernel/test/inet_sockopt_SUITE.erl +++ b/lib/kernel/test/inet_sockopt_SUITE.erl @@ -110,9 +110,14 @@ simple(Config) when is_list(Config) -> {S1,S2} = create_socketpair(Opt, Opt), {ok,Opt} = inet:getopts(S1,OptTags), {ok,Opt} = inet:getopts(S2,OptTags), - COpt = [{X,case X of nodelay -> false;_ -> Y end} || {X,Y} <- Opt], + NoPushOpt = case os:type() of + {unix, Osname} when Osname =:= linux; Osname =:= freebsd -> {nopush, true}; + {_,_} -> {nopush, false} + end, + COpt = [{X,case X of nodelay -> false;_ -> Y end} || {X,Y} <- [NoPushOpt|Opt]], + COptTags = [X || {X,_} <- COpt], inet:setopts(S1,COpt), - {ok,COpt} = inet:getopts(S1,OptTags), + {ok,COpt} = inet:getopts(S1,COptTags), {ok,Opt} = inet:getopts(S2,OptTags), gen_tcp:close(S1), gen_tcp:close(S2), diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl index 6a006cdc01..a0154b2694 100644 --- a/lib/kernel/test/init_SUITE.erl +++ b/lib/kernel/test/init_SUITE.erl @@ -295,7 +295,7 @@ is_real_system(KernelVsn, StdlibVsn) -> %% before restart. %% ------------------------------------------------ many_restarts() -> - [{timetrap,{minutes,8}}]. + [{timetrap,{minutes,16}}]. many_restarts(Config) when is_list(Config) -> {ok, Node} = loose_node:start(init_test, "", ?DEFAULT_TIMEOUT_SEC), @@ -315,7 +315,7 @@ loop_restart(N,Node,EHPid) -> loose_node:stop(Node), ct:fail(not_stopping) end, - ok = wait_for(30, Node, EHPid), + ok = wait_for(60, Node, EHPid), loop_restart(N-1,Node,rpc:call(Node,erlang,whereis,[logger])). wait_for(0,Node,_) -> @@ -367,7 +367,8 @@ restart(Config) when is_list(Config) -> SysProcs0 = rpc:call(Node, ?MODULE, find_system_processes, []), io:format("SysProcs0=~p~n", [SysProcs0]), [InitPid, PurgerPid, LitCollectorPid, - DirtySigNPid, DirtySigHPid, DirtySigMPid] = SysProcs0, + DirtySigNPid, DirtySigHPid, DirtySigMPid, + PrimFilePid] = SysProcs0, InitPid = rpc:call(Node, erlang, whereis, [init]), PurgerPid = rpc:call(Node, erlang, whereis, [erts_code_purger]), Procs = rpc:call(Node, erlang, processes, []), @@ -385,7 +386,8 @@ restart(Config) when is_list(Config) -> SysProcs1 = rpc:call(Node, ?MODULE, find_system_processes, []), io:format("SysProcs1=~p~n", [SysProcs1]), [InitPid1, PurgerPid1, LitCollectorPid1, - DirtySigNPid1, DirtySigHPid1, DirtySigMPid1] = SysProcs1, + DirtySigNPid1, DirtySigHPid1, DirtySigMPid1, + PrimFilePid1] = SysProcs1, %% Still the same init process! InitPid1 = rpc:call(Node, erlang, whereis, [init]), @@ -411,6 +413,10 @@ restart(Config) when is_list(Config) -> DirtySigMP = pid_to_list(DirtySigMPid), DirtySigMP = pid_to_list(DirtySigMPid1), + %% and same prim_file helper process! + PrimFileP = pid_to_list(PrimFilePid), + PrimFileP = pid_to_list(PrimFilePid1), + NewProcs0 = rpc:call(Node, erlang, processes, []), NewProcs = NewProcs0 -- SysProcs1, case check_processes(NewProcs, MaxPid) of @@ -437,7 +443,8 @@ restart(Config) when is_list(Config) -> literal_collector, dirty_sig_handler_normal, dirty_sig_handler_high, - dirty_sig_handler_max}). + dirty_sig_handler_max, + prim_file}). find_system_processes() -> find_system_procs(processes(), #sys_procs{}). @@ -448,10 +455,11 @@ find_system_procs([], SysProcs) -> SysProcs#sys_procs.literal_collector, SysProcs#sys_procs.dirty_sig_handler_normal, SysProcs#sys_procs.dirty_sig_handler_high, - SysProcs#sys_procs.dirty_sig_handler_max]; + SysProcs#sys_procs.dirty_sig_handler_max, + SysProcs#sys_procs.prim_file]; find_system_procs([P|Ps], SysProcs) -> case process_info(P, [initial_call, priority]) of - [{initial_call,{otp_ring0,start,2}},_] -> + [{initial_call,{erl_init,start,2}},_] -> undefined = SysProcs#sys_procs.init, find_system_procs(Ps, SysProcs#sys_procs{init = P}); [{initial_call,{erts_code_purger,start,0}},_] -> @@ -472,6 +480,9 @@ find_system_procs([P|Ps], SysProcs) -> {priority,max}] -> undefined = SysProcs#sys_procs.dirty_sig_handler_max, find_system_procs(Ps, SysProcs#sys_procs{dirty_sig_handler_max = P}); + [{initial_call,{prim_file,start,0}},_] -> + undefined = SysProcs#sys_procs.prim_file, + find_system_procs(Ps, SysProcs#sys_procs{prim_file = P}); _ -> find_system_procs(Ps, SysProcs) end. diff --git a/lib/kernel/test/kernel_bench.spec b/lib/kernel/test/kernel_bench.spec index 4de133f21b..898ceb59e0 100644 --- a/lib/kernel/test/kernel_bench.spec +++ b/lib/kernel/test/kernel_bench.spec @@ -1,2 +1,3 @@ {groups,"../kernel_test",zlib_SUITE,[bench]}. {groups,"../kernel_test",file_SUITE,[bench]}. +{suites,"../kernel_test",[logger_stress_SUITE]}. diff --git a/lib/kernel/test/logger.cover b/lib/kernel/test/logger.cover index 960bc0abff..9691aa295e 100644 --- a/lib/kernel/test/logger.cover +++ b/lib/kernel/test/logger.cover @@ -4,9 +4,12 @@ logger_backend, logger_config, logger_disk_log_h, - logger_h_common, logger_filters, logger_formatter, + logger_handler_watcher, + logger_h_common, + logger_olp, + logger_proxy, logger_server, logger_simple_h, logger_std_h, diff --git a/lib/kernel/test/logger.spec b/lib/kernel/test/logger.spec index 1ab90b3e93..3aec37951d 100644 --- a/lib/kernel/test/logger.spec +++ b/lib/kernel/test/logger.spec @@ -7,5 +7,7 @@ logger_filters_SUITE, logger_formatter_SUITE, logger_legacy_SUITE, + logger_olp_SUITE, + logger_proxy_SUITE, logger_simple_h_SUITE, logger_std_h_SUITE]}. diff --git a/lib/kernel/test/logger_SUITE.erl b/lib/kernel/test/logger_SUITE.erl index 36c093d7f4..d831d0d108 100644 --- a/lib/kernel/test/logger_SUITE.erl +++ b/lib/kernel/test/logger_SUITE.erl @@ -246,6 +246,18 @@ change_config(_Config) -> {ok,C4} = logger:get_handler_config(h1), C4 = C3#{custom:=new_custom}, + %% Change handler config: Id and module can not be changed + {error,{illegal_config_change,Old,New}} = + logger:set_handler_config(h1,id,newid), + %% Check that only the faulty field is included in return + [{id,h1}] = maps:to_list(Old), + [{id,newid}] = maps:to_list(New), + %% Check that both fields are included when both are changed + {error,{illegal_config_change, + #{id:=h1,module:=?MODULE}, + #{id:=newid,module:=newmodule}}} = + logger:set_handler_config(h1,#{id=>newid,module=>newmodule}), + %% Change primary config: Single key PConfig0 = logger:get_primary_config(), ok = logger:set_primary_config(level,warning), @@ -436,27 +448,32 @@ set_application_level(_Config) -> {error,{not_loaded,mnesia}} = logger:set_application_level(mnesia, warning), {error,{not_loaded,mnesia}} = logger:unset_application_level(mnesia), - application:load(mnesia), - {ok, Modules} = application:get_key(mnesia, modules), - [] = logger:get_module_level(Modules), + case application:load(mnesia) of + ok -> + {ok, Modules} = application:get_key(mnesia, modules), + [] = logger:get_module_level(Modules), - {error,{invalid_level,warn}} = logger:set_application_level(mnesia, warn), + {error,{invalid_level,warn}} = + logger:set_application_level(mnesia, warn), - ok = logger:set_application_level(mnesia, debug), - DebugModules = lists:sort([{M,debug} || M <- Modules]), - DebugModules = lists:sort(logger:get_module_level(Modules)), + ok = logger:set_application_level(mnesia, debug), + DebugModules = lists:sort([{M,debug} || M <- Modules]), + DebugModules = lists:sort(logger:get_module_level(Modules)), - ok = logger:set_application_level(mnesia, warning), + ok = logger:set_application_level(mnesia, warning), - WarnModules = lists:sort([{M,warning} || M <- Modules]), - WarnModules = lists:sort(logger:get_module_level(Modules)), + WarnModules = lists:sort([{M,warning} || M <- Modules]), + WarnModules = lists:sort(logger:get_module_level(Modules)), - ok = logger:unset_application_level(mnesia), - [] = logger:get_module_level(Modules). + ok = logger:unset_application_level(mnesia), + [] = logger:get_module_level(Modules); + {error,{"no such file or directory","mnesia.app"}} -> + {skip, "Cannot load mnesia, does not exist"} + end. set_application_level(cleanup,_Config) -> - ok = logger:unset_application_level(mnesia), - ok = application:unload(mnesia), + _ = logger:unset_application_level(mnesia), + _ = application:unload(mnesia), ok. cache_module_level(_Config) -> diff --git a/lib/kernel/test/logger_disk_log_h_SUITE.erl b/lib/kernel/test/logger_disk_log_h_SUITE.erl index a815db14e9..9bbec42de8 100644 --- a/lib/kernel/test/logger_disk_log_h_SUITE.erl +++ b/lib/kernel/test/logger_disk_log_h_SUITE.erl @@ -24,6 +24,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/logger.hrl"). -include_lib("kernel/src/logger_internal.hrl"). +-include_lib("kernel/src/logger_olp.hrl"). -include_lib("kernel/src/logger_h_common.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("kernel/include/file.hrl"). @@ -92,11 +93,11 @@ all() -> disk_log_opts, default_formatter, logging, + filter_config, errors, formatter_fail, config_fail, bad_input, - info_and_reset, reconfig, sync, disk_log_full, @@ -302,6 +303,20 @@ logging(cleanup, _Config) -> Name = list_to_atom(lists:concat([?FUNCTION_NAME,"_1"])), remove_and_stop(Name). +filter_config(_Config) -> + ok = logger:add_handler(?MODULE,logger_disk_log_h,#{}), + {ok,#{config:=HConfig}=Config} = logger:get_handler_config(?MODULE), + HConfig = maps:without([olp],HConfig), + + FakeFullHConfig = HConfig#{olp=>{regname,self(),erlang:make_ref()}}, + #{config:=HConfig} = + logger_disk_log_h:filter_config(Config#{config=>FakeFullHConfig}), + ok. + +filter_config(cleanup,_Config) -> + logger:remove_handler(?MODULE), + ok. + errors(Config) -> PrivDir = ?config(priv_dir,Config), Name1 = list_to_atom(lists:concat([?FUNCTION_NAME,"_1"])), @@ -316,13 +331,29 @@ errors(Config) -> %%! TODO: %%! Check how bad log_opts are handled! - {error,{illegal_config_change,_,_}} = - logger:set_handler_config(Name1, - config, - #{file=>LogFile1, - type=>halt}), - {error,{illegal_config_change,_,_}} = - logger:set_handler_config(Name1,id,new), + {error,{illegal_config_change, + logger_disk_log_h, + #{type:=wrap}, + #{type:=halt}}} = + logger:update_handler_config(Name1, + config, + #{type=>halt, + file=>LogFile1}), + + {error,{illegal_config_change, + logger_disk_log_h, + #{file:=LogFile1}, + #{file:="newfilename"}}} = + logger:update_handler_config(Name1, + config, + #{file=>"newfilename"}), + + %% Read-only fields may (accidentially) be included in the change, + %% but it won't take effect + {ok,C} = logger:get_handler_config(Name1), + ok = logger:set_handler_config(Name1,config,#{olp=>dummyvalue}), + {ok,C} = logger:get_handler_config(Name1), + ok = logger:remove_handler(Name1), {error,{not_found,Name1}} = logger:remove_handler(Name1), @@ -380,20 +411,22 @@ formatter_fail(cleanup,_Config) -> ok. config_fail(_Config) -> - {error,{handler_not_added,{invalid_config,logger_disk_log_h,{bad,bad}}}} = + {error,{handler_not_added,{invalid_config,logger_disk_log_h,#{bad:=bad}}}} = logger:add_handler(?MODULE,logger_disk_log_h, #{config => #{bad => bad}, filter_default=>log, formatter=>{?MODULE,self()}}), - {error,{handler_not_added,{invalid_levels,{_,1,_}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=1}}}} = logger:add_handler(?MODULE,logger_disk_log_h, #{config => #{drop_mode_qlen=>1}}), - {error,{handler_not_added,{invalid_levels,{43,42,_}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{sync_mode_qlen:=43, + drop_mode_qlen:=42}}}} = logger:add_handler(?MODULE,logger_disk_log_h, #{config => #{sync_mode_qlen=>43, drop_mode_qlen=>42}}), - {error,{handler_not_added,{invalid_levels,{_,43,42}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=43, + flush_qlen:=42}}}} = logger:add_handler(?MODULE,logger_disk_log_h, #{config => #{drop_mode_qlen=>43, flush_qlen=>42}}), @@ -402,40 +435,26 @@ config_fail(_Config) -> #{filter_default=>log, formatter=>{?MODULE,self()}}), %% can't change the disk log options for a log already in use - {error,{illegal_config_change,_,_}} = - logger:set_handler_config(?MODULE,config, - #{max_no_files=>2}), - %% can't change name of an existing handler - {error,{illegal_config_change,_,_}} = - logger:set_handler_config(?MODULE,id,bad), + {error,{illegal_config_change,logger_disk_log_h,_,_}} = + logger:update_handler_config(?MODULE,config, + #{max_no_files=>2}), %% incorrect values of OP params {ok,#{config := HConfig}} = logger:get_handler_config(?MODULE), - {error,{invalid_levels,_}} = - logger:set_handler_config(?MODULE,config, - HConfig#{sync_mode_qlen=>100, - flush_qlen=>99}), + {error,{invalid_olp_levels,_}} = + logger:update_handler_config(?MODULE,config, + HConfig#{sync_mode_qlen=>100, + flush_qlen=>99}), %% invalid name of config parameter - {error,{invalid_config,logger_disk_log_h,{filesync_rep_int,2000}}} = - logger:set_handler_config(?MODULE, config, - HConfig#{filesync_rep_int => 2000}), + {error,{invalid_config,logger_disk_log_h,#{filesync_rep_int:=2000}}} = + logger:update_handler_config(?MODULE, config, + HConfig#{filesync_rep_int => 2000}), ok. config_fail(cleanup,_Config) -> logger:remove_handler(?MODULE). bad_input(_Config) -> {error,{badarg,{filesync,["BadType"]}}} = - logger_disk_log_h:filesync("BadType"), - {error,{badarg,{info,["BadType"]}}} = logger_disk_log_h:info("BadType"), - {error,{badarg,{reset,["BadType"]}}} = logger_disk_log_h:reset("BadType"). - -info_and_reset(_Config) -> - ok = logger:add_handler(?MODULE,logger_disk_log_h, - #{filter_default=>log, - formatter=>{?MODULE,self()}}), - #{id := ?MODULE} = logger_disk_log_h:info(?MODULE), - ok = logger_disk_log_h:reset(?MODULE). -info_and_reset(cleanup,_Config) -> - logger:remove_handler(?MODULE). + logger_disk_log_h:filesync("BadType"). reconfig(Config) -> Dir = ?config(priv_dir,Config), @@ -444,7 +463,7 @@ reconfig(Config) -> #{filter_default=>log, filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), formatter=>{?MODULE,self()}}), - #{id := ?MODULE, + #{%id := ?MODULE, sync_mode_qlen := ?SYNC_MODE_QLEN, drop_mode_qlen := ?DROP_MODE_QLEN, flush_qlen := ?FLUSH_QLEN, @@ -455,14 +474,32 @@ reconfig(Config) -> overload_kill_qlen := ?OVERLOAD_KILL_QLEN, overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE, overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER, - filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL, - log_opts := #{type := ?DISK_LOG_TYPE, - max_no_files := ?DISK_LOG_MAX_NO_FILES, - max_no_bytes := ?DISK_LOG_MAX_NO_BYTES, - file := _DiskLogFile}} = - logger_disk_log_h:info(?MODULE), - - {ok,#{config := HConfig0}} = logger:get_handler_config(?MODULE), + cb_state := + #{handler_state := + #{log_opts := #{type := ?DISK_LOG_TYPE, + max_no_files := ?DISK_LOG_MAX_NO_FILES, + max_no_bytes := ?DISK_LOG_MAX_NO_BYTES, + file := DiskLogFile}}, + filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL}} = + logger_olp:info(h_proc_name()), + {ok,#{config := + #{sync_mode_qlen := ?SYNC_MODE_QLEN, + drop_mode_qlen := ?DROP_MODE_QLEN, + flush_qlen := ?FLUSH_QLEN, + burst_limit_enable := ?BURST_LIMIT_ENABLE, + burst_limit_max_count := ?BURST_LIMIT_MAX_COUNT, + burst_limit_window_time := ?BURST_LIMIT_WINDOW_TIME, + overload_kill_enable := ?OVERLOAD_KILL_ENABLE, + overload_kill_qlen := ?OVERLOAD_KILL_QLEN, + overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE, + overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER, + filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL, + file := DiskLogFile, + max_no_files := ?DISK_LOG_MAX_NO_FILES, + max_no_bytes := ?DISK_LOG_MAX_NO_BYTES, + type := wrap} = HConfig0}} = + logger:get_handler_config(?MODULE), + HConfig1 = HConfig0#{sync_mode_qlen => 1, drop_mode_qlen => 2, flush_qlen => 3, @@ -475,7 +512,7 @@ reconfig(Config) -> overload_kill_restart_after => infinity, filesync_repeat_interval => no_repeat}, ok = logger:set_handler_config(?MODULE, config, HConfig1), - #{id := ?MODULE, + #{%id := ?MODULE, sync_mode_qlen := 1, drop_mode_qlen := 2, flush_qlen := 3, @@ -486,8 +523,31 @@ reconfig(Config) -> overload_kill_qlen := 100000, overload_kill_mem_size := 10000000, overload_kill_restart_after := infinity, - filesync_repeat_interval := no_repeat} = - logger_disk_log_h:info(?MODULE), + cb_state := #{filesync_repeat_interval := no_repeat}} = + logger_olp:info(h_proc_name()), + {ok,#{config:=HConfig1}} = logger:get_handler_config(?MODULE), + + ok = logger:update_handler_config(?MODULE, config, + #{flush_qlen => ?FLUSH_QLEN}), + {ok,#{config:=C1}} = logger:get_handler_config(?MODULE), + ct:log("C1: ~p",[C1]), + C1 = HConfig1#{flush_qlen => ?FLUSH_QLEN}, + + ok = logger:set_handler_config(?MODULE, config, #{sync_mode_qlen => 1}), + {ok,#{config:=C2}} = logger:get_handler_config(?MODULE), + ct:log("C2: ~p",[C2]), + C2 = HConfig0#{sync_mode_qlen => 1}, + + ok = logger:set_handler_config(?MODULE, config, #{drop_mode_qlen => 100}), + {ok,#{config:=C3}} = logger:get_handler_config(?MODULE), + ct:log("C3: ~p",[C3]), + C3 = HConfig0#{drop_mode_qlen => 100}, + + ok = logger:update_handler_config(?MODULE, config, #{sync_mode_qlen => 1}), + {ok,#{config:=C4}} = logger:get_handler_config(?MODULE), + ct:log("C4: ~p",[C4]), + C4 = HConfig0#{sync_mode_qlen => 1, + drop_mode_qlen => 100}, ok = logger:remove_handler(?MODULE), @@ -502,11 +562,50 @@ reconfig(Config) -> max_no_files => 1, max_no_bytes => 1024, file => File}}), - #{log_opts := #{type := halt, - max_no_files := 1, - max_no_bytes := 1024, - file := File}} = - logger_disk_log_h:info(?MODULE), + #{cb_state := + #{handler_state := + #{log_opts := #{type := halt, + max_no_files := 1, + max_no_bytes := 1024, + file := File}}}} = + logger_olp:info(h_proc_name()), + {ok,#{config := + #{type := halt, + max_no_files := 1, + max_no_bytes := 1024, + file := File}=HaltHConfig} = Config2} = + logger:get_handler_config(?MODULE), + + ok = logger:update_handler_config(?MODULE, level, notice), + {ok,C5} = logger:get_handler_config(?MODULE), + ct:log("C5: ~p",[C5]), + C5 = Config2#{level => notice}, + + ok = logger:set_handler_config(?MODULE, level, info), + {ok,C6} = logger:get_handler_config(?MODULE), + ct:log("C6: ~p",[C6]), + C6 = Config2#{level => info}, + + %% You are not allowed to actively set the write once fields + %% (type, max_no_files, max_no_bytes, file) in runtime. + {error, {illegal_config_change,_,_,_}} = + logger:set_handler_config(?MODULE,config,#{type=>wrap}), + {error, {illegal_config_change,_,_,_}} = + logger:set_handler_config(?MODULE,config,#{max_no_files=>2}), + {error, {illegal_config_change,_,_,_}} = + logger:set_handler_config(?MODULE,config,#{max_no_bytes=>2048}), + {error, {illegal_config_change,_,_,_}} = + logger:set_handler_config(?MODULE,config,#{file=>"otherfile.log"}), + {ok,C7} = logger:get_handler_config(?MODULE), + ct:log("C7: ~p",[C7]), + C7 = C6, + + %% ... but if you don't specify the write once fields, then + %% set_handler_config shall NOT reset them to their default value + ok = logger:set_handler_config(?MODULE,config,#{sync_mode_qlen=>1}), + {ok,#{config:=C8}} = logger:get_handler_config(?MODULE), + ct:log("C8: ~p",[C8]), + C8 = HaltHConfig#{sync_mode_qlen=>1}, ok. reconfig(cleanup, _Config) -> @@ -523,59 +622,55 @@ sync(Config) -> filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), formatter=>{?MODULE,nl}}), - start_tracer([{disk_log,blog,2}, - {logger_disk_log_h,disk_log_sync,2}], - [{disk_log,blog,<<"first\n">>}, - {logger_disk_log_h,disk_log_sync}]), + start_tracer([{logger_disk_log_h,disk_log_write,3}, + {disk_log,sync,1}], + [{logger_disk_log_h,disk_log_write,<<"first\n">>}, + {disk_log,sync}]), logger:notice("first", ?domain), %% wait for automatic disk_log_sync check_tracer(?FILESYNC_REPEAT_INTERVAL*2), - %% check that if there's no repeated disk_log_sync active, + %% check that if there's no repeated filesync active, %% a disk_log_sync is still performed when handler goes idle {ok,#{config := HConfig}} = logger:get_handler_config(?MODULE), HConfig1 = HConfig#{filesync_repeat_interval => no_repeat}, - ok = logger:set_handler_config(?MODULE, config, HConfig1), - + ok = logger:update_handler_config(?MODULE, config, HConfig1), no_repeat = maps:get(filesync_repeat_interval, - logger_disk_log_h:info(?MODULE)), - %% The following timer is to make sure the time from last log - %% ("first") to next ("second") is long enough, so the a flush is - %% triggered by the idle timeout between "fourth" and "fifth". - timer:sleep(?IDLE_DETECT_TIME_MSEC*2), - - start_tracer([{disk_log,blog,2}, - {logger_disk_log_h,disk_log_sync,2}], - [{disk_log,blog,<<"second\n">>}, - {logger_disk_log_h,disk_log_sync}, - {disk_log,blog,<<"third\n">>}, - {logger_disk_log_h,disk_log_sync}]), + maps:get(cb_state,logger_olp:info(h_proc_name()))), + + start_tracer([{logger_disk_log_h,disk_log_write,3}, + {disk_log,sync,1}], + [{logger_disk_log_h,disk_log_write,<<"second\n">>}, + {disk_log,sync}, + {logger_disk_log_h,disk_log_write,<<"third\n">>}, + {disk_log,sync}]), logger:notice("second", ?domain), - timer:sleep(?IDLE_DETECT_TIME_MSEC*2), + timer:sleep(?IDLE_DETECT_TIME*2), logger:notice("third", ?domain), %% wait for automatic disk_log_sync - check_tracer(?IDLE_DETECT_TIME_MSEC*2), + check_tracer(?IDLE_DETECT_TIME*2), try_read_file(Log, {ok,<<"first\nsecond\nthird\n">>}, 1000), - %% switch repeated disk_log_sync on and verify that the looping works + %% switch repeated filesync on and verify that the looping works SyncInt = 1000, WaitT = 4500, - OneSync = {logger_disk_log_h,handle_cast,repeated_disk_log_sync}, - %% receive 1 initial repeated_disk_log_sync, then 1 per sec - start_tracer([{logger_disk_log_h,handle_cast,2}], - [OneSync || _ <- lists:seq(1, 1 + trunc(WaitT/SyncInt))]), + OneSync = {logger_h_common,handle_cast,repeated_filesync}, + %% receive 1 repeated_filesync per sec + start_tracer([{{logger_h_common,handle_cast,2}, + [{[repeated_filesync,'_'],[],[{message,{caller}}]}]}], + [OneSync || _ <- lists:seq(1, trunc(WaitT/SyncInt))]), HConfig2 = HConfig#{filesync_repeat_interval => SyncInt}, - ok = logger:set_handler_config(?MODULE, config, HConfig2), + ok = logger:update_handler_config(?MODULE, config, HConfig2), SyncInt = maps:get(filesync_repeat_interval, - logger_disk_log_h:info(?MODULE)), + maps:get(cb_state,logger_olp:info(h_proc_name()))), timer:sleep(WaitT), HConfig3 = HConfig#{filesync_repeat_interval => no_repeat}, - ok = logger:set_handler_config(?MODULE, config, HConfig3), + ok = logger:update_handler_config(?MODULE, config, HConfig3), check_tracer(100), ok. sync(cleanup,_Config) -> @@ -609,7 +704,7 @@ disk_log_wrap(Config) -> end, {ok,_} = dbg:tracer(process, {TraceFun, Tester}), {ok,_} = dbg:p(whereis(h_proc_name()), [c]), - {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 2, []), + {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 3, []), Text = [34 + rand:uniform(126-34) || _ <- lists:seq(1,MaxBytes)], ct:pal("String = ~p (~w)", [Text, erts_debug:size(Text)]), @@ -627,7 +722,7 @@ disk_log_wrap(Config) -> timer:sleep(1000), dbg:stop_clear(), Received = lists:flatmap(fun({trace,_M,handle_info, - [{disk_log,_Node,_Name,What},_]}) -> + [_,{disk_log,_Node,_Name,What},_]}) -> [{trace,What}]; ({log,_}) -> [] @@ -663,7 +758,7 @@ disk_log_full(Config) -> end, {ok,_} = dbg:tracer(process, {TraceFun, Tester}), {ok,_} = dbg:p(whereis(h_proc_name()), [c]), - {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 2, []), + {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 3, []), NoOfChars = 5, Text = [34 + rand:uniform(126-34) || _ <- lists:seq(1,NoOfChars)], @@ -673,20 +768,24 @@ disk_log_full(Config) -> timer:sleep(2000), dbg:stop_clear(), Received = lists:flatmap(fun({trace,_M,handle_info, - [{disk_log,_Node,_Name,What},_]}) -> + [_,{disk_log,_Node,_Name,What},_]}) -> [{trace,What}]; ({log,_}) -> [] end, test_server:messages_get()), ct:pal("Trace =~n~p", [Received]), - [{trace,full}, - {trace,{error_status,{error,{full,_}}}}] = Received, + + %% The tail here could be an error_status notification, if the + %% last write was synchronous, but in most cases it will not be + [{trace,full}|_] = Received, + %% [{trace,full}, + %% {trace,{error_status,{error,{full,_}}}}] = Received, ok. disk_log_full(cleanup, _Config) -> dbg:stop_clear(), logger:remove_handler(?MODULE). -disk_log_events(Config) -> +disk_log_events(_Config) -> Node = node(), Log = ?MODULE, ok = logger:add_handler(?MODULE, @@ -712,14 +811,14 @@ disk_log_events(Config) -> end, {ok,_} = dbg:tracer(process, {TraceFun, Tester}), {ok,_} = dbg:p(whereis(h_proc_name()), [c]), - {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 2, []), + {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 3, []), [whereis(h_proc_name()) ! E || E <- Events], %% wait for trace messages timer:sleep(2000), dbg:stop_clear(), Received = lists:map(fun({trace,_M,handle_info, - [Got,_]}) -> Got + [_,Got,_]}) -> Got end, test_server:messages_get()), ct:pal("Trace =~n~p", [Received]), NoOfEvents = length(Events), @@ -742,13 +841,17 @@ write_failure(Config) -> false = (undefined == rpc:call(Node, ets, whereis, [?TEST_HOOKS_TAB])), rpc:call(Node, ets, insert, [?TEST_HOOKS_TAB,{tester,self()}]), rpc:call(Node, ?MODULE, set_internal_log, [?MODULE,internal_log]), - rpc:call(Node, ?MODULE, set_result, [disk_log_blog,ok]), - HState = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]), - ct:pal("LogOpts = ~p", [LogOpts = maps:get(log_opts, HState)]), - + rpc:call(Node, ?MODULE, set_result, [disk_log_write,ok]), + HState = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]), + LogOpts = maps:get(log_opts, + maps:get(handler_state, + maps:get(cb_state,HState))), + ct:pal("LogOpts = ~p", [LogOpts]), + + %% ?check and ?check_no_log in this test only check for internal log events ok = log_on_remote_node(Node, "Logged1"), rpc:call(Node, logger_disk_log_h, filesync, [?STANDARD_HANDLER]), - ?check_no_log, + ?check_no_log, % no internal log when write ok SyncRepInt = case (fun() -> is_atom(?FILESYNC_REPEAT_INTERVAL) end)() of true -> 5500; @@ -757,24 +860,26 @@ write_failure(Config) -> try_read_file(Log, {ok,<<"Logged1\n">>}, SyncRepInt), - rpc:call(Node, ?MODULE, set_result, [disk_log_blog,{error,no_such_log}]), + rpc:call(Node, ?MODULE, set_result, [disk_log_write,{error,no_such_log}]), ok = log_on_remote_node(Node, "Cause simple error printout"), + %% this should have caused an internal log ?check({error,{?STANDARD_HANDLER,log,LogOpts,{error,no_such_log}}}), - + ok = log_on_remote_node(Node, "No second error printout"), - ?check_no_log, + ?check_no_log, % but don't log same error twice - rpc:call(Node, ?MODULE, set_result, [disk_log_blog, + rpc:call(Node, ?MODULE, set_result, [disk_log_write, {error,{full,?STANDARD_HANDLER}}]), ok = log_on_remote_node(Node, "Cause simple error printout"), + %% this was a different error, so it should be logged ?check({error,{?STANDARD_HANDLER,log,LogOpts, {error,{full,?STANDARD_HANDLER}}}}), - rpc:call(Node, ?MODULE, set_result, [disk_log_blog,ok]), + rpc:call(Node, ?MODULE, set_result, [disk_log_write,ok]), ok = log_on_remote_node(Node, "Logged2"), rpc:call(Node, logger_disk_log_h, filesync, [?STANDARD_HANDLER]), - ?check_no_log, + ?check_no_log, % no internal log when write ok try_read_file(Log, {ok,<<"Logged1\nLogged2\n">>}, SyncRepInt), ok. write_failure(cleanup, _Config) -> @@ -793,15 +898,16 @@ sync_failure(Config) -> rpc:call(Node, ets, insert, [?TEST_HOOKS_TAB,{tester,self()}]), rpc:call(Node, ?MODULE, set_internal_log, [?MODULE,internal_log]), rpc:call(Node, ?MODULE, set_result, [disk_log_sync,ok]), - HState = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]), - LogOpts = maps:get(log_opts, HState), + HState = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]), + LogOpts = maps:get(log_opts, maps:get(handler_state, + maps:get(cb_state,HState))), SyncInt = 500, - ok = rpc:call(Node, logger, set_handler_config, + ok = rpc:call(Node, logger, update_handler_config, [?STANDARD_HANDLER, config, #{filesync_repeat_interval => SyncInt}]), - Info = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]), - SyncInt = maps:get(filesync_repeat_interval, Info), + Info = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]), + SyncInt = maps:get(filesync_repeat_interval, maps:get(cb_state, Info)), ok = log_on_remote_node(Node, "Logged1"), ?check_no_log, @@ -872,7 +978,7 @@ op_switch_to_sync(Config) -> drop_mode_qlen => NumOfReqs+1, flush_qlen => 2*NumOfReqs, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), send_burst({n,NumOfReqs}, seq, {chars,79}, notice), Lines = count_lines(Log), NumOfReqs = Lines, @@ -897,7 +1003,7 @@ op_switch_to_drop(Config) -> drop_mode_qlen => 2, flush_qlen => Procs*NumOfReqs*Bursts, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), %% It sometimes happens that the handler either gets %% the requests in a slow enough pace so that dropping %% never occurs. Therefore, lets generate a number of @@ -943,7 +1049,7 @@ op_switch_to_flush(Config) -> drop_mode_qlen => 300, flush_qlen => 300, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), NumOfReqs = 1500, Procs = 10, Bursts = 10, @@ -985,7 +1091,7 @@ limit_burst_disabled(Config) -> burst_limit_window_time => 2000, drop_mode_qlen => 200, flush_qlen => 300}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), NumOfReqs = 100, send_burst({n,NumOfReqs}, seq, {chars,79}, notice), Logged = count_lines(Log), @@ -1005,7 +1111,7 @@ limit_burst_enabled_one(Config) -> burst_limit_window_time => 2000, drop_mode_qlen => 200, flush_qlen => 300}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), NumOfReqs = 100, send_burst({n,NumOfReqs}, seq, {chars,79}, notice), Logged = count_lines(Log), @@ -1026,7 +1132,7 @@ limit_burst_enabled_period(Config) -> burst_limit_window_time => BurstTWin, drop_mode_qlen => 20000, flush_qlen => 20001}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), Windows = 3, Sent = send_burst({t,BurstTWin*Windows}, seq, {chars,79}, notice), @@ -1046,7 +1152,7 @@ kill_disabled(Config) -> HConfig#{config=>DLHConfig#{overload_kill_enable=>false, overload_kill_qlen=>10, overload_kill_mem_size=>100}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), NumOfReqs = 100, send_burst({n,NumOfReqs}, seq, {chars,79}, notice), Logged = count_lines(Log), @@ -1068,7 +1174,7 @@ qlen_kill_new(Config) -> overload_kill_qlen=>10, overload_kill_mem_size=>Mem0+50000, overload_kill_restart_after=>RestartAfter}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), MRef = erlang:monitor(process, Pid0), NumOfReqs = 100, Procs = 4, @@ -1077,7 +1183,7 @@ qlen_kill_new(Config) -> receive {'DOWN', MRef, _, _, Info} -> case Info of - {shutdown,{overloaded,?MODULE,QLen,Mem}} -> + {shutdown,{overloaded,QLen,Mem}} -> ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]); killed -> ct:pal("Slow shutdown, handler process was killed!", []) @@ -1087,7 +1193,7 @@ qlen_kill_new(Config) -> ok after 5000 -> - Info = logger_disk_log_h:info(?MODULE), + Info = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info]), ct:fail("Handler not dead! It should not have survived this!") end. @@ -1105,7 +1211,7 @@ mem_kill_new(Config) -> overload_kill_qlen=>50000, overload_kill_mem_size=>Mem0+500, overload_kill_restart_after=>RestartAfter}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), MRef = erlang:monitor(process, Pid0), NumOfReqs = 100, Procs = 4, @@ -1114,7 +1220,7 @@ mem_kill_new(Config) -> receive {'DOWN', MRef, _, _, Info} -> case Info of - {shutdown,{overloaded,?MODULE,QLen,Mem}} -> + {shutdown,{overloaded,QLen,Mem}} -> ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]); killed -> ct:pal("Slow shutdown, handler process was killed!", []) @@ -1124,7 +1230,7 @@ mem_kill_new(Config) -> ok after 5000 -> - Info = logger_disk_log_h:info(?MODULE), + Info = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info]), ct:fail("Handler not dead! It should not have survived this!") end. @@ -1139,7 +1245,7 @@ restart_after(Config) -> HConfig#{config=>DLHConfig#{overload_kill_enable=>true, overload_kill_qlen=>10, overload_kill_restart_after=>infinity}}, - ok = logger:set_handler_config(?MODULE, NewHConfig1), + ok = logger:update_handler_config(?MODULE, NewHConfig1), MRef1 = erlang:monitor(process, whereis(h_proc_name())), %% kill handler send_burst({n,100}, {spawn,4,0}, {chars,79}, notice), @@ -1150,7 +1256,7 @@ restart_after(Config) -> ok after 5000 -> - Info1 = logger_std_h:info(?MODULE), + Info1 = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info1]), ct:fail("Handler not dead! It should not have survived this!") end, @@ -1161,7 +1267,7 @@ restart_after(Config) -> HConfig#{config=>DLHConfig#{overload_kill_enable=>true, overload_kill_qlen=>10, overload_kill_restart_after=>RestartAfter}}, - ok = logger:set_handler_config(?MODULE, NewHConfig2), + ok = logger:update_handler_config(?MODULE, NewHConfig2), Pid0 = whereis(h_proc_name()), MRef2 = erlang:monitor(process, Pid0), %% kill handler @@ -1174,7 +1280,7 @@ restart_after(Config) -> ok after 5000 -> - Info2 = logger_std_h:info(?MODULE), + Info2 = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info2]), ct:fail("Handler not dead! It should not have survived this!") end, @@ -1194,12 +1300,16 @@ handler_requests_under_load(Config) -> drop_mode_qlen => 1000, flush_qlen => 2000, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), - Pid = spawn_link(fun() -> send_requests(?MODULE, 1, [{filesync,[]}, - {info,[]}, - {reset,[]}, - {change_config,[]}]) - end), + ok = logger:update_handler_config(?MODULE, NewHConfig), + Pid = spawn_link( + fun() -> send_requests(1,[{logger_disk_log_h,filesync,[?MODULE],[]}, + {logger_olp,info,[h_proc_name()],[]}, + {logger_olp,reset,[h_proc_name()],[]}, + {logger,update_handler_config, + [?MODULE, config, + #{overload_kill_enable => false}], + []}]) + end), Procs = 100, Sent = Procs * send_burst({n,5000}, {spawn,Procs,10}, {chars,79}, notice), Pid ! {self(),finish}, @@ -1211,29 +1321,22 @@ handler_requests_under_load(Config) -> [E || E <- Res, is_tuple(E) andalso (element(1,E) == error)] end, - Errors = [{Req,FindError(Res)} || {Req,Res} <- ReqResult], - NoOfReqs = lists:foldl(fun({_,Res}, N) -> N + length(Res) end, 0, ReqResult), + Errors = [{Func,FindError(Res)} || {_,Func,_,Res} <- ReqResult], + NoOfReqs = lists:foldl(fun({_,_,_,Res}, N) -> N + length(Res) end, + 0, ReqResult), ct:pal("~w requests made. Errors: ~n~p", [NoOfReqs,Errors]), ok = file_delete(Log). handler_requests_under_load(cleanup, _Config) -> ok = stop_handler(?MODULE). -send_requests(HName, TO, Reqs = [{Req,Res}|Rs]) -> +send_requests(TO, Reqs = [{Mod,Func,Args,Res}|Rs]) -> receive {From,finish} -> From ! {self(),Reqs} after TO -> - Result = - case Req of - change_config -> - logger:set_handler_config(HName, logger_disk_log_h, - #{overload_kill_enable => - false}); - Func -> - logger_disk_log_h:Func(HName) - end, - send_requests(HName, TO, Rs ++ [{Req,[Result|Res]}]) + Result = apply(Mod,Func,Args), + send_requests(TO, Rs ++ [{Mod,Func,Args,[Result|Res]}]) end. %%%----------------------------------------------------------------- @@ -1351,15 +1454,6 @@ format(Msg,Tag) -> erlang:display(Error), exit(Error). -remove(Handler, LogName) -> - logger_disk_log_h:remove(Handler, LogName), - HState = #{log_names := Logs} = logger_disk_log_h:info(), - false = maps:is_key(LogName, HState), - false = lists:member(LogName, Logs), - false = logger_config:exist(?LOGGER_TABLE, LogName), - {error,no_such_log} = disk_log:info(LogName), - ok. - start_and_add(Name, Config, LogOpts) -> HConfig = maps:get(config, Config, #{}), HConfig1 = maps:merge(HConfig, LogOpts), @@ -1486,7 +1580,9 @@ start_tracer(Trace,Expected) -> ok. tpl([{M,F,A}|Trace]) -> - {ok,Match} = dbg:tpl(M,F,A,c), + tpl([{{M,F,A},c}|Trace]); +tpl([{{M,F,A},MS}|Trace]) -> + {ok,Match} = dbg:tpl(M,F,A,MS), case lists:keyfind(matched,1,Match) of {_,_,1} -> ok; @@ -1499,10 +1595,10 @@ tpl([{M,F,A}|Trace]) -> tpl([]) -> ok. -tracer({trace,_,call,{logger_disk_log_h,handle_cast,[Op|_]},Caller}, +tracer({trace,_,call,{logger_h_common,handle_cast,[Op|_]},Caller}, {Pid,[{Mod,Func,Op}|Expected]}) -> maybe_tracer_done(Pid,Expected,{Mod,Func,Op},Caller); -tracer({trace,_,call,{Mod=disk_log,Func=blog,[_,Data]},Caller}, {Pid,[{Mod,Func,Data}|Expected]}) -> +tracer({trace,_,call,{Mod=logger_disk_log_h,Func=disk_log_write,[_,_,Data]},Caller}, {Pid,[{Mod,Func,Data}|Expected]}) -> maybe_tracer_done(Pid,Expected,{Mod,Func,Data},Caller); tracer({trace,_,call,{Mod,Func,_},Caller}, {Pid,[{Mod,Func}|Expected]}) -> maybe_tracer_done(Pid,Expected,{Mod,Func},Caller); diff --git a/lib/kernel/test/logger_env_var_SUITE.erl b/lib/kernel/test/logger_env_var_SUITE.erl index e8d1a313dc..9d2ad11be8 100644 --- a/lib/kernel/test/logger_env_var_SUITE.erl +++ b/lib/kernel/test/logger_env_var_SUITE.erl @@ -59,7 +59,8 @@ groups() -> logger_undefined, logger_many_handlers_default_first, logger_many_handlers_default_last, - logger_many_handlers_default_last_broken_filter + logger_many_handlers_default_last_broken_filter, + logger_proxy ]}, {bad,[],[bad_error_logger, bad_level, @@ -541,6 +542,19 @@ logger_many_handlers(Config, Env, LogErr, LogInfo, NumProgress) -> ok. +logger_proxy(Config) -> + %% assume current node runs with default settings + DefOpts = logger_olp:get_opts(logger_proxy), + {ok,_,Node} = setup(Config, + [{logger,[{proxy,#{sync_mode_qlen=>0, + drop_mode_qlen=>2}}]}]), + Expected = DefOpts#{sync_mode_qlen:=0, + drop_mode_qlen:=2}, + Expected = rpc:call(Node,logger_olp,get_opts,[logger_proxy]), + Expected = rpc:call(Node,logger,get_proxy_config,[]), + + ok. + sasl_compatible_false(Config) -> Log = file(Config,?FUNCTION_NAME), {ok,_,Node} = setup(Config, diff --git a/lib/kernel/test/logger_olp_SUITE.erl b/lib/kernel/test/logger_olp_SUITE.erl new file mode 100644 index 0000000000..ea3eec89f5 --- /dev/null +++ b/lib/kernel/test/logger_olp_SUITE.erl @@ -0,0 +1,90 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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(logger_olp_SUITE). + +-compile(export_all). + +-include_lib("kernel/src/logger_olp.hrl"). + +suite() -> + [{timetrap,{seconds,30}}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_Group, Config) -> + Config. + +end_per_group(_Group, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(Case, Config) -> + try apply(?MODULE,Case,[cleanup,Config]) + catch error:undef -> ok + end, + ok. + +groups() -> + []. + +all() -> + [idle_timer]. + +%%%----------------------------------------------------------------- +%%% Test cases +idle_timer(_Config) -> + {ok,_Pid,Olp} = logger_olp:start_link(?MODULE,?MODULE,self(),#{}), + [logger_olp:load(Olp,{msg,N}) || N<-lists:seq(1,3)], + timer:sleep(?IDLE_DETECT_TIME*2), + [{load,{msg,1}}, + {load,{msg,2}}, + {load,{msg,3}}, + {notify,idle}] = test_server:messages_get(), + logger_olp:cast(Olp,hello), + timer:sleep(?IDLE_DETECT_TIME*2), + [{cast,hello}] = test_server:messages_get(), + ok. +idle_timer(cleanup,_Config) -> + unlink(whereis(?MODULE)), + logger_olp:stop(?MODULE), + ok. + +%%%----------------------------------------------------------------- +%%% Olp callbacks +init(P) -> + {ok,P}. + +handle_load(M,P) -> + P ! {load,M}, + P. + +handle_cast(M,P) -> + P ! {cast,M}, + {noreply,P}. + +notify(N,P) -> + P ! {notify,N}, + P. diff --git a/lib/kernel/test/logger_proxy_SUITE.erl b/lib/kernel/test/logger_proxy_SUITE.erl new file mode 100644 index 0000000000..777531e4ed --- /dev/null +++ b/lib/kernel/test/logger_proxy_SUITE.erl @@ -0,0 +1,274 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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(logger_proxy_SUITE). + +-compile(export_all). + +%% -include_lib("common_test/include/ct.hrl"). +%% -include_lib("kernel/include/logger.hrl"). +%% -include_lib("kernel/src/logger_internal.hrl"). + +%% -define(str,"Log from "++atom_to_list(?FUNCTION_NAME)++ +%% ":"++integer_to_list(?LINE)). +%% -define(map_rep,#{function=>?FUNCTION_NAME, line=>?LINE}). +%% -define(keyval_rep,[{function,?FUNCTION_NAME}, {line,?LINE}]). + +%% -define(MY_LOC(N),#{mfa=>{?MODULE,?FUNCTION_NAME,?FUNCTION_ARITY}, +%% file=>?FILE, line=>?LINE-N}). + +%% -define(TRY(X), my_try(fun() -> X end)). + + +-define(HNAME,list_to_atom(lists:concat([?MODULE,"_",?FUNCTION_NAME]))). +-define(LOC,#{mfa=>{?MODULE,?FUNCTION_NAME,?FUNCTION_ARITY},line=>?LINE}). +-define(ENSURE_TIME,5000). + +suite() -> + [{timetrap,{seconds,30}}, + {ct_hooks,[logger_test_lib]}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_Group, Config) -> + Config. + +end_per_group(_Group, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(Case, Config) -> + try apply(?MODULE,Case,[cleanup,Config]) + catch error:undef -> ok + end, + ok. + +groups() -> + []. + +all() -> + [basic, + emulator, + remote, + remote_emulator, + config, + restart_after, + terminate]. + +%%%----------------------------------------------------------------- +%%% Test cases +basic(_Config) -> + ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}), + logger_proxy ! {log,notice,"Log from: ~p; ~p",[?FUNCTION_NAME,?LINE],L1=?LOC}, + ok = ensure(L1), + logger_proxy ! {log,notice,[{test_case,?FUNCTION_NAME},{line,?LINE}],L2=?LOC}, + ok = ensure(L2), + logger_proxy:log({remote,node(),{log,notice, + "Log from: ~p; ~p", + [?FUNCTION_NAME,?LINE], + L3=?LOC}}), + ok = ensure(L3), + logger_proxy:log({remote,node(),{log,notice, + [{test_case,?FUNCTION_NAME}, + {line,?LINE}], + L4=?LOC}}), + ok = ensure(L4), + ok. +basic(cleanup,_Config) -> + ok = logger:remove_handler(?HNAME). + +emulator(_Config) -> + ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}), + Pid = spawn(fun() -> erlang:error(some_reason) end), + ok = ensure(#{pid=>Pid}), + ok. +emulator(cleanup,_Config) -> + ok = logger:remove_handler(?HNAME). + +remote(Config) -> + {ok,_,Node} = logger_test_lib:setup(Config,[{logger,[{proxy,#{}}]}]), + ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}), + L1 = ?LOC, spawn(Node,fun() -> logger:notice("Log from ~p; ~p",[?FUNCTION_NAME,?LINE],L1) end), + ok = ensure(L1), + L2 = ?LOC, spawn(Node,fun() -> logger:notice([{test_case,?FUNCTION_NAME},{line,?LINE}],L2) end), + ok = ensure(L2), + ok. +remote(cleanup,_Config) -> + ok = logger:remove_handler(?HNAME). + +remote_emulator(Config) -> + {ok,_,Node} = logger_test_lib:setup(Config,[{logger,[{proxy,#{}}]}]), + ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}), + Pid = spawn(Node,fun() -> erlang:error(some_reason) end), + ok = ensure(#{pid=>Pid}), + ok. +remote_emulator(cleanup,_Config) -> + ok = logger:remove_handler(?HNAME). + +config(_Config) -> + C1 = #{sync_mode_qlen:=SQ, + drop_mode_qlen:=DQ} = logger:get_proxy_config(), + C1 = logger_olp:get_opts(logger_proxy), + + %% Update the existing config with these two values + SQ1 = SQ+1, + DQ1 = DQ+1, + ok = logger:update_proxy_config(#{sync_mode_qlen=>SQ1, + drop_mode_qlen=>DQ1}), + C2 = logger:get_proxy_config(), % reads from ets table + C2 = logger_olp:get_opts(logger_proxy), % ensure consistency with process opts + C2 = C1#{sync_mode_qlen:=SQ1, + drop_mode_qlen:=DQ1}, + + %% Update the existing again with only one value + SQ2 = SQ+2, + ok = logger:update_proxy_config(#{sync_mode_qlen=>SQ2}), + C3 = logger:get_proxy_config(), + C3 = logger_olp:get_opts(logger_proxy), + C3 = C2#{sync_mode_qlen:=SQ2}, + + %% Set the config, i.e. merge with defaults + ok = logger:set_proxy_config(#{sync_mode_qlen=>SQ1}), + C4 = logger:get_proxy_config(), + C4 = logger_olp:get_opts(logger_proxy), + C4 = C1#{sync_mode_qlen:=SQ1}, + + %% Reset to default + ok = logger:set_proxy_config(#{}), + C5 = logger:get_proxy_config(), + C5 = logger_olp:get_opts(logger_proxy), + C5 = logger_proxy:get_default_config(), + + %% Errors + {error,{invalid_olp_config,_}} = + logger:set_proxy_config(#{faulty_key=>1}), + {error,{invalid_olp_config,_}} = + logger:set_proxy_config(#{sync_mode_qlen=>infinity}), + {error,{invalid_config,[]}} = logger:set_proxy_config([]), + + {error,{invalid_olp_config,_}} = + logger:update_proxy_config(#{faulty_key=>1}), + {error,{invalid_olp_config,_}} = + logger:update_proxy_config(#{sync_mode_qlen=>infinity}), + {error,{invalid_config,[]}} = logger:update_proxy_config([]), + + C5 = logger:get_proxy_config(), + C5 = logger_olp:get_opts(logger_proxy), + + ok. +config(cleanup,_Config) -> + _ = logger:set_logger_proxy(logger_proxy:get_default_config()), + ok. + +restart_after(_Config) -> + Restart = 3000, + ok = logger:update_proxy_config(#{overload_kill_enable => true, + overload_kill_qlen => 10, + overload_kill_restart_after => Restart}), + Proxy = whereis(logger_proxy), + Proxy = erlang:system_info(system_logger), + ProxyConfig = logger:get_proxy_config(), + ProxyConfig = logger_olp:get_opts(logger_proxy), + + Ref = erlang:monitor(process,Proxy), + spawn(fun() -> + [logger_proxy ! {log,debug, + [{test_case,?FUNCTION_NAME}, + {line,?LINE}], + ?LOC} || _ <- lists:seq(1,100)] + end), + receive + {'DOWN',Ref,_,_,_Reason} -> + undefined = erlang:system_info(system_logger), + timer:sleep(Restart), + poll_restarted(10) + after 5000 -> + ct:fail(proxy_not_terminated) + end, + + Proxy1 = whereis(logger_proxy), + Proxy1 = erlang:system_info(system_logger), + ProxyConfig = logger:get_proxy_config(), + ProxyConfig = logger_olp:get_opts(logger_proxy), + + ok. +restart_after(cleanup,_Config) -> + _ = logger:set_logger_proxy(logger_proxy:get_default_config()), + ok. + +%% Test that system_logger flag is set to logger process if +%% logger_proxy terminates for other reason than overloaded. +terminate(_Config) -> + Logger = whereis(logger), + Proxy = whereis(logger_proxy), + Proxy = erlang:system_info(system_logger), + ProxyConfig = logger:get_proxy_config(), + ProxyConfig = logger_olp:get_opts(logger_proxy), + + Ref = erlang:monitor(process,Proxy), + ok = logger_olp:stop(Proxy), + receive + {'DOWN',Ref,_,_,_Reason} -> + Logger = erlang:system_info(system_logger), + logger_proxy:restart(), + poll_restarted(10) + after 5000 -> + ct:fail(proxy_not_terminated) + end, + + Proxy1 = whereis(logger_proxy), + Proxy1 = erlang:system_info(system_logger), + ProxyConfig = logger:get_proxy_config(), + ProxyConfig = logger_olp:get_opts(logger_proxy), + + ok. + +%%%----------------------------------------------------------------- +%%% Internal functions + +poll_restarted(0) -> + ct:fail(proxy_not_restarted); +poll_restarted(N) -> + timer:sleep(1000), + case whereis(logger_proxy) of + undefined -> + poll_restarted(N-1); + _Pid -> + ok + end. + +%% Logger handler callback +log(#{meta:=Meta},#{config:=Pid}) -> + Pid ! {logged,Meta}. + +%% Check that the log from the logger callback function log/2 is received +ensure(Match) -> + receive {logged,Meta} -> + case maps:with(maps:keys(Match),Meta) of + Match -> ok; + _NoMatch -> {error,Match,Meta,test_server:messages_get()} + end + after ?ENSURE_TIME -> {error,Match,test_server:messages_get()} + end. diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl index 3426567bbf..484d914ec3 100644 --- a/lib/kernel/test/logger_std_h_SUITE.erl +++ b/lib/kernel/test/logger_std_h_SUITE.erl @@ -25,10 +25,15 @@ -include_lib("kernel/include/logger.hrl"). -include_lib("kernel/src/logger_internal.hrl"). -include_lib("kernel/src/logger_h_common.hrl"). +-include_lib("kernel/src/logger_olp.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("kernel/include/file.hrl"). --define(check_no_log, [] = test_server:messages_get()). +-define(check_no_log, + begin + timer:sleep(?IDLE_DETECT_TIME*2), + [] = test_server:messages_get() + end). -define(check(Expected), receive {log,Expected} -> @@ -108,13 +113,13 @@ all() -> add_remove_instance_file1, add_remove_instance_file2, default_formatter, + filter_config, errors, formatter_fail, config_fail, crash_std_h_to_file, crash_std_h_to_disk_log, bad_input, - info_and_reset, reconfig, file_opts, sync, @@ -135,11 +140,12 @@ all() -> mem_kill_new, mem_kill_std, restart_after, - handler_requests_under_load + handler_requests_under_load, + recreate_deleted_log ]. add_remove_instance_tty(_Config) -> - {error,{handler_not_added,{invalid_config,logger_std_h,{type,tty}}}} = + {error,{handler_not_added,{invalid_config,logger_std_h,#{type:=tty}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{type => tty}, filter_default=>log, @@ -204,6 +210,20 @@ default_formatter(_Config) -> match = re:run(Msg,"=NOTICE REPORT====.*\n"++M1,[{capture,none}]), ok. +filter_config(_Config) -> + ok = logger:add_handler(?MODULE,logger_std_h,#{}), + {ok,#{config:=HConfig}=Config} = logger:get_handler_config(?MODULE), + HConfig = maps:without([olp],HConfig), + + FakeFullHConfig = HConfig#{olp=>{regname,self(),erlang:make_ref()}}, + #{config:=HConfig} = + logger_std_h:filter_config(Config#{config=>FakeFullHConfig}), + ok. + +filter_config(cleanup,_Config) -> + logger:remove_handler(?MODULE), + ok. + errors(Config) -> Dir = ?config(priv_dir,Config), Log = filename:join(Dir,?FUNCTION_NAME), @@ -219,7 +239,7 @@ errors(Config) -> {error, {handler_not_added, - {invalid_config,logger_std_h,{type,faulty_type}}}} = + {invalid_config,logger_std_h,#{type:=faulty_type}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{type => faulty_type}}), @@ -230,13 +250,13 @@ errors(Config) -> _ -> NoDir = lists:concat(["/",?MODULE,"_dir"]), {error, - {handler_not_added,{{open_failed,NoDir,eacces},_}}} = + {handler_not_added,{open_failed,NoDir,eacces}}} = logger:add_handler(myh2,logger_std_h, #{config=>#{type=>{file,NoDir}}}) end, {error, - {handler_not_added,{{open_failed,Log,_},_}}} = + {handler_not_added,{open_failed,Log,_}}} = logger:add_handler(myh3,logger_std_h, #{config=>#{type=>{file,Log,[bad_file_opt]}}}), @@ -293,25 +313,27 @@ formatter_fail(cleanup,_Config) -> logger:remove_handler(?MODULE). config_fail(_Config) -> - {error,{handler_not_added,{invalid_config,logger_std_h,{bad,bad}}}} = + {error,{handler_not_added,{invalid_config,logger_std_h,#{bad:=bad}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{bad => bad}, filter_default=>log, formatter=>{?MODULE,self()}}), {error,{handler_not_added,{invalid_config,logger_std_h, - {restart_type,bad}}}} = + #{restart_type:=bad}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{restart_type => bad}, filter_default=>log, formatter=>{?MODULE,self()}}), - {error,{handler_not_added,{invalid_levels,{_,1,_}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=1}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{drop_mode_qlen=>1}}), - {error,{handler_not_added,{invalid_levels,{43,42,_}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{sync_mode_qlen:=43, + drop_mode_qlen:=42}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{sync_mode_qlen=>43, drop_mode_qlen=>42}}), - {error,{handler_not_added,{invalid_levels,{_,43,42}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=43, + flush_qlen:=42}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{drop_mode_qlen=>43, flush_qlen=>42}}), @@ -319,18 +341,24 @@ config_fail(_Config) -> ok = logger:add_handler(?MODULE,logger_std_h, #{filter_default=>log, formatter=>{?MODULE,self()}}), - {error,{illegal_config_change,_,_}} = + {error,{illegal_config_change,logger_std_h,#{type:=_},#{type:=_}}} = logger:set_handler_config(?MODULE,config, #{type=>{file,"file"}}), - {error,{illegal_config_change,_,_}} = - logger:set_handler_config(?MODULE,id,bad), - {error,{invalid_levels,_}} = + + {error,{invalid_olp_levels,_}} = logger:set_handler_config(?MODULE,config, #{sync_mode_qlen=>100, flush_qlen=>99}), - {error,{invalid_config,logger_std_h,{filesync_rep_int,2000}}} = + {error,{invalid_config,logger_std_h,#{filesync_rep_int:=2000}}} = logger:set_handler_config(?MODULE, config, #{filesync_rep_int => 2000}), + + %% Read-only fields may (accidentially) be included in the change, + %% but it won't take effect + {ok,C} = logger:get_handler_config(?MODULE), + ok = logger:set_handler_config(?MODULE,config,#{olp=>dummyvalue}), + {ok,C} = logger:get_handler_config(?MODULE), + ok. config_fail(cleanup,_Config) -> @@ -396,10 +424,13 @@ crash_std_h(Config,Func,Var,Type,Log) -> %% logger would send the log event to the logger process here instead %% of logging it itself. log_on_remote_node(Node,Msg) -> + Pid = self(), _ = spawn_link(Node, fun() -> erlang:group_leader(whereis(user),self()), - logger:notice(Msg) + logger:notice(Msg), + Pid ! done end), + receive done -> ok end, ok. @@ -427,14 +458,7 @@ sync_and_read(Node,file,Log) -> end. bad_input(_Config) -> - {error,{badarg,{filesync,["BadType"]}}} = logger_std_h:filesync("BadType"), - {error,{badarg,{info,["BadType"]}}} = logger_std_h:info("BadType"), - {error,{badarg,{reset,["BadType"]}}} = logger_std_h:reset("BadType"). - - -info_and_reset(_Config) -> - #{id := ?STANDARD_HANDLER} = logger_std_h:info(?STANDARD_HANDLER), - ok = logger_std_h:reset(?STANDARD_HANDLER). + {error,{badarg,{filesync,["BadType"]}}} = logger_std_h:filesync("BadType"). reconfig(Config) -> Dir = ?config(priv_dir,Config), @@ -444,9 +468,10 @@ reconfig(Config) -> filter_default=>log, filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), formatter=>{?MODULE,self()}}), - #{id := ?MODULE, - type := standard_io, - file_ctrl_pid := FileCtrlPid, + #{%id := ?MODULE, + cb_state:=#{handler_state := #{type := standard_io, + file_ctrl_pid := FileCtrlPid}, + filesync_repeat_interval := no_repeat}, sync_mode_qlen := ?SYNC_MODE_QLEN, drop_mode_qlen := ?DROP_MODE_QLEN, flush_qlen := ?FLUSH_QLEN, @@ -456,9 +481,25 @@ reconfig(Config) -> overload_kill_enable := ?OVERLOAD_KILL_ENABLE, overload_kill_qlen := ?OVERLOAD_KILL_QLEN, overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE, - overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER, - filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL} = - logger_std_h:info(?MODULE), + overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER} = + logger_olp:info(h_proc_name()), + + {ok, + #{config:= + #{type := standard_io, + sync_mode_qlen := ?SYNC_MODE_QLEN, + drop_mode_qlen := ?DROP_MODE_QLEN, + flush_qlen := ?FLUSH_QLEN, + burst_limit_enable := ?BURST_LIMIT_ENABLE, + burst_limit_max_count := ?BURST_LIMIT_MAX_COUNT, + burst_limit_window_time := ?BURST_LIMIT_WINDOW_TIME, + overload_kill_enable := ?OVERLOAD_KILL_ENABLE, + overload_kill_qlen := ?OVERLOAD_KILL_QLEN, + overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE, + overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER, + filesync_repeat_interval := no_repeat} = + DefaultHConf}} + = logger:get_handler_config(?MODULE), ok = logger:set_handler_config(?MODULE, config, #{sync_mode_qlen => 1, @@ -471,10 +512,11 @@ reconfig(Config) -> overload_kill_qlen => 100000, overload_kill_mem_size => 10000000, overload_kill_restart_after => infinity, - filesync_repeat_interval => no_repeat}), - #{id := ?MODULE, - type := standard_io, - file_ctrl_pid := FileCtrlPid, + filesync_repeat_interval => 5000}), + #{%id := ?MODULE, + cb_state := #{handler_state := #{type := standard_io, + file_ctrl_pid := FileCtrlPid}, + filesync_repeat_interval := no_repeat}, sync_mode_qlen := 1, drop_mode_qlen := 2, flush_qlen := 3, @@ -484,8 +526,77 @@ reconfig(Config) -> overload_kill_enable := true, overload_kill_qlen := 100000, overload_kill_mem_size := 10000000, - overload_kill_restart_after := infinity, - filesync_repeat_interval := no_repeat} = logger_std_h:info(?MODULE), + overload_kill_restart_after := infinity} = logger_olp:info(h_proc_name()), + + {ok,#{config := + #{type := standard_io, + sync_mode_qlen := 1, + drop_mode_qlen := 2, + flush_qlen := 3, + burst_limit_enable := false, + burst_limit_max_count := 10, + burst_limit_window_time := 10, + overload_kill_enable := true, + overload_kill_qlen := 100000, + overload_kill_mem_size := 10000000, + overload_kill_restart_after := infinity, + filesync_repeat_interval := no_repeat} = HConf}} = + logger:get_handler_config(?MODULE), + + ok = logger:update_handler_config(?MODULE, config, + #{flush_qlen => ?FLUSH_QLEN}), + {ok,#{config:=C1}} = logger:get_handler_config(?MODULE), + ct:log("C1: ~p",[C1]), + C1 = HConf#{flush_qlen => ?FLUSH_QLEN}, + + ok = logger:set_handler_config(?MODULE, config, #{sync_mode_qlen => 1}), + {ok,#{config:=C2}} = logger:get_handler_config(?MODULE), + ct:log("C2: ~p",[C2]), + C2 = DefaultHConf#{sync_mode_qlen => 1}, + + ok = logger:set_handler_config(?MODULE, config, #{drop_mode_qlen => 100}), + {ok,#{config:=C3}} = logger:get_handler_config(?MODULE), + ct:log("C3: ~p",[C3]), + C3 = DefaultHConf#{drop_mode_qlen => 100}, + + ok = logger:update_handler_config(?MODULE, config, #{sync_mode_qlen => 1}), + {ok,#{config:=C4}} = logger:get_handler_config(?MODULE), + ct:log("C4: ~p",[C4]), + C4 = DefaultHConf#{sync_mode_qlen => 1, + drop_mode_qlen => 100}, + + ok = logger:remove_handler(?MODULE), + + File = filename:join(Dir,lists:concat([?FUNCTION_NAME,".log"])), + ok = logger:add_handler(?MODULE, + logger_std_h, + #{config => #{type => {file,File}}, + filter_default=>log, + filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), + formatter=>{?MODULE,self()}}), + + {ok,#{config:=#{filesync_repeat_interval:=FSI}=FileHConfig}} = + logger:get_handler_config(?MODULE), + ok = logger:update_handler_config(?MODULE,config, + #{filesync_repeat_interval=>FSI+2000}), + {ok,#{config:=C5}} = logger:get_handler_config(?MODULE), + ct:log("C5: ~p",[C5]), + C5 = FileHConfig#{filesync_repeat_interval=>FSI+2000}, + + %% You are not allowed to actively set 'type' in runtime, since + %% this is a write once field. + {error, {illegal_config_change,logger_std_h,_,_}} = + logger:set_handler_config(?MODULE,config,#{type=>standard_io}), + {ok,#{config:=C6}} = logger:get_handler_config(?MODULE), + ct:log("C6: ~p",[C6]), + C6 = C5, + + %% ... but if you don't specify 'type', then set_handler_config shall + %% NOT reset it to its default value + ok = logger:set_handler_config(?MODULE,config,#{sync_mode_qlen=>1}), + {ok,#{config:=C7}} = logger:get_handler_config(?MODULE), + ct:log("C7: ~p",[C7]), + C7 = FileHConfig#{sync_mode_qlen=>1}, ok. reconfig(cleanup, _Config) -> @@ -497,7 +608,7 @@ file_opts(Config) -> Log = filename:join(Dir, lists:concat([?FUNCTION_NAME,".log"])), BadFileOpts = [raw], BadType = {file,Log,BadFileOpts}, - {error,{handler_not_added,{{open_failed,Log,enoent},_}}} = + {error,{handler_not_added,{open_failed,Log,enoent}}} = logger:add_handler(?MODULE, logger_std_h, #{config => #{type => BadType}}), @@ -510,7 +621,9 @@ file_opts(Config) -> filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), formatter=>{?MODULE,self()}}), - #{type := OkType} = logger_std_h:info(?MODULE), + #{cb_state := #{handler_state := #{type := OkType}}} = + logger_olp:info(h_proc_name()), + {ok,#{config := #{type := OkType}}} = logger:get_handler_config(?MODULE), logger:notice(M1=?msg,?domain), ?check(M1), B1 = ?bin(M1), @@ -533,10 +646,8 @@ sync(Config) -> %% check repeated filesync happens start_tracer([{logger_std_h, write_to_dev, 5}, - {logger_std_h, sync_dev, 4}, {file, datasync, 1}], [{logger_std_h, write_to_dev, <<"first\n">>}, - {logger_std_h, sync_dev}, {file,datasync}]), logger:notice("first", ?domain), @@ -545,10 +656,8 @@ sync(Config) -> %% check that explicit filesync is only done once start_tracer([{logger_std_h, write_to_dev, 5}, - {logger_std_h, sync_dev, 4}, {file, datasync, 1}], [{logger_std_h, write_to_dev, <<"second\n">>}, - {logger_std_h, sync_dev}, {file,datasync}, {no_more,500} ]), @@ -561,43 +670,39 @@ sync(Config) -> %% check that if there's no repeated filesync active, %% a filesync is still performed when handler goes idle - logger:set_handler_config(?MODULE, config, - #{filesync_repeat_interval => no_repeat}), - no_repeat = maps:get(filesync_repeat_interval, logger_std_h:info(?MODULE)), - %% The following timer is to make sure the time from last log - %% ("second") to next ("third") is long enough, so the a flush is - %% triggered by the idle timeout between "thrid" and "fourth". - timer:sleep(?IDLE_DETECT_TIME_MSEC*2), + ok = logger:update_handler_config(?MODULE, config, + #{filesync_repeat_interval => no_repeat}), + no_repeat = maps:get(filesync_repeat_interval, + maps:get(cb_state, logger_olp:info(h_proc_name()))), start_tracer([{logger_std_h, write_to_dev, 5}, - {logger_std_h, sync_dev, 4}, {file, datasync, 1}], [{logger_std_h, write_to_dev, <<"third\n">>}, - {logger_std_h, sync_dev}, {file,datasync}, {logger_std_h, write_to_dev, <<"fourth\n">>}, - {logger_std_h, sync_dev}, {file,datasync}]), logger:notice("third", ?domain), %% wait for automatic filesync - timer:sleep(?IDLE_DETECT_TIME_MSEC*2), + timer:sleep(?IDLE_DETECT_TIME*2), logger:notice("fourth", ?domain), %% wait for automatic filesync - check_tracer(?IDLE_DETECT_TIME_MSEC*2), + check_tracer(?IDLE_DETECT_TIME*2), %% switch repeated filesync on and verify that the looping works SyncInt = 1000, WaitT = 4500, - OneSync = {logger_std_h,handle_cast,repeated_filesync}, - %% receive 1 initial repeated_filesync, then 1 per sec - start_tracer([{logger_std_h,handle_cast,2}], - [OneSync || _ <- lists:seq(1, 1 + trunc(WaitT/SyncInt))]), - - logger:set_handler_config(?MODULE, config, - #{filesync_repeat_interval => SyncInt}), - SyncInt = maps:get(filesync_repeat_interval, logger_std_h:info(?MODULE)), + OneSync = {logger_h_common,handle_cast,repeated_filesync}, + %% receive 1 repeated_filesync per sec + start_tracer([{{logger_h_common,handle_cast,2}, + [{[repeated_filesync,'_'],[],[]}]}], + [OneSync || _ <- lists:seq(1, trunc(WaitT/SyncInt))]), + + ok = logger:update_handler_config(?MODULE, config, + #{filesync_repeat_interval => SyncInt}), + SyncInt = maps:get(filesync_repeat_interval, + maps:get(cb_state,logger_olp:info(h_proc_name()))), timer:sleep(WaitT), - logger:set_handler_config(?MODULE, config, - #{filesync_repeat_interval => no_repeat}), + ok = logger:update_handler_config(?MODULE, config, + #{filesync_repeat_interval => no_repeat}), check_tracer(100), ok. sync(cleanup, _Config) -> @@ -652,11 +757,9 @@ sync_failure(Config) -> rpc:call(Node, ?MODULE, set_result, [file_datasync,ok]), SyncInt = 500, - ok = rpc:call(Node, logger, set_handler_config, + ok = rpc:call(Node, logger, update_handler_config, [?STANDARD_HANDLER, config, #{filesync_repeat_interval => SyncInt}]), - Info = rpc:call(Node, logger_std_h, info, [?STANDARD_HANDLER]), - SyncInt = maps:get(filesync_repeat_interval, Info), ok = log_on_remote_node(Node, "Logged1"), ?check_no_log, @@ -718,7 +821,7 @@ op_switch_to_sync_file(Config) -> drop_mode_qlen => NumOfReqs+1, flush_qlen => 2*NumOfReqs, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), %% TRecvPid = start_op_trace(), send_burst({n,NumOfReqs}, seq, {chars,79}, notice), Lines = count_lines(Log), @@ -747,7 +850,7 @@ op_switch_to_sync_tty(Config) -> drop_mode_qlen => NumOfReqs+1, flush_qlen => 2*NumOfReqs, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), send_burst({n,NumOfReqs}, seq, {chars,79}, notice), ok. op_switch_to_sync_tty(cleanup, _Config) -> @@ -770,7 +873,7 @@ op_switch_to_drop_file(Config) -> flush_qlen => Procs*NumOfReqs*Bursts, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), %% It sometimes happens that the handler gets the %% requests in a slow enough pace so that dropping %% never occurs. Therefore, lets generate a number of @@ -807,7 +910,7 @@ op_switch_to_drop_tty(Config) -> flush_qlen => Procs*NumOfReqs+1, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), send_burst({n,NumOfReqs}, {spawn,Procs,0}, {chars,79}, notice), ok. op_switch_to_drop_tty(cleanup, _Config) -> @@ -832,7 +935,7 @@ op_switch_to_flush_file(Config) -> drop_mode_qlen => 300, flush_qlen => 300, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), NumOfReqs = 1500, Procs = 10, Bursts = 10, @@ -879,7 +982,7 @@ op_switch_to_flush_tty(Config) -> drop_mode_qlen => 100, flush_qlen => 100, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), NumOfReqs = 1000, Procs = 100, send_burst({n,NumOfReqs}, {spawn,Procs,0}, {chars,79}, notice), @@ -895,7 +998,7 @@ limit_burst_disabled(Config) -> burst_limit_window_time => 2000, drop_mode_qlen => 200, flush_qlen => 300}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), NumOfReqs = 100, send_burst({n,NumOfReqs}, seq, {chars,79}, notice), Logged = count_lines(Log), @@ -915,7 +1018,7 @@ limit_burst_enabled_one(Config) -> burst_limit_window_time => 2000, drop_mode_qlen => 200, flush_qlen => 300}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), NumOfReqs = 100, send_burst({n,NumOfReqs}, seq, {chars,79}, notice), Logged = count_lines(Log), @@ -936,7 +1039,7 @@ limit_burst_enabled_period(Config) -> burst_limit_window_time => BurstTWin, drop_mode_qlen => 20000, flush_qlen => 20001}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), Windows = 3, Sent = send_burst({t,BurstTWin*Windows}, seq, {chars,79}, notice), @@ -956,7 +1059,7 @@ kill_disabled(Config) -> HConfig#{config=>StdHConfig#{overload_kill_enable=>false, overload_kill_qlen=>10, overload_kill_mem_size=>100}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), NumOfReqs = 100, send_burst({n,NumOfReqs}, seq, {chars,79}, notice), Logged = count_lines(Log), @@ -977,7 +1080,7 @@ qlen_kill_new(Config) -> overload_kill_qlen=>10, overload_kill_mem_size=>Mem0+50000, overload_kill_restart_after=>RestartAfter}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), MRef = erlang:monitor(process, Pid0), NumOfReqs = 100, Procs = 4, @@ -986,7 +1089,7 @@ qlen_kill_new(Config) -> receive {'DOWN', MRef, _, _, Info} -> case Info of - {shutdown,{overloaded,?MODULE,QLen,Mem}} -> + {shutdown,{overloaded,QLen,Mem}} -> ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]); killed -> ct:pal("Slow shutdown, handler process was killed!", []) @@ -996,7 +1099,7 @@ qlen_kill_new(Config) -> ok after 5000 -> - Info = logger_std_h:info(?MODULE), + Info = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info]), ct:fail("Handler not dead! It should not have survived this!") end. @@ -1011,7 +1114,7 @@ qlen_kill_std(_Config) -> %% File = lists:concat([?MODULE,"_",?FUNCTION_NAME,".log"]), %% Log = filename:join(Dir, File), %% Node = start_std_h_on_new_node(Config, ?FUNCTION_NAME, Log), - %% ok = rpc:call(Node, logger, set_handler_config, + %% ok = rpc:call(Node, logger, update_handler_config, %% [?STANDARD_HANDLER, config, %% #{overload_kill_enable=>true, %% overload_kill_qlen=>10, @@ -1028,7 +1131,7 @@ mem_kill_new(Config) -> overload_kill_qlen=>50000, overload_kill_mem_size=>Mem0+500, overload_kill_restart_after=>RestartAfter}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), + ok = logger:update_handler_config(?MODULE, NewHConfig), MRef = erlang:monitor(process, Pid0), NumOfReqs = 100, Procs = 4, @@ -1037,7 +1140,7 @@ mem_kill_new(Config) -> receive {'DOWN', MRef, _, _, Info} -> case Info of - {shutdown,{overloaded,?MODULE,QLen,Mem}} -> + {shutdown,{overloaded,QLen,Mem}} -> ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]); killed -> ct:pal("Slow shutdown, handler process was killed!", []) @@ -1047,7 +1150,7 @@ mem_kill_new(Config) -> ok after 5000 -> - Info = logger_std_h:info(?MODULE), + Info = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info]), ct:fail("Handler not dead! It should not have survived this!") end. @@ -1067,7 +1170,7 @@ restart_after(Config) -> HConfig#{config=>StdHConfig#{overload_kill_enable=>true, overload_kill_qlen=>10, overload_kill_restart_after=>infinity}}, - ok = logger:set_handler_config(?MODULE, NewHConfig1), + ok = logger:update_handler_config(?MODULE, NewHConfig1), MRef1 = erlang:monitor(process, whereis(h_proc_name())), %% kill handler send_burst({n,100}, {spawn,4,0}, {chars,79}, notice), @@ -1078,18 +1181,19 @@ restart_after(Config) -> ok after 5000 -> - Info1 = logger_std_h:info(?MODULE), + Info1 = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info1]), ct:fail("Handler not dead! It should not have survived this!") end, - + {Log,_,_} = start_handler(?MODULE, ?FUNCTION_NAME, Config), RestartAfter = ?OVERLOAD_KILL_RESTART_AFTER, + NewHConfig2 = HConfig#{config=>StdHConfig#{overload_kill_enable=>true, overload_kill_qlen=>10, overload_kill_restart_after=>RestartAfter}}, - ok = logger:set_handler_config(?MODULE, NewHConfig2), + ok = logger:update_handler_config(?MODULE, NewHConfig2), Pid0 = whereis(h_proc_name()), MRef2 = erlang:monitor(process, Pid0), %% kill handler @@ -1102,7 +1206,7 @@ restart_after(Config) -> ok after 5000 -> - Info2 = logger_std_h:info(?MODULE), + Info2 = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info2]), ct:fail("Handler not dead! It should not have survived this!") end, @@ -1123,12 +1227,16 @@ handler_requests_under_load(Config) -> drop_mode_qlen => 1000, flush_qlen => 2000, burst_limit_enable => false}}, - ok = logger:set_handler_config(?MODULE, NewHConfig), - Pid = spawn_link(fun() -> send_requests(?MODULE, 1, [{filesync,[]}, - {info,[]}, - {reset,[]}, - {change_config,[]}]) - end), + ok = logger:update_handler_config(?MODULE, NewHConfig), + Pid = spawn_link( + fun() -> send_requests(1,[{logger_std_h,filesync,[?MODULE],[]}, + {logger_olp,info,[h_proc_name()],[]}, + {logger_olp,reset,[h_proc_name()],[]}, + {logger,update_handler_config, + [?MODULE, config, + #{overload_kill_enable => false}], + []}]) + end), Sent = send_burst({t,10000}, seq, {chars,79}, notice), Pid ! {self(),finish}, ReqResult = receive {Pid,Result} -> Result end, @@ -1139,36 +1247,45 @@ handler_requests_under_load(Config) -> [E || E <- Res, is_tuple(E) andalso (element(1,E) == error)] end, - Errors = [{Req,FindError(Res)} || {Req,Res} <- ReqResult], - NoOfReqs = lists:foldl(fun({_,Res}, N) -> N + length(Res) end, 0, ReqResult), + Errors = [{Func,FindError(Res)} || {_,Func,_,Res} <- ReqResult], + NoOfReqs = lists:foldl(fun({_,_,_,Res}, N) -> N + length(Res) end, + 0, ReqResult), ct:pal("~w requests made. Errors: ~n~p", [NoOfReqs,Errors]), ok = file_delete(Log). handler_requests_under_load(cleanup, _Config) -> ok = stop_handler(?MODULE). -send_requests(HName, TO, Reqs = [{Req,Res}|Rs]) -> +recreate_deleted_log(Config) -> + {Log,_HConfig,_StdHConfig} = + start_handler(?MODULE, ?FUNCTION_NAME, Config), + logger:notice("first",?domain), + logger_std_h:filesync(?MODULE), + ok = file:rename(Log,Log++".old"), + logger:notice("second",?domain), + logger_std_h:filesync(?MODULE), + {ok,<<"first\n">>} = file:read_file(Log++".old"), + {ok,<<"second\n">>} = file:read_file(Log), + ok. +recreate_deleted_log(cleanup, _Config) -> + ok = stop_handler(?MODULE). + +%%%----------------------------------------------------------------- +%%% +send_requests(TO, Reqs = [{Mod,Func,Args,Res}|Rs]) -> receive {From,finish} -> From ! {self(),Reqs} after TO -> - Result = - case Req of - change_config -> - logger:set_handler_config(HName, config, - #{overload_kill_enable => - false}); - Func -> - logger_std_h:Func(HName) - end, - send_requests(HName, TO, Rs ++ [{Req,[Result|Res]}]) + Result = apply(Mod,Func,Args), + send_requests(TO, Rs ++ [{Mod,Func,Args,[Result|Res]}]) end. %%%----------------------------------------------------------------- %%% -start_handler(Name, TTY, Config) when TTY == standard_io; - TTY == standard_error-> +start_handler(Name, TTY, _Config) when TTY == standard_io; + TTY == standard_error-> ok = logger:add_handler(Name, logger_std_h, #{config => #{type => TTY}, @@ -1422,7 +1539,7 @@ start_op_trace() -> {ok,_} = dbg:p(self(), [c]), MS1 = dbg:fun2ms(fun([_]) -> return_trace() end), - {ok,_} = dbg:tp(logger_h_common, check_load, 1, MS1), + {ok,_} = dbg:tpl(logger_h_common, check_load, 1, MS1), {ok,_} = dbg:tpl(logger_h_common, flush_log_requests, 2, []), @@ -1496,7 +1613,10 @@ analyse(Msgs) -> start_tracer(Trace,Expected) -> Pid = self(), - FileCtrlPid = maps:get(file_ctrl_pid, logger_std_h:info(?MODULE)), + FileCtrlPid = maps:get(file_ctrl_pid, + maps:get(handler_state, + maps:get(cb_state, + logger_olp:info(h_proc_name())))), dbg:tracer(process,{fun tracer/2,{Pid,Expected}}), dbg:p(whereis(h_proc_name()),[c]), dbg:p(FileCtrlPid,[c]), @@ -1504,7 +1624,9 @@ start_tracer(Trace,Expected) -> ok. tpl([{M,F,A}|Trace]) -> - {ok,Match} = dbg:tpl(M,F,A,[]), + tpl([{{M,F,A},[]}|Trace]); +tpl([{{M,F,A},MS}|Trace]) -> + {ok,Match} = dbg:tpl(M,F,A,MS), case lists:keyfind(matched,1,Match) of {_,_,1} -> ok; @@ -1517,7 +1639,7 @@ tpl([{M,F,A}|Trace]) -> tpl([]) -> ok. -tracer({trace,_,call,{logger_std_h,handle_cast,[Op|_]}}, +tracer({trace,_,call,{logger_h_common,handle_cast,[Op|_]}}, {Pid,[{Mod,Func,Op}|Expected]}) -> maybe_tracer_done(Pid,Expected,{Mod,Func,Op}); tracer({trace,_,call,{Mod=logger_std_h,Func=write_to_dev,[_,Data,_,_,_]}}, diff --git a/lib/kernel/test/logger_stress_SUITE.erl b/lib/kernel/test/logger_stress_SUITE.erl new file mode 100644 index 0000000000..4072e8c86a --- /dev/null +++ b/lib/kernel/test/logger_stress_SUITE.erl @@ -0,0 +1,456 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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(logger_stress_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct_event.hrl"). +-include_lib("kernel/include/logger.hrl"). +-include_lib("kernel/src/logger_h_common.hrl"). + +-ifdef(SAVE_STATS). + -define(COLLECT_STATS(_All_,_Procs_), + ct:pal("~p",[stats(_All_,_Procs_)])). +-else. + -define(COLLECT_STATS(_All_,_Procs__), ok). +-endif. + +-define(TEST_DURATION,120). % seconds + +suite() -> + [{timetrap,{minutes,3}}, + {ct_hooks,[logger_test_lib]}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_Group, Config) -> + Config. + +end_per_group(_Group, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(Case, Config) -> + try apply(?MODULE,Case,[cleanup,Config]) + catch error:undef -> ok + end, + ok. + +groups() -> + []. + +all() -> + [allow_events, + reject_events, + std_handler, + disk_log_handler, + emulator_events, + remote_events, + remote_to_disk_log, + remote_emulator_events, + remote_emulator_to_disk_log]. + +%%%----------------------------------------------------------------- +%%% Test cases +%%%----------------------------------------------------------------- +%% Time from log macro call to handler callback +allow_events(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,?MODULE,#{}}]}, + {logger_level,notice}]), + N = 100000, + {T,_} = timer:tc(fun() -> rpc:call(Node,?MODULE,nlogs,[N]) end), + IOPS = N * 1000/T, % log events allowed per millisecond + ct_event:notify(#event{name = benchmark_data, + data = [{value,IOPS}]}), + {comment,io_lib:format("~.2f accepted events pr millisecond", + [IOPS])}. + +%% Time from log macro call to reject (log level) +reject_events(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,?MODULE,#{}}]}, + {logger_level,error}]), + N = 1000000, + {T,_} = timer:tc(fun() -> rpc:call(Node,?MODULE,nlogs,[N]) end), + IOPS = N * 1000/T, % log events rejected per millisecond + ct_event:notify(#event{name = benchmark_data, + data = [{value,IOPS}]}), + {comment,io_lib:format("~.2f rejected events pr millisecond", + [IOPS])}. + +%% Cascading failure that produce gen_server and proc_lib reports - +%% how many of the produced log events are actually written to a log +%% with logger_std_h file handler. +std_handler(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,logger_std_h, + #{config=>#{type=>{file,"default.log"}}}}]}]), + + cascade({Node,{logger_backend,log_allowed,2},[]}, + {Node,{logger_std_h,write,4},[{default,logger_std_h_default}]}, + fun otp_cascading/0). +std_handler(cleanup,_Config) -> + _ = file:delete("default.log"), + ok. + +%% Cascading failure that produce gen_server and proc_lib reports - +%% how many of the produced log events are actually written to a log +%% with logger_disk_log_h wrap file handler. +disk_log_handler(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,logger_disk_log_h,#{}}]}]), + cascade({Node,{logger_backend,log_allowed,2},[]}, + {Node,{logger_disk_log_h,write,4}, + [{default,logger_disk_log_h_default}]}, + fun otp_cascading/0). +disk_log_handler(cleanup,_Config) -> + Files = filelib:wildcard("default.log.*"), + [_ = file:delete(F) || F <- Files], + ok. + +%% Cascading failure that produce log events from the emulator - how +%% many of the produced log events pass through the proxy. +emulator_events(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,?MODULE,#{}}]}]), + cascade({Node,{?MODULE,producer,0},[]}, + {Node,{?MODULE,log,2},[{proxy,logger_proxy}]}, + fun em_cascading/0). + +%% Cascading failure that produce gen_server and proc_lib reports on +%% remote node - how many of the produced log events pass through the +%% proxy. +remote_events(Config) -> + {ok,_,Node1} = + logger_test_lib:setup([{postfix,1}|Config], + [{logger, + [{handler,default,?MODULE,#{}}]}]), + {ok,_,Node2} = + logger_test_lib:setup([{postfix,2}|Config],[]), + cascade({Node2,{logger_backend,log_allowed,2},[{remote_proxy,logger_proxy}]}, + {Node1,{?MODULE,log,2},[{local_proxy,logger_proxy}]}, + fun otp_cascading/0). + +%% Cascading failure that produce gen_server and proc_lib reports on +%% remote node - how many of the produced log events are actually +%% written to a log with logger_disk_log_h wrap file handler. +remote_to_disk_log(Config) -> + {ok,_,Node1} = + logger_test_lib:setup([{postfix,1}|Config], + [{logger, + [{handler,default,logger_disk_log_h,#{}}]}]), + {ok,_,Node2} = + logger_test_lib:setup([{postfix,2}|Config],[]), + cascade({Node2,{logger_backend,log_allowed,2},[{remote_proxy,logger_proxy}]}, + {Node1,{logger_disk_log_h,write,4}, + [{local_proxy,logger_proxy}, + {local_default,logger_disk_log_h_default}]}, + fun otp_cascading/0). +remote_to_disk_log(cleanup,_Config) -> + Files = filelib:wildcard("default.log.*"), + [_ = file:delete(F) || F <- Files], + ok. + +%% Cascading failure that produce log events from the emulator on +%% remote node - how many of the produced log events pass through the +%% proxy. +remote_emulator_events(Config) -> + {ok,_,Node1} = + logger_test_lib:setup([{postfix,1}|Config], + [{logger, + [{handler,default,?MODULE,#{}}]}]), + {ok,_,Node2} = + logger_test_lib:setup([{postfix,2}|Config],[]), + cascade({Node2,{?MODULE,producer,0},[{remote_proxy,logger_proxy}]}, + {Node1,{?MODULE,log,2},[{local_proxy,logger_proxy}]}, + fun em_cascading/0). + +%% Cascading failure that produce log events from the emulator on +%% remote node - how many of the produced log events are actually +%% written to a log with logger_disk_log_h wrap file handler. +remote_emulator_to_disk_log(Config) -> + {ok,_,Node1} = + logger_test_lib:setup([{postfix,1}|Config], + [{logger, + [{handler,default,logger_disk_log_h,#{}}]}]), + {ok,_,Node2} = + logger_test_lib:setup([{postfix,2}|Config],[]), + cascade({Node2,{?MODULE,producer,0},[{remote_proxy,logger_proxy}]}, + {Node1,{logger_disk_log_h,write,4}, + [{local_proxy,logger_proxy}, + {local_default,logger_disk_log_h_default}]}, + fun em_cascading/0). +remote_emulator_to_disk_log(cleanup,_Config) -> + Files = filelib:wildcard("default.log.*"), + [_ = file:delete(F) || F <- Files], + ok. + +%%%----------------------------------------------------------------- +%%% Internal functions +nlogs(N) -> + group_leader(whereis(user),self()), + Str = "\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "[\\]^_`abcdefghijklmnopqr", + [?LOG_NOTICE(Str) || _ <- lists:seq(1,N)], + ok. + +%% cascade(ProducerInfo,ConsumerInfo,TestFun) +cascade({PNode,PMFA,_PStatProcs},{CNode,CMFA,_CStatProcs},TestFun) -> + Tab = ets:new(counter,[set,public]), + ets:insert(Tab,{producer,0}), + ets:insert(Tab,{consumer,0}), + dbg:tracer(process,{fun tracer/2,{Tab,PNode,CNode}}), + dbg:n(PNode), + dbg:n(CNode), + dbg:cn(node()), + dbg:p(all,[call,arity]), + dbg:tpl(PMFA,[]), + dbg:tpl(CMFA,[]), + + Pid = rpc:call(CNode,?MODULE,wrap_test,[PNode,TestFun]), + MRef = erlang:monitor(process,Pid), + TO = ?TEST_DURATION*1000, + receive {'DOWN',MRef,_,_,Reason} -> + ct:fail({remote_pid_down,Reason}) + after TO -> + All = ets:lookup_element(Tab,producer,2), + Written = ets:lookup_element(Tab,consumer,2), + dbg:stop_clear(), + ?COLLECT_STATS(All, + [{PNode,P,Id} || {Id,P} <- _PStatProcs] ++ + [{CNode,P,Id} || {Id,P} <- _CStatProcs]), + Ratio = Written/All * 100, + ct_event:notify(#event{name = benchmark_data, + data = [{value,Ratio}]}), + {comment,io_lib:format("~p % (~p written, ~p produced)", + [round(Ratio),Written,All])} + end. + +wrap_test(Fun) -> + wrap_test(node(),Fun). +wrap_test(Node,Fun) -> + reset(), + group_leader(whereis(user),self()), + rpc:call(Node,?MODULE,do_fun,[Fun]). + +do_fun(Fun) -> + reset(), + Fun(). + +reset() -> + reset([logger_std_h_default, logger_disk_log_h_default, logger_proxy]). +reset([P|Ps]) -> + is_pid(whereis(P)) andalso logger_olp:reset(P), + reset(Ps); +reset([]) -> + ok. + + +tracer({trace,_,call,{?MODULE,producer,_}},{Tab,_PNode,_CNode}=S) -> + ets:update_counter(Tab,producer,1), + S; +tracer({trace,Pid,call,{logger_backend,log_allowed,_}},{Tab,PNode,_CNode}=S) when node(Pid)=:=PNode -> + ets:update_counter(Tab,producer,1), + S; +tracer({trace,_,call,{?MODULE,log,_}},{Tab,_PNode,_CNode}=S) -> + ets:update_counter(Tab,consumer,1), + S; +tracer({trace,_,call,{_,write,_}},{Tab,_PNode,_CNode}=S) -> + ets:update_counter(Tab,consumer,1), + S; +tracer(_,S) -> + S. + + +%%%----------------------------------------------------------------- +%%% Collect statistics +-define(STAT_KEYS, + [burst_drops, + calls, + casts, + drops, + flushed, + flushes, + freq, + last_qlen, + max_qlen, + time, + writes]). +-define(EVENT_KEYS, + [calls,casts,flushed]). + +stats(All,Procs) -> + NI = [{Id,rpc:call(N,logger_olp,info,[P])} || {N,P,Id}<-Procs], + [{all,All}|[stats(Id,I,All) || {Id,I} <- NI]]. + +stats(Id,Info,All) -> + S = maps:with(?STAT_KEYS,Info), + AllOnProc = lists:sum(maps:values(maps:with(?EVENT_KEYS,S))), + if All>0 -> + Writes = maps:get(writes,S), + {_,ActiveTime} = maps:get(time,S), + Rate = round(100*Writes/All), + RateOnProc = + if AllOnProc>0 -> + round(100*Writes/AllOnProc); + true -> + 0 + end, + AvFreq = + if ActiveTime>0 -> + round(Writes/ActiveTime); + true -> + 0 + end, + {Id, + {stats,S}, + {rate,Rate}, + {rate_on_proc,RateOnProc}, + {av_freq,AvFreq}}; + true -> + {Id,none} + end. + +%%%----------------------------------------------------------------- +%%% Spawn a lot of processes that crash repeatedly, causing a lot of +%%% error reports from the emulator. +em_cascading() -> + spawn(fun() -> super() end). + +super() -> + process_flag(trap_exit,true), + spawn_link(fun server/0), + [spawn_link(fun client/0) || _<-lists:seq(1,10000)], + super_loop(). + +super_loop() -> + receive + {'EXIT',_,server} -> + spawn_link(fun server/0), + super_loop(); + {'EXIT',_,_} -> + _L = lists:sum(lists:seq(1,10000)), + spawn_link(fun client/0), + super_loop() + end. + +client() -> + receive + after 1 -> + case whereis(server) of + Pid when is_pid(Pid) -> + ok; + undefined -> + producer(), + erlang:error(some_exception) + end + end, + client(). + +server() -> + register(server,self()), + receive + after 3000 -> + exit(server) + end. + + +%%%----------------------------------------------------------------- +%%% Create a supervisor tree with processes that crash repeatedly, +%%% causing a lot of supervisor reports and crashreports +otp_cascading() -> + {ok,Pid} = supervisor:start_link({local,otp_super}, ?MODULE, [otp_super]), + unlink(Pid), + Pid. + +otp_server_sup() -> + supervisor:start_link({local,otp_server_sup},?MODULE,[otp_server_sup]). + +otp_client_sup(N) -> + supervisor:start_link({local,otp_client_sup},?MODULE,[otp_client_sup,N]). + +otp_server() -> + gen_server:start_link({local,otp_server},?MODULE,[otp_server],[]). + +otp_client() -> + gen_server:start_link(?MODULE,[otp_client],[]). + +init([otp_super]) -> + {ok, {{one_for_one, 200, 10}, + [{client_sup, + {?MODULE, otp_client_sup, [10000]}, + permanent, 1000, supervisor, [?MODULE]}, + {server_sup, + {?MODULE, otp_server_sup, []}, + permanent, 1000, supervisor, [?MODULE]} + ]}}; +init([otp_server_sup]) -> + {ok, {{one_for_one, 2, 10}, + [{server, + {?MODULE, otp_server, []}, + permanent, 1000, worker, [?MODULE]} + ]}}; +init([otp_client_sup,N]) -> + spawn(fun() -> + [supervisor:start_child(otp_client_sup,[]) + || _ <- lists:seq(1,N)] + end), + {ok, {{simple_one_for_one, N*10, 1}, + [{client, + {?MODULE, otp_client, []}, + permanent, 1000, worker, [?MODULE]} + ]}}; +init([otp_server]) -> + {ok, server, 10000}; +init([otp_client]) -> + {ok, client,1}. + +handle_info(timeout, client) -> + true = is_pid(whereis(otp_server)), + {noreply,client,1}; +handle_info(timeout, server) -> + exit(self(), some_error). + +%%%----------------------------------------------------------------- +%%% Logger callbacks +log(_LogEvent,_Config) -> + ok. + +%%%----------------------------------------------------------------- +%%% Function to trace on for counting produced emulator messages +producer() -> + ok. diff --git a/lib/kernel/test/logger_test_lib.erl b/lib/kernel/test/logger_test_lib.erl index 81eb9ce5eb..be4bc427fb 100644 --- a/lib/kernel/test/logger_test_lib.erl +++ b/lib/kernel/test/logger_test_lib.erl @@ -28,11 +28,17 @@ post_end_per_testcase/5, post_end_per_suite/3]). setup(Config,Vars) -> + Postfix = case proplists:get_value(postfix, Config) of + undefined -> ""; + P -> ["_",P] + end, FuncStr = lists:concat([proplists:get_value(suite, Config), "_", - proplists:get_value(tc, Config)]), + proplists:get_value(tc, Config)| + Postfix]), ConfigFileName = filename:join(proplists:get_value(priv_dir, Config), FuncStr), file:write_file(ConfigFileName ++ ".config", io_lib:format("[{kernel, ~p}].",[Vars])), - case test_server:start_node(proplists:get_value(tc, Config), slave, + Sname = lists:concat([proplists:get_value(tc,Config)|Postfix]), + case test_server:start_node(Sname, slave, [{args, ["-pa ",filename:dirname(code:which(?MODULE)), " -boot start_sasl -kernel start_timer true " "-config ",ConfigFileName]}]) of diff --git a/lib/kernel/test/prim_file_SUITE.erl b/lib/kernel/test/prim_file_SUITE.erl index a02b5f87d1..2f465a15bc 100644 --- a/lib/kernel/test/prim_file_SUITE.erl +++ b/lib/kernel/test/prim_file_SUITE.erl @@ -1300,7 +1300,8 @@ e_delete(Config) when is_list(Config) -> case os:type() of {win32, _} -> %% Remove a character device. - {error, eacces} = ?PRIM_FILE:delete("nul"); + expect({error, eacces}, {error, einval}, + ?PRIM_FILE:delete("nul")); _ -> ?PRIM_FILE:write_file_info( Base, #file_info {mode=0}), diff --git a/lib/kernel/test/sendfile_SUITE.erl b/lib/kernel/test/sendfile_SUITE.erl index 0c0b1cbcb6..ad060aa05c 100644 --- a/lib/kernel/test/sendfile_SUITE.erl +++ b/lib/kernel/test/sendfile_SUITE.erl @@ -341,7 +341,21 @@ t_sendfile_closeduring(Config) -> -1 end, - ok = sendfile_send({127,0,0,1}, Send, 0). + ok = sendfile_send({127,0,0,1}, Send, 0, [{active,false}]), + [] = flush(), + ok = sendfile_send({127,0,0,1}, Send, 0, [{active,true}]), + [] = flush(), + ok. + +flush() -> + lists:reverse(flush([])). + +flush(Acc) -> + receive M -> + flush([M | Acc]) + after 0 -> + Acc + end. t_sendfile_crashduring(Config) -> Filename = proplists:get_value(big_file, Config), @@ -409,12 +423,16 @@ sendfile_send(Send) -> sendfile_send(Host, Send) -> sendfile_send(Host, Send, []). sendfile_send(Host, Send, Orig) -> + sendfile_send(Host, Send, Orig, [{active,false}]). + +sendfile_send(Host, Send, Orig, SockOpts) -> + SFServer = spawn_link(?MODULE, sendfile_server, [self(), Orig]), receive {server, Port} -> - {ok, Sock} = gen_tcp:connect(Host, Port, - [binary,{packet,0}, - {active,false}]), + Opts = [binary,{packet,0}|SockOpts], + io:format("connect with opts = ~p\n", [Opts]), + {ok, Sock} = gen_tcp:connect(Host, Port, Opts), Data = case proplists:get_value(arity,erlang:fun_info(Send)) of 1 -> Send(Sock); diff --git a/lib/kernel/test/seq_trace_SUITE.erl b/lib/kernel/test/seq_trace_SUITE.erl index cf4bf11328..663f910751 100644 --- a/lib/kernel/test/seq_trace_SUITE.erl +++ b/lib/kernel/test/seq_trace_SUITE.erl @@ -19,13 +19,17 @@ %% -module(seq_trace_SUITE). +%% label_capability_mismatch needs to run a part of the test on an OTP 20 node. +-compile(r20). + -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2]). -export([token_set_get/1, tracer_set_get/1, print/1, send/1, distributed_send/1, recv/1, distributed_recv/1, trace_exit/1, distributed_exit/1, call/1, port/1, - match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1]). + match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1, + send_literal/1]). %% internal exports -export([simple_tracer/2, one_time_receiver/0, one_time_receiver/1, @@ -44,7 +48,7 @@ suite() -> {timetrap,{minutes,1}}]. all() -> - [token_set_get, tracer_set_get, print, send, + [token_set_get, tracer_set_get, print, send, send_literal, distributed_send, recv, distributed_recv, trace_exit, distributed_exit, call, port, match_set_seq_token, gc_seq_token, label_capability_mismatch]. @@ -158,23 +162,51 @@ do_print(TsType) -> {0,{print,_,_,[],print3}, Ts1}] = stop_tracer(2), check_ts(TsType, Ts0), check_ts(TsType, Ts1). - + send(Config) when is_list(Config) -> lists:foreach(fun do_send/1, ?TIMESTAMP_MODES). do_send(TsType) -> + do_send(TsType, send). + +do_send(TsType, Msg) -> seq_trace:reset_trace(), start_tracer(), Receiver = spawn(?MODULE,one_time_receiver,[]), Label = make_ref(), seq_trace:set_token(label,Label), set_token_flags([send, TsType]), - Receiver ! send, + Receiver ! Msg, Self = self(), seq_trace:reset_trace(), - [{Label,{send,_,Self,Receiver,send}, Ts}] = stop_tracer(1), + [{Label,{send,_,Self,Receiver,Msg}, Ts}] = stop_tracer(1), check_ts(TsType, Ts). +%% This testcase tests that we do not segfault when we have a +%% literal as the message and the message is copied onto the +%% heap during a GC. +send_literal(Config) when is_list(Config) -> + lists:foreach(fun do_send_literal/1, + [atom, make_ref(), ets:new(hej,[]), 1 bsl 64, + "gurka", {tuple,test,with,#{}}, #{}]). + +do_send_literal(Msg) -> + N = 10000, + seq_trace:reset_trace(), + start_tracer(), + Label = make_ref(), + 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)], + erlang:garbage_collect(Receiver), + Self = self(), + seq_trace:reset_trace(), + [{Label,{send,_,Self,Receiver,Msg}, Ts} | _] = stop_tracer(N), + check_ts(no_timestamp, Ts). + distributed_send(Config) when is_list(Config) -> lists:foreach(fun do_distributed_send/1, ?TIMESTAMP_MODES). @@ -329,7 +361,7 @@ do_incompatible_labels(Rel) -> Mdir = filename:dirname(Dir), true = rpc:call(Node,code,add_patha,[Mdir]), seq_trace:reset_trace(), - rpc:call(Node,?MODULE,start_tracer,[]), + true = is_pid(rpc:call(Node,?MODULE,start_tracer,[])), Receiver = spawn(Node,?MODULE,one_time_receiver,[]), %% This node does not support arbitrary labels, so it must fail with a @@ -356,7 +388,7 @@ do_compatible_labels(Rel) -> Mdir = filename:dirname(Dir), true = rpc:call(Node,code,add_patha,[Mdir]), seq_trace:reset_trace(), - rpc:call(Node,?MODULE,start_tracer,[]), + true = is_pid(rpc:call(Node,?MODULE,start_tracer,[])), Receiver = spawn(Node,?MODULE,one_time_receiver,[]), %% This node does not support arbitrary labels, but small integers should diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk index fe22e2af98..4b43c6ae9d 100644 --- a/lib/kernel/vsn.mk +++ b/lib/kernel/vsn.mk @@ -1 +1 @@ -KERNEL_VSN = 6.0.1 +KERNEL_VSN = 6.2 |