Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
2761-stdlib-Add-gen_server-format_status-1.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2761-stdlib-Add-gen_server-format_status-1.patch of Package erlang
From 91c05338485c0d627128118ce0967595d7afafb4 Mon Sep 17 00:00:00 2001 From: Lukas Larsson <lukas@erlang.org> Date: Wed, 9 Jun 2021 15:12:48 +0200 Subject: [PATCH] stdlib: Add gen_server:format_status/1 This new format_status callback can also be used to format the messages in the sys logger or crash reports. It is built in order to make it possible to extend in the future with more values. Fixes #4673 --- lib/stdlib/doc/src/gen_event.xml | 76 ++++++ lib/stdlib/doc/src/gen_server.xml | 78 ++++++ lib/stdlib/doc/src/gen_statem.xml | 109 +++++++- lib/stdlib/src/gen.erl | 42 ++- lib/stdlib/src/gen_event.erl | 69 +++-- lib/stdlib/src/gen_server.erl | 115 ++++++--- lib/stdlib/src/gen_statem.erl | 121 +++++---- lib/stdlib/src/sys.erl | 2 +- lib/stdlib/test/dummy_h.erl | 4 +- lib/stdlib/test/erl_internal_SUITE.erl | 12 +- lib/stdlib/test/gen_event_SUITE.erl | 35 ++- lib/stdlib/test/gen_server_SUITE.erl | 241 ++++++++++++++++-- .../format_status_server.erl | 55 ++++ lib/stdlib/test/gen_statem_SUITE.erl | 157 +++++++++--- .../format_status_statem.erl | 40 +++ 15 files changed, 962 insertions(+), 194 deletions(-) create mode 100644 lib/stdlib/test/gen_server_SUITE_data/format_status_server.erl create mode 100644 lib/stdlib/test/gen_statem_SUITE_data/format_status_statem.erl diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml index aaff07f0b1..4e2b7b2e0d 100644 --- a/lib/stdlib/doc/src/gen_event.xml +++ b/lib/stdlib/doc/src/gen_event.xml @@ -834,6 +834,77 @@ gen_event:stop -----> Module:terminate/2 </desc> </func> + <func> + <name since="">Module:format_status(Status) -> NewStatus</name> + <fsummary>Optional function for providing a term describing the + current event handler state.</fsummary> + <type> + <v>Status = #{ state => State,</v> + <v> + message => Message,</v> + <v> + reason => Reason,</v> + <v> + log => Log }</v> + <v>NewStatus = Status</v> + <v>State = Message = Reason = term()</v> + <v>Log = [<seetype marker="sys#system_event">sys:system_event()</seetype>]</v> + </type> + <desc> + <note> + <p>This callback is optional, so event handler modules need + not export it. If a handler does not export this function, + the <c>gen_event</c> module uses the handler state directly for + the purposes described below.</p> + <p> + If this callback is exported but fails, to hide possibly sensitive + data, the default function will instead return the fact that + <c>format_status/1</c> has crashed.</p> + </note> + <p>This function is called by a <c>gen_event</c> process in the + following situations:</p> + <list type="bulleted"> + <item>One of <seemfa marker="sys#get_status/1"> + <c>sys:get_status/1,2</c></seemfa> + is invoked to get the <c>gen_event</c> status.</item> + <item>The event handler terminates abnormally and <c>gen_event</c> + logs an error.</item> + </list> + <p>This callback is to be used to limit the status of the event handler before + returned by <seemfa marker="sys#get_status/1"><c>sys:get_status/1,2</c></seemfa> or + sent to <seeerl marker="kernel:logger"><c>logger</c></seeerl>. The status + of the event handler is passed as a map with the following associations: + </p> + <taglist> + <tag><c>state => term()</c></tag> + <item>The internal state of the event handler.</item> + <tag><c>message => term()</c></tag> + <item>The message that caused the event handler to terminate.</item> + <tag><c>reason => term()</c></tag> + <item>The reason that caused the event handler to terminate.</item> + <tag><c>log => [</c><seetype marker="sys#system_event"><c>sys:system_event/0</c></seetype><c>]</c></tag> + <item>The <seemfa marker="sys#log/2">sys log</seemfa> of the server.</item> + </taglist> + <p>New associations may be added into the status map without prior notice.</p> + <p>The function is to return <c>NewStatus</c>, a map containing the same + associations as the input map.</p> + <p>Two possible use cases for this callback is to either remove + sensitive information from the state so that it not printed in log files, + or to remove large irrelevant terms of state that clutter the logs.</p> + <code type="erl"><![CDATA[ +format_status(Status) -> + maps:map( + fun(state,State) -> + maps:remove(private_key, State); + (message,{password, _Pass}) -> + {password, removed}; + (_,Value) -> + Value + end, Status). +]]></code> + </desc> + </func> + <func> <name since="OTP R14B">Module:format_status(Opt, [PDict, State]) -> Status</name> <fsummary>Optional function for providing a term describing the @@ -845,6 +916,11 @@ gen_event:stop -----> Module:terminate/2 <v>Status = term()</v> </type> <desc> + <warning> + <p>This callback is deprecated, in new code use <seemfa marker="#Module:format_status/1"> + format_status/1</seemfa>. If a <seemfa marker="#Module:format_status/1">format_status/1</seemfa> + callback exists, then this function will never be called.</p> + </warning> <note> <p>This callback is optional, so event handler modules need not export it. If a handler does not export this function, diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml index 645d612fce..0e9157d553 100644 --- a/lib/stdlib/doc/src/gen_server.xml +++ b/lib/stdlib/doc/src/gen_server.xml @@ -787,6 +787,79 @@ gen_server:abcast -----> Module:handle_cast/2 </desc> </func> + <func> + <name since="">Module:format_status(Status) -> NewStatus</name> + <fsummary>Optional function for providing a term describing the + current <c>gen_server</c> status.</fsummary> + <type> + <v>Status = #{ state => State,</v> + <v> + message => Message,</v> + <v> + reason => Reason,</v> + <v> + log => Log }</v> + <v>NewStatus = Status</v> + <v>State = Message = Reason = term()</v> + <v>Log = [<seetype marker="sys#system_event">sys:system_event()</seetype>]</v> + </type> + <desc> + <note> + <p>This callback is optional, so callback modules need not + export it. The <c>gen_server</c> module provides a default + implementation of this function that returns the callback + module state.</p> + <p> + If this callback is exported but fails, to hide possibly sensitive + data, the default function will instead return the fact that + <c>format_status/1</c> has crashed.</p> + </note> + <p>This function is called by a <c>gen_server</c> process in the following situations:</p> + <list type="bulleted"> + <item> + <p><seemfa marker="sys#get_status/1"><c>sys:get_status/1,2</c></seemfa> + is invoked to get the <c>gen_server</c> status.</p> + </item> + <item> + <p>The <c>gen_server</c> process terminates abnormally and logs an error.</p> + </item> + </list> + <p>This callback is to be used to limit the status of the process before + returned by <seemfa marker="sys#get_status/1"><c>sys:get_status/1,2</c></seemfa> or + sent to <seeerl marker="kernel:logger"><c>logger</c></seeerl>. The status + of the server is passed as a map with the following associations: + </p> + <taglist> + <tag><c>state => term()</c></tag> + <item>The internal state of the <c>gen_server</c> process.</item> + <tag><c>message => term()</c></tag> + <item>The message that caused the server to terminate.</item> + <tag><c>reason => term()</c></tag> + <item>The reason that caused the server to terminate.</item> + <tag><c>log => [</c><seetype marker="sys#system_event"><c>sys:system_event/0</c></seetype><c>]</c></tag> + <item>The <seemfa marker="sys#log/2">sys log</seemfa> of the server.</item> + </taglist> + <p>New associations may be added into the status map without prior notice.</p> + <p>The function is to return <c>NewStatus</c>, a map containing the same + associations as the input map.</p> + <p>Two possible use cases for this callback is to either remove + sensitive information from the state so that it not printed in log files, + or to remove large irrelevant terms of state that do clutter the logs.</p> + <p>Example:</p> + <code type="erl"><![CDATA[ +format_status(Status) -> + maps:map( + fun(state,State) -> + maps:remove(private_key, State); + (message,{password, _Pass}) -> + {password, removed}; + (_,Value) -> + Value + end, Status). +]]></code> + </desc> + </func> + <func> <name since="OTP R13B04">Module:format_status(Opt, [PDict, State]) -> Status</name> <fsummary>Optional function for providing a term describing the @@ -798,6 +871,11 @@ gen_server:abcast -----> Module:handle_cast/2 <v>Status = term()</v> </type> <desc> + <warning> + <p>This callback is deprecated, in new code use <seemfa marker="#Module:format_status/1"> + format_status/1</seemfa>. If a <seemfa marker="#Module:format_status/1">format_status/1</seemfa> + callback exists, then this function will never be called.</p> + </warning> <note> <p>This callback is optional, so callback modules need not export it. The <c>gen_server</c> module provides a default diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index c6790c1d48..971b0b3d4d 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -1246,8 +1246,8 @@ handle_event(_, _, State, Data) -> such as the <seeerl marker="#state callback">state callback</seeerl>, <seemfa marker="#Module:code_change/4"><c><anno>NewModule</anno>:code_change/4</c></seemfa>, - <seemfa marker="#Module:format_status/2"> - <c><anno>NewModule</anno>:format_status/2</c> + <seemfa marker="#Module:format_status/1"> + <c><anno>NewModule</anno>:format_status/1</c> </seemfa> and <seemfa marker="#Module:terminate/3"> @@ -2475,6 +2475,106 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre> </desc> </func> + <func> + <name since="">Module:format_status(Status) -> NewStatus</name> + <fsummary>Optional function for providing a term describing the + current <c>gen_statem</c> status.</fsummary> + <type> + <v>Status = #{ state => <seetype marker="#state">state()</seetype>,</v> + <v> + reason => term(),</v> + <v> + data => <seetype marker="#data">data()</seetype>,</v> + <v> + queue => Queue,</v> + <v> + postponed => Postponed,</v> + <v> + timeouts => Timeouts,</v> + <v> + log => Log }</v> + <v>NewStatus = Status</v> + <v>Queue = Postponed = [{EventType,EventContent}]</v> + <v>EventType = <seetype marker="#event_type">event_type()</seetype></v> + <v>Timeouts = [{<seetype marker="#timeout_event_type">timeout_event_type()</seetype>,EventContent}]</v> + <v>EventContent = term()</v> + <v>Log = [<seetype marker="sys#system_event">sys:system_event()</seetype>]</v> + </type> + <desc> + <note> + <p> + This callback is optional, so a callback module does not need + to export it. The <c>gen_statem</c> module provides a default + implementation of this function that returns + <c>{State,Data}</c>. + </p> + <p> + If this callback is exported but fails, + to hide possibly sensitive data, + the default function will instead return <c>{State,Info}</c>, + where <c>Info</c> says nothing but the fact that + <c>format_status/2</c> has crashed. + </p> + </note> + <p>This function is called by a <c>gen_statem</c> process when + any of the following apply:</p> + <list type="bulleted"> + <item> + <p><seemfa marker="sys#get_status/1"><c>sys:get_status/1,2</c></seemfa> + is invoked to get the <c>gen_statem</c> status.</p> + </item> + <item> + <p>The <c>gen_statem</c> process terminates abnormally and logs an error.</p> + </item> + </list> + <p> + This function is useful for changing the form and + appearance of the <c>gen_statem</c> status for these cases. A + callback module wishing to change the + <seemfa marker="sys#get_status/1"><c>sys:get_status/1,2</c></seemfa> + return value and how + its status appears in termination error logs exports an + instance of <c>format_status/1</c>, which returns a map + describing the current status of the <c>gen_statem</c>. + </p> + <taglist> + <tag><c>state => </c><seetype marker="#state"><c>state()</c></seetype></tag> + <item>The current state of the <c>gen_statem</c> process.</item> + <tag><c>data => </c><seetype marker="#data"><c>data()</c></seetype></tag> + <item>The state data of the the <c>gen_statem</c> process.</item> + <tag><c>reason => term()</c></tag> + <item>The reason that caused the state machine to terminate.</item> + <tag><c>queue => [{</c><seetype marker="#event_type"><c>event_type()</c></seetype><c>,term()}]</c></tag> + <item>The event queue of the <c>gen_statem</c> process.</item> + <tag><c>postponed => [{</c><seetype marker="#event_type"><c>event_type()</c></seetype><c>,term()}]</c></tag> + <item>The <seetype marker="#postpone">postponed</seetype> events queue of the <c>gen_statem</c> process.</item> + <tag><c>log => [</c><seetype marker="sys#system_event"><c>sys:system_event/0</c></seetype><c>]</c></tag> + <item>The <seemfa marker="sys#log/2">sys log</seemfa> of the server.</item> + </taglist> + <p>New associations may be added into the status map without prior notice.</p> + <p>The function is to return <c>NewStatus</c>, a map containing the same + associations as the input map.</p> + <p> + One use case for this function is to return compact alternative + state representations to avoid having large state terms + printed in log files. Another use is to hide sensitive data from + being written to the error log. + </p> + <p>Example:</p> + <code type="erl"><![CDATA[ +format_status(Status) -> + maps:map( + fun(state,State) -> + maps:remove(private_key, State); + (message,{password, _Pass}) -> + {password, removed}; + (_,Value) -> + Value + end, Status). +]]></code> + </desc> + </func> + <func> <name since="OTP 19.0">Module:format_status(Opt, [PDict,State,Data]) -> Status @@ -2498,6 +2598,11 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre> <v>Status = term()</v> </type> <desc> + <warning> + <p>This callback is deprecated, in new code use <seemfa marker="#Module:format_status/1"> + format_status/1</seemfa>. If a <seemfa marker="#Module:format_status/1">format_status/1</seemfa> + callback exists, then this function will never be called.</p> + </warning> <note> <p> This callback is optional, so a callback module does not need diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl index 6a07eb34d8..17512266c7 100644 --- a/lib/stdlib/src/gen.erl +++ b/lib/stdlib/src/gen.erl @@ -35,10 +35,12 @@ -export([init_it/6, init_it/7]). --export([format_status_header/2]). +-export([format_status_header/2, format_status/4]). -define(default_timeout, 5000). +-include("logger.hrl"). + %%----------------------------------------------------------------- -type linkage() :: 'monitor' | 'link' | 'nolink'. @@ -546,3 +548,41 @@ format_status_header(TagLine, RegName) when is_atom(RegName) -> lists:concat([TagLine, " ", RegName]); format_status_header(TagLine, Name) -> {TagLine, Name}. + +-spec format_status(Mod :: module(), Opt :: terminate | normal, Status, Args) -> + ReturnStatus when + Status :: #{ atom() => term() }, + ReturnStatus :: #{ atom() => term(), '$status' => term()}, + Args :: list(term()) | undefined. +format_status(Mod, Opt, Status, Args) -> + case {erlang:function_exported(Mod, format_status, 1), + erlang:function_exported(Mod, format_status, 2)} of + {true, _} -> + try Mod:format_status(Status) of + NewStatus when is_map(NewStatus) -> + MergedStatus = maps:merge(Status, NewStatus), + case maps:size(MergedStatus) =:= maps:size(NewStatus) of + true -> + MergedStatus; + false -> + Status#{ 'EXIT' => atom_to_list(Mod) ++ ":format_status/1 returned a map with unknown keys" } + end; + _ -> + Status#{ 'EXIT' => atom_to_list(Mod) ++ ":format_status/1 did not return a map" } + catch + _:_ -> + Status#{ 'EXIT' => atom_to_list(Mod) ++ ":format_status/1 crashed" } + end; + {false, true} when is_list(Args) -> + try Mod:format_status(Opt, Args) of + Result -> + Status#{ '$status' => Result } + catch + throw:Result -> + Status#{ '$status' => Result }; + _:_ -> + Status#{ 'EXIT' => atom_to_list(Mod) ++ ":format_status/2 crashed" } + end; + {false, _} -> + Status + end. diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl index b4927b92bd..0e27061fa8 100644 --- a/lib/stdlib/src/gen_event.erl +++ b/lib/stdlib/src/gen_event.erl @@ -118,9 +118,15 @@ PDict :: [{Key :: term(), Value :: term()}], State :: term(), Status :: term(). +-callback format_status(Status) -> NewStatus when + Status :: #{ state => term(), + message => term(), + reason => term(), + log => [sys:system_event()] }, + NewStatus :: Status. -optional_callbacks( - [handle_info/2, terminate/2, code_change/3, format_status/2]). + [handle_info/2, terminate/2, code_change/3, format_status/1, format_status/2]). %%--------------------------------------------------------------------------- @@ -776,6 +782,8 @@ do_terminate(Mod, Handler, Args, State, LastIn, SName, Reason) -> ok end. +-spec report_terminate(_, How, _, _, _, _, _) -> ok when + How :: crash | normal | shutdown | {swapped, handler(), false | pid()}. report_terminate(Handler, crash, {error, Why}, State, LastIn, SName, _) -> report_terminate(Handler, Why, State, LastIn, SName); report_terminate(Handler, How, _, State, LastIn, SName, _) -> @@ -795,14 +803,34 @@ report_terminate(Handler, Reason, State, LastIn, SName) -> report_error(_Handler, normal, _, _, _) -> ok; report_error(_Handler, shutdown, _, _, _) -> ok; report_error(_Handler, {swapped,_,_}, _, _, _) -> ok; -report_error(Handler, Reason, State, LastIn, SName) -> +report_error(Handler, Exit, State, LastIn, SName) -> + + %% The reason comes from a catch expression, so we remove + %% the 'EXIT' and stacktrace from it so that the format_status + %% callback does not have deal with that. + {Reason, ReasonFun} = + case Exit of + {'EXIT',{R,ST}} -> + {R, fun(Reason) -> {'EXIT',{Reason,ST}} end}; + {'EXIT',R} -> + {R, fun(Reason) -> {'EXIT',Reason} end}; + R -> + {R, fun(Reason) -> Reason end} + end, + Status = gen:format_status( + Handler#handler.module, + terminate, + #{ state => State, + message => LastIn, + reason => Reason + }, + [get(), State]), ?LOG_ERROR(#{label=>{gen_event,terminate}, handler=>handler(Handler), name=>SName, - last_message=>LastIn, - state=>format_status(terminate,Handler#handler.module, - get(),State), - reason=>Reason}, + last_message=>maps:get(message,Status), + state=>maps:get('$status',Status,maps:get(state,Status)), + reason=>ReasonFun(maps:get(reason,Status))}, #{domain=>[otp], report_cb=>fun gen_event:format_log/2, error_logger=>#{tag=>error, @@ -992,24 +1020,19 @@ get_modules(MSL) -> %% Status information %%----------------------------------------------------------------- format_status(Opt, StatusData) -> - [PDict, SysState, Parent, _Debug, [ServerName, MSL, _HibernateAfterTimeout, _Hib]] = StatusData, - Header = gen:format_status_header("Status for event handler", - ServerName), - FmtMSL = [MS#handler{state=format_status(Opt, Mod, PDict, State)} - || #handler{module = Mod, state = State} = MS <- MSL], + [PDict, SysState, Parent, Debug, [ServerName, MSL, _HibernateAfterTimeout, _Hib]] = StatusData, + Header = gen:format_status_header("Status for event handler", ServerName), + {FmtMSL, Logs} = + lists:mapfoldl( + fun(#handler{module = Mod, state = State} = MS, Logs) -> + Status = gen:format_status( + Mod, Opt, #{ log => Logs, state => State }, + [PDict, State]), + {MS#handler{state=maps:get('$status',Status,maps:get(state,Status))}, + maps:get(log,Status)} + end, sys:get_log(Debug), MSL), [{header, Header}, {data, [{"Status", SysState}, + {"Logged Events", Logs}, {"Parent", Parent}]}, {items, {"Installed handlers", FmtMSL}}]. - -format_status(Opt, Mod, PDict, State) -> - case erlang:function_exported(Mod, format_status, 2) of - true -> - Args = [PDict, State], - case catch Mod:format_status(Opt, Args) of - {'EXIT', _} -> State; - Else -> Else - end; - false -> - State - end. diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index 9da3c59ff9..2cd212d7d1 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -168,6 +168,12 @@ -callback code_change(OldVsn :: (term() | {down, term()}), State :: term(), Extra :: term()) -> {ok, NewState :: term()} | {error, Reason :: term()}. +-callback format_status(Status) -> NewStatus when + Status :: #{ state => term(), + message => term(), + reason => term(), + log => [sys:system_event()] }, + NewStatus :: Status. -callback format_status(Opt, StatusData) -> Status when Opt :: 'normal' | 'terminate', StatusData :: [PDict | State], @@ -176,7 +182,8 @@ Status :: term(). -optional_callbacks( - [handle_info/2, handle_continue/2, terminate/2, code_change/3, format_status/2]). + [handle_info/2, handle_continue/2, terminate/2, code_change/3, + format_status/1, format_status/2]). %%% ----------------------------------------------------------------- %%% Starts a generic server. @@ -906,27 +913,28 @@ print_event(Dev, Event, Name) -> -spec terminate(_, _, _, _, _, _, _, _) -> no_return(). terminate(Reason, Stacktrace, Name, From, Msg, Mod, State, Debug) -> - terminate(exit, Reason, Stacktrace, Reason, Name, From, Msg, Mod, State, Debug). + terminate(exit, Reason, Stacktrace, false, Name, From, Msg, Mod, State, Debug). -spec terminate(_, _, _, _, _, _, _, _, _) -> no_return(). terminate(Class, Reason, Stacktrace, Name, From, Msg, Mod, State, Debug) -> - ReportReason = {Reason, Stacktrace}, - terminate(Class, Reason, Stacktrace, ReportReason, Name, From, Msg, Mod, State, Debug). + terminate(Class, Reason, Stacktrace, true, Name, From, Msg, Mod, State, Debug). -spec terminate(_, _, _, _, _, _, _, _, _, _) -> no_return(). -terminate(Class, Reason, Stacktrace, ReportReason, Name, From, Msg, Mod, State, Debug) -> +terminate(Class, Reason, Stacktrace, ReportStacktrace, Name, From, Msg, Mod, State, Debug) -> Reply = try_terminate(Mod, terminate_reason(Class, Reason, Stacktrace), State), case Reply of {'EXIT', C, R, S} -> - error_info({R, S}, Name, From, Msg, Mod, State, Debug), + error_info(R, S, Name, From, Msg, Mod, State, Debug), erlang:raise(C, R, S); _ -> case {Class, Reason} of {exit, normal} -> ok; {exit, shutdown} -> ok; {exit, {shutdown,_}} -> ok; - _ -> - error_info(ReportReason, Name, From, Msg, Mod, State, Debug) + _ when ReportStacktrace -> + error_info(Reason, Stacktrace, Name, From, Msg, Mod, State, Debug); + _ -> + error_info(Reason, undefined, Name, From, Msg, Mod, State, Debug) end end, case Stacktrace of @@ -939,19 +947,37 @@ terminate(Class, Reason, Stacktrace, ReportReason, Name, From, Msg, Mod, State, terminate_reason(error, Reason, Stacktrace) -> {Reason, Stacktrace}; terminate_reason(exit, Reason, _Stacktrace) -> Reason. -error_info(_Reason, application_controller, _From, _Msg, _Mod, _State, _Debug) -> +error_info(_Reason, _ST, application_controller, _From, _Msg, _Mod, _State, _Debug) -> %% OTP-5811 Don't send an error report if it's the system process %% application_controller which is terminating - let init take care %% of it instead ok; -error_info(Reason, Name, From, Msg, Mod, State, Debug) -> +error_info(Reason, ST, Name, From, Msg, Mod, State, Debug) -> Log = sys:get_log(Debug), + Status = + gen:format_status(Mod, terminate, + #{ reason => Reason, + state => State, + message => Msg, + log => Log }, + [get(),State]), + ReportReason = + if ST == undefined -> + %% When ST is undefined, it should not be included in the + %% reported reason for the crash as it is then caused + %% by an invalid return from a callback and thus thus the + %% stacktrace is irrelevant. + maps:get(reason, Status); + true -> + {maps:get(reason, Status), ST} + end, + ?LOG_ERROR(#{label=>{gen_server,terminate}, name=>Name, - last_message=>Msg, - state=>format_status(terminate, Mod, get(), State), - log=>format_log_state(Mod, Log), - reason=>Reason, + last_message=>maps:get(message,Status), + state=>maps:get('EXIT',Status,maps:get('$status',Status,maps:get(state,Status))), + log=>format_log_state(Mod,maps:get(log,Status)), + reason=>ReportReason, client_info=>client_stacktrace(From)}, #{domain=>[otp], report_cb=>fun gen_server:format_log/2, @@ -1212,37 +1238,42 @@ mod(_) -> "t". format_status(Opt, StatusData) -> [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time, _HibernateAfterTimeout]] = StatusData, Header = gen:format_status_header("Status for generic server", Name), - Log = sys:get_log(Debug), - Specific = case format_status(Opt, Mod, PDict, State) of - S when is_list(S) -> S; - S -> [S] - end, + Status = + case gen:format_status(Mod, Opt, #{ state => State, log => sys:get_log(Debug) }, + [PDict, State]) of + #{ 'EXIT' := R } = M -> + M#{ '$status' => [{data,[{"State",R}]}] }; + %% Status is set when the old format_status/2 is called, + %% so we do a little backwards compatibility dance here + #{ '$status' := S } = M when is_list(S) -> M; + #{ '$status' := S } = M -> M#{ '$status' := [S] }; + #{ state := S } = M -> + M#{ '$status' => [{data, [{"State",S}] }] } + end, [{header, Header}, {data, [{"Status", SysState}, {"Parent", Parent}, - {"Logged events", format_log_state(Mod, Log)}]} | - Specific]. + {"Logged events", format_log_state(Mod, maps:get(log,Status))}]} | + maps:get('$status',Status)]. format_log_state(Mod, Log) -> - [case Event of - {out,Msg,From,State} -> - {out,Msg,From,format_status(terminate, Mod, get(), State)}; - {noreply,State} -> - {noreply,format_status(terminate, Mod, get(), State)}; - _ -> Event - end || Event <- Log]. - -format_status(Opt, Mod, PDict, State) -> - DefStatus = case Opt of - terminate -> State; - _ -> [{data, [{"State", State}]}] - end, - case erlang:function_exported(Mod, format_status, 2) of - true -> - case catch Mod:format_status(Opt, [PDict, State]) of - {'EXIT', _} -> DefStatus; - Else -> Else - end; - _ -> - DefStatus + %% If format_status/1 was exported, the log has already been handled by + %% that call, so we should not pass all log events into the callback again. + case erlang:function_exported(Mod, format_status, 1) of + false -> + [case Event of + {out,Msg,From,State} -> + Status = gen:format_status( + Mod, terminate, #{ state => State }, + [get(), State]), + {out, Msg, From, maps:get(state, Status) }; + {noreply,State} -> + Status = gen:format_status( + Mod, terminate, #{ state => State }, + [get(), State]), + {noreply, maps:get(state, Status)}; + _ -> Event + end || Event <- Log]; + true -> + Log end. diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index e91a24fd53..6086bb02b2 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -338,6 +338,8 @@ %% often condensed way. For StatusOption =:= 'normal' the preferred %% return term is [{data,[{"State",FormattedState}]}], and for %% StatusOption =:= 'terminate' it is just FormattedState. +%% +%% Deprecated -callback format_status( StatusOption, [ [{Key :: term(), Value :: term()}] | @@ -346,8 +348,21 @@ Status :: term() when StatusOption :: 'normal' | 'terminate'. +%% Format the callback module status in some sensible that is +%% often condensed way. +-callback format_status(Status) -> NewStatus when + Status :: #{ state => state(), + data => data(), + reason => term(), + queue => [{event_type(), term()}], + postponed => [{event_type(), term()}], + timeouts => [{timeout_event_type(), term()}], + log => [sys:system_event()] }, + NewStatus :: Status. + -optional_callbacks( - [format_status/2, % Has got a default implementation + [format_status/1, % Has got a default implementation + format_status/2, % Has got a default implementation terminate/3, % Has got a default implementation code_change/4, % Only needed by advanced soft upgrade %% @@ -893,22 +908,39 @@ system_replace_state( format_status( Opt, [PDict,SysState,Parent,Debug, - {#params{name = Name, modules = Modules} = P, - #state{postponed = Postponed, timers = Timers} = S}]) -> + {#params{name = Name, modules = [Mod | _] = Modules}, + #state{postponed = Postponed, timers = Timers, + state_data = {State,Data}}}]) -> Header = gen:format_status_header("Status for state machine", Name), - Log = sys:get_log(Debug), + + {NumTimers, ListTimers} = list_timeouts(Timers), + StatusMap = #{ state => State, data => Data, + postponed => Postponed, log => sys:get_log(Debug), + timeouts => ListTimers + }, + + NewStatusMap = + case gen:format_status(Mod, Opt, StatusMap, [PDict,State,Data]) of + #{ 'EXIT' := R } -> + Crashed = [{data,[{"State",{State,R}}]}], + StatusMap#{ '$status' => Crashed }; + %% Status is set when the old format_status/2 is called, + %% so we do a little backwards compatibility dance here + #{ '$status' := L } = SM when is_list(L) -> SM; + #{ '$status' := T } = SM -> SM#{ '$status' := [T] }; + #{ state := S, data := D } = SM -> + SM#{ '$status' => [{data,[{"State",{S,D}}]}]} + end, + [{header,Header}, {data, [{"Status",SysState}, {"Parent",Parent}, {"Modules",Modules}, - {"Time-outs",list_timeouts(Timers)}, - {"Logged Events",Log}, - {"Postponed",Postponed}]} | - case format_status(Opt, PDict, update_parent(P, Parent), S) of - L when is_list(L) -> L; - T -> [T] - end]. + {"Time-outs",{NumTimers,maps:get(timeouts,NewStatusMap)}}, + {"Logged Events",maps:get(log,NewStatusMap)}, + {"Postponed",maps:get(postponed,NewStatusMap)}]} | + maps:get('$status',NewStatusMap)]. %% Update #params.parent only if it differs. This should not %% be possible today (OTP-22.0), but could happen for example @@ -2390,25 +2422,44 @@ error_info( Class, Reason, Stacktrace, Debug, #params{ name = Name, - modules = Modules, + modules = [Mod|_] = Modules, callback_mode = CallbackMode, - state_enter = StateEnter} = P, + state_enter = StateEnter}, #state{ postponed = Postponed, - timers = Timers} = S, + timers = Timers, + state_data = {State,Data}}, Q) -> - Log = sys:get_log(Debug), + + {NumTimers,ListTimers} = list_timeouts(Timers), + + Status = + gen:format_status(Mod, terminate, + #{ reason => Reason, + state => State, + data => Data, + queue => Q, + postponed => Postponed, + timeouts => ListTimers, + log => sys:get_log(Debug)}, + [get(),State,Data]), + NewState = case maps:find('$status', Status) of + error -> + {maps:get(state,Status),maps:get(data,Status)}; + {ok, S} -> + S + end, ?LOG_ERROR(#{label=>{gen_statem,terminate}, name=>Name, - queue=>Q, - postponed=>Postponed, + queue=>maps:get(queue,Status), + postponed=>maps:get(postponed,Status), modules=>Modules, callback_mode=>CallbackMode, state_enter=>StateEnter, - state=>format_status(terminate, get(), P, S), - timeouts=>list_timeouts(Timers), - log=>Log, - reason=>{Class,Reason,Stacktrace}, + state=>NewState, + timeouts=>{NumTimers,maps:get(timeouts,Status)}, + log=>maps:get(log,Status), + reason=>{Class,maps:get(reason,Status),Stacktrace}, client_info=>client_stacktrace(Q)}, #{domain=>[otp], report_cb=>fun gen_statem:format_log/2, @@ -2741,34 +2792,6 @@ single(false) -> "". mod(latin1) -> ""; mod(_) -> "t". -%% Call Module:format_status/2 or return a default value -format_status( - Opt, PDict, - #params{modules = [Module | _]}, - #state{state_data = {State,Data} = State_Data}) -> - case erlang:function_exported(Module, format_status, 2) of - true -> - try Module:format_status(Opt, [PDict,State,Data]) - catch - Result -> Result; - _:_ -> - format_status_default( - Opt, - {State, - atom_to_list(Module) ++ ":format_status/2 crashed"}) - end; - false -> - format_status_default(Opt, State_Data) - end. - -%% The default Module:format_status/3 -format_status_default(Opt, State_Data) -> - case Opt of - terminate -> - State_Data; - _ -> - [{data,[{"State",State_Data}]}] - end. -compile({inline, [listify/1]}). listify(Item) when is_list(Item) -> diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl index e803b749f7..d4152c4f0a 100644 --- a/lib/stdlib/src/sys.erl +++ b/lib/stdlib/src/sys.erl @@ -40,7 +40,7 @@ %% Types %%----------------------------------------------------------------- --export_type([dbg_opt/0, dbg_fun/0, debug_option/0]). +-export_type([dbg_opt/0, dbg_fun/0, debug_option/0, system_event/0]). -type name() :: pid() | atom() | {'global', term()} diff --git a/lib/stdlib/test/dummy_h.erl b/lib/stdlib/test/dummy_h.erl index 7a9eb11f98..f0693e6069 100644 --- a/lib/stdlib/test/dummy_h.erl +++ b/lib/stdlib/test/dummy_h.erl @@ -22,7 +22,7 @@ %% Test event handler for gen_event_SUITE.erl -export([init/1, handle_event/2, handle_call/2, handle_info/2, - terminate/2]). + terminate/2, format_status/1]). init(make_error) -> {error, my_error}; @@ -97,3 +97,5 @@ terminate(_Reason, {undef_in_terminate, {Mod, Fun}}) -> terminate(_Reason, _State) -> ok. +format_status(#{ state := _State } = S) -> + S#{ state := "dummy1_h handler state" }. diff --git a/lib/stdlib/test/erl_internal_SUITE.erl b/lib/stdlib/test/erl_internal_SUITE.erl index 7d9df1f989..7d147956b9 100644 --- a/lib/stdlib/test/erl_internal_SUITE.erl +++ b/lib/stdlib/test/erl_internal_SUITE.erl @@ -80,7 +80,7 @@ callbacks(application) -> callbacks(gen_server) -> [{init,1}, {handle_call,3}, {handle_cast,2}, {handle_info,2}, {terminate,2}, {code_change,3}, - {format_status,2}, {handle_continue, 2}]; + {format_status,1}, {format_status,2}, {handle_continue, 2}]; callbacks(gen_fsm) -> [{init,1}, {handle_event,3}, {handle_sync_event,4}, {handle_info,3}, {terminate,3}, {code_change,4}, @@ -88,11 +88,11 @@ callbacks(gen_fsm) -> callbacks(gen_event) -> [{init,1}, {handle_event,2}, {handle_call,2}, {handle_info,2}, {terminate,2}, {code_change,3}, - {format_status,2}]; + {format_status,1}, {format_status,2}]; callbacks(gen_statem) -> [{init, 1}, {callback_mode, 0}, {'StateName', 3}, {handle_event, 4}, {terminate, 3}, {code_change, 4}, - {format_status, 2}]; + {format_status, 1}, {format_status, 2}]; callbacks(supervisor_bridge) -> [{init,1}, {terminate,2}]; callbacks(supervisor) -> @@ -101,14 +101,14 @@ callbacks(supervisor) -> optional_callbacks(application) -> []; optional_callbacks(gen_server) -> - [{handle_info, 2}, {handle_continue, 2}, {terminate, 2}, {code_change, 3}, {format_status, 2}]; + [{handle_info, 2}, {handle_continue, 2}, {terminate, 2}, {code_change, 3}, {format_status, 1}, {format_status, 2}]; optional_callbacks(gen_fsm) -> [{handle_info, 3}, {terminate, 3}, {code_change, 4}, {format_status, 2}]; optional_callbacks(gen_event) -> - [{handle_info, 2}, {terminate, 2}, {code_change, 3}, {format_status, 2}]; + [{handle_info, 2}, {terminate, 2}, {code_change, 3}, {format_status, 1}, {format_status, 2}]; optional_callbacks(gen_statem) -> [{'StateName', 3}, {handle_event, 4}, - {terminate, 3}, {code_change, 4}, {format_status, 2}]; + {terminate, 3}, {code_change, 4}, {format_status, 1}, {format_status, 2}]; optional_callbacks(supervisor_bridge) -> []; optional_callbacks(supervisor) -> diff --git a/lib/stdlib/test/gen_event_SUITE.erl b/lib/stdlib/test/gen_event_SUITE.erl index cb292cf01f..ffbbebabdc 100644 --- a/lib/stdlib/test/gen_event_SUITE.erl +++ b/lib/stdlib/test/gen_event_SUITE.erl @@ -974,7 +974,7 @@ call(Config) when is_list(Config) -> ok. flush() -> - receive _ -> flush() after 0 -> ok end. + receive M -> [M|flush()] after 0 -> [] end. info(Config) when is_list(Config) -> {ok,_} = gen_event:start({local, my_dummy_handler}), @@ -1100,22 +1100,25 @@ info(Config) when is_list(Config) -> ok = gen_event:stop(my_dummy_handler), ok. -%% Test that sys:get_status/1,2 calls format_status/2. +%% Test that sys:get_status/1,2 calls format_status/1,2. call_format_status(Config) when is_list(Config) -> + call_format_status(dummy1_h), + call_format_status(dummy_h); +call_format_status(Module) when is_atom(Module) -> {ok, Pid} = gen_event:start({local, my_dummy_handler}), %% State here intentionally differs from what we expect from format_status State = self(), FmtState = "dummy1_h handler state", - ok = gen_event:add_handler(my_dummy_handler, dummy1_h, [State]), + ok = gen_event:add_handler(my_dummy_handler, Module, [State]), Status1 = sys:get_status(Pid), Status2 = sys:get_status(Pid, 5000), ok = gen_event:stop(Pid), {status, Pid, _, [_, _, Pid, [], Data1]} = Status1, HandlerInfo1 = proplists:get_value(items, Data1), - {"Installed handlers", [{_,dummy1_h,_,FmtState,_}]} = HandlerInfo1, + {"Installed handlers", [{_,Module,_,FmtState,_}]} = HandlerInfo1, {status, Pid, _, [_, _, Pid, [], Data2]} = Status2, HandlerInfo2 = proplists:get_value(items, Data2), - {"Installed handlers", [{_,dummy1_h,_,FmtState,_}]} = HandlerInfo2, + {"Installed handlers", [{_,Module,_,FmtState,_}]} = HandlerInfo2, ok. %% Test that sys:get_status/1,2 calls format_status/2 for anonymous @@ -1136,28 +1139,38 @@ call_format_status_anon(Config) when is_list(Config) -> error_format_status(Config) when is_list(Config) -> error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), + try + error_format_status(dummy1_h), + error_format_status(dummy_h) + after + process_flag(trap_exit, OldFl), + error_logger_forwarder:unregister() + end; +error_format_status(Module) when is_atom(Module) -> State = self(), {ok, Pid} = gen_event:start({local, my_dummy_handler}), - ok = gen_event:add_sup_handler(my_dummy_handler, dummy1_h, [State]), + ok = gen_event:add_sup_handler(my_dummy_handler, Module, [State]), ok = gen_event:notify(my_dummy_handler, do_crash), receive - {gen_event_EXIT,dummy1_h,{'EXIT',_}} -> ok + {gen_event_EXIT,Module,{'EXIT',_}} -> ok after 5000 -> - ct:fail(exit_gen_event) + ct:fail({exit_gen_event,flush()}) end, FmtState = "dummy1_h handler state", receive {error,_GroupLeader, {Pid, "** gen_event handler"++_, - [dummy1_h,my_dummy_handler,do_crash, + [Module,my_dummy_handler,do_crash, FmtState, _]}} -> ok; Other -> - io:format("Unexpected: ~p", [Other]), + ct:pal("Unexpected: ~p", [Other]), ct:fail(failed) + after 5000 -> + ct:fail({exit_gen_event,flush()}) end, + unlink(Pid), ok = gen_event:stop(Pid), - process_flag(trap_exit, OldFl), ok. %% Test that sys:get_state/1,2 return the gen_event state. diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl index 0a7cf80923..f2b99ad360 100644 --- a/lib/stdlib/test/gen_server_SUITE.erl +++ b/lib/stdlib/test/gen_server_SUITE.erl @@ -33,7 +33,8 @@ spec_init_local_registered_parent/1, spec_init_global_registered_parent/1, otp_5854/1, hibernate/1, auto_hibernate/1, otp_7669/1, call_format_status/1, - error_format_status/1, terminate_crash_format/1, + error_format_status/1, terminate_crash_format/1, crash_in_format_status/1, + throw_in_format_status/1, format_all_status/1, get_state/1, replace_state/1, call_with_huge_message_queue/1, undef_handle_call/1, undef_handle_cast/1, undef_handle_info/1, undef_init/1, undef_code_change/1, undef_terminate1/1, @@ -71,7 +72,7 @@ all() -> spec_init_local_registered_parent, spec_init_global_registered_parent, otp_5854, hibernate, auto_hibernate, otp_7669, - call_format_status, error_format_status, terminate_crash_format, + {group, format_status}, get_state, replace_state, call_with_huge_message_queue, {group, undef_callbacks}, undef_in_terminate, undef_in_handle_info, @@ -80,12 +81,18 @@ all() -> groups() -> [{stop, [], [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, + {format_status, [], + [call_format_status, error_format_status, terminate_crash_format, + crash_in_format_status, throw_in_format_status, format_all_status]}, {undef_callbacks, [], [undef_handle_call, undef_handle_cast, undef_handle_info, undef_handle_continue, undef_init, undef_code_change, undef_terminate1, undef_terminate2]}]. init_per_suite(Config) -> + DataDir = ?config(data_dir, Config), + Server = filename:join(DataDir, "format_status_server.erl"), + {ok, format_status_server} = compile:file(Server), Config. end_per_suite(_Config) -> @@ -1330,48 +1337,65 @@ do_otp_7669_stop() -> ?MODULE, stop, []), undefined = global:whereis_name(?MODULE). -%% Verify that sys:get_status correctly calls our format_status/2 fun. +%% Verify that sys:get_status correctly calls our format_status/1,2 fun. call_format_status(Config) when is_list(Config) -> + OldFl = process_flag(trap_exit, true), + call_format_status(?MODULE, format_status_called), + call_format_status(format_status_server,{data,[{"State",format_status_called}]}), + process_flag(trap_exit, OldFl). +call_format_status(Module, Match) when is_atom(Module) -> + Parent = self(), {ok, Pid} = gen_server:start_link({local, call_format_status}, - ?MODULE, [], []), + Module, [], []), Status1 = sys:get_status(call_format_status), {status, Pid, Mod, [_Pdict1, running, Parent, _, Data1]} = Status1, - [format_status_called | _] = lists:reverse(Data1), + [Match | _] = lists:reverse(Data1), Status2 = sys:get_status(call_format_status, 5000), {status, Pid, Mod, [_Pdict2, running, Parent, _, Data2]} = Status2, - [format_status_called | _] = lists:reverse(Data2), + [Match | _] = lists:reverse(Data2), + gen_server:call(Pid, stop), + receive {'EXIT',_,_} -> ok end, %% check that format_status can handle a name being a pid (atom is %% already checked by the previous test) - {ok, Pid3} = gen_server:start_link(gen_server_SUITE, [], []), + {ok, Pid3} = gen_server:start_link(Module, [], []), Status3 = sys:get_status(Pid3), {status, Pid3, Mod, [_PDict3, running, Parent, _, Data3]} = Status3, - [format_status_called | _] = lists:reverse(Data3), + [Match | _] = lists:reverse(Data3), + gen_server:call(Pid3, stop), + receive {'EXIT',_,_} -> ok end, %% check that format_status can handle a name being a term other than a %% pid or atom GlobalName1 = {global, "CallFormatStatus"}, - {ok, Pid4} = gen_server:start_link(GlobalName1, - gen_server_SUITE, [], []), + {ok, Pid4} = gen_server:start_link(GlobalName1, Module, [], []), Status4 = sys:get_status(Pid4), {status, Pid4, Mod, [_PDict4, running, Parent, _, Data4]} = Status4, - [format_status_called | _] = lists:reverse(Data4), + [Match | _] = lists:reverse(Data4), + gen_server:call(Pid4, stop), + receive {'EXIT',_,_} -> ok end, GlobalName2 = {global, {name, "term"}}, - {ok, Pid5} = gen_server:start_link(GlobalName2, - gen_server_SUITE, [], []), + {ok, Pid5} = gen_server:start_link(GlobalName2, Module, [], []), Status5 = sys:get_status(GlobalName2), {status, Pid5, Mod, [_PDict5, running, Parent, _, Data5]} = Status5, - [format_status_called | _] = lists:reverse(Data5), + [Match | _] = lists:reverse(Data5), + gen_server:call(Pid5, stop), + receive {'EXIT',_,_} -> ok end, ok. -%% Verify that error termination correctly calls our format_status/2 fun. +%% Verify that error termination correctly calls our format_status/1,2 fun. error_format_status(Config) when is_list(Config) -> error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), + error_format_status(?MODULE), + error_format_status(format_status_server), + process_flag(trap_exit, OldFl); +error_format_status(Module) when is_atom(Module) -> + State = "called format_status", - {ok, Pid} = gen_server:start_link(?MODULE, {state, State}, []), + {ok, Pid} = gen_server:start_link(Module, {state, State}, []), {'EXIT',{crashed,_}} = (catch gen_server:call(Pid, crash)), receive {'EXIT', Pid, crashed} -> @@ -1387,19 +1411,24 @@ error_format_status(Config) when is_list(Config) -> ClientPid, [_|_] = _ClientStack]}} -> ok; Other -> - io:format("Unexpected: ~p", [Other]), + ct:pal("Unexpected: ~p", [Other]), ct:fail(failed) end, - process_flag(trap_exit, OldFl), + receive + {error_report,_,_} -> ok + end, ok. -%% Verify that error when terminating correctly calls our format_status/2 fun -%% +%% Verify that error when terminating correctly calls our format_status/1,2 fun terminate_crash_format(Config) when is_list(Config) -> error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), + terminate_crash_format(?MODULE), + terminate_crash_format(format_status_server), + process_flag(trap_exit, OldFl); +terminate_crash_format(Module) when is_atom(Module) -> State = crash_terminate, - {ok, Pid} = gen_server:start_link(?MODULE, {state, State}, []), + {ok, Pid} = gen_server:start_link(Module, {state, State}, []), gen_server:call(Pid, stop), receive {'EXIT', Pid, {crash, terminate}} -> ok end, ClientPid = self(), @@ -1418,9 +1447,167 @@ terminate_crash_format(Config) when is_list(Config) -> io:format("Timeout: expected error logger msg", []), ct:fail(failed) end, - process_flag(trap_exit, OldFl), + receive + {error_report,_,_} -> ok + end, + ok. + +crash_in_format_status(Config) when is_list(Config) -> + error_logger_forwarder:register(), + OldFl = process_flag(trap_exit, true), + crash_in_format_status(?MODULE, "gen_server_SUITE:format_status/2 crashed"), + crash_in_format_status(format_status_server, "format_status_server:format_status/1 crashed"), + process_flag(trap_exit, OldFl). +crash_in_format_status(Module, Match) when is_atom(Module) -> + State = fun(_) -> exit({crash,format_status}) end, + {ok, Pid} = gen_server:start_link(Module, {state, State}, []), + + {status,Pid, _, [_,_,_,_,Info]} = sys:get_status(Pid), + {data,[{"State",Match}]} = lists:last(Info), + + gen_server:call(Pid, stop), + receive {'EXIT', Pid, stopped} -> ok end, + ClientPid = self(), + receive + {error,_GroupLeader, + {Pid, + "** Generic server"++_, + [Pid, stop, Match, stopped, + ClientPid, [_|_] = _ClientStack]}} -> + ok; + Other -> + ct:pal("Unexpected: ~p", [Other]), + ct:fail(failed) + after 5000 -> + io:format("Timeout: expected error logger msg", []), + ct:fail(failed) + end, + receive + {error_report,_,_} -> ok + end, + ok. + +throw_in_format_status(Config) when is_list(Config) -> + error_logger_forwarder:register(), + OldFl = process_flag(trap_exit, true), + throw_in_format_status(?MODULE,{throw,format_status}), + throw_in_format_status(format_status_server,"format_status_server:format_status/1 crashed"), + process_flag(trap_exit, OldFl). +throw_in_format_status(Module, Match) when is_atom(Module) -> + State = fun(_) -> throw({throw,format_status}) end, + {ok, Pid} = gen_server:start_link(Module, {state, State}, []), + + {status,Pid, _, [_,_,_,_,Info]} = sys:get_status(Pid), + case lists:last(Info) of + {data,[{"State",Match}]} -> + ok; + Match -> + ok + end, + + gen_server:call(Pid, stop), + receive {'EXIT', Pid, stopped} -> ok end, + ClientPid = self(), + receive + {error,_GroupLeader, + {Pid, "** Generic server"++_, + [Pid, stop, Match, stopped, + ClientPid, [_|_] = _ClientStack]}} -> + ok; + Other -> + ct:pal("Unexpected: ~p", [Other]), + ct:fail(failed) + after 5000 -> + io:format("Timeout: expected error logger msg", []), + ct:fail(failed) + end, + receive + {error_report,_,_} -> ok + end, ok. +%% Test that the state, reason, message format status calls works as they should +%% The test makes sure that both sys:get_status and the crash report works as they +%% should and can be used to strip data from both the reason, message, state and the +%% sys logger logs. + +%%%% The sys logger log messages that should be matched +-define(LOG_MESSAGES, + {log,{in,{_,_,started_p}}}, + {log,{out,ok,_,State}}, + {log,{in,{_,_,{delayed_answer,10}}}}, + {log,{noreply,{_,_,State}}}, + {log,{in,timeout}}, + {log,{noreply,State}}). + +format_all_status(Config) when is_list(Config) -> + error_logger_forwarder:register(), + OldFl = process_flag(trap_exit, true), + + State = fun(M) -> + maps:map( + fun(log, Values) -> + [{log, Value} || Value <- Values]; + (Key, Value) -> + {Key, Value} + end, M) + end, + {ok, Pid} = gen_server:start_link(format_status_server, {state, State}, []), + sys:log(Pid, true), + ok = gen_server:call(Pid, started_p), + delayed = gen_server:call(Pid, {delayed_answer, 10}), + + {status,Pid, _, [_,_,_,_,Info]} = sys:get_status(Pid), + [{header, _Hdr}, + {data, [_Status,_Parent,{"Logged events",LoggedEvents}]}, + {data, [{"State",{state,State}}]}] = Info, + + [?LOG_MESSAGES] = LoggedEvents, + + ok = gen_server:call(Pid, stop), + receive {'EXIT', Pid, stopped} -> ok end, + ClientPid = self(), + receive + {error,_GroupLeader, + {Pid, "** Generic server"++_, + [Pid, {message, stop}, {state,State}, {reason, stopped}, + ?LOG_MESSAGES, {log,{in,{_,_,stop}}}, + ClientPid, [_|_] = _ClientStack]}} -> + ok; + Other -> + ct:pal("Unexpected: ~p", [Other]), + ct:fail(failed) + after 5000 -> + io:format("Timeout: expected error logger msg", []), + ct:fail(failed) + end, + receive + {error_report,_,_} -> ok + end, + + {ok, Pid2} = gen_server:start_link(format_status_server, {state, State}, []), + catch gen_server:call(Pid2, crash), + receive {'EXIT', Pid2, crashed} -> ok end, + receive + {error,_GroupLeader2, + {Pid2, "** Generic server"++_, + [Pid2, {message, crash}, {state,State}, + {{reason, crashed},[_|_] = _ServerStack}, + ClientPid, [_|_] = _ClientStack2]}} -> + ok; + Other2 -> + ct:pal("Unexpected: ~p", [Other2]), + ct:fail(failed) + after 5000 -> + io:format("Timeout: expected error logger msg", []), + ct:fail(failed) + end, + receive + {error_report,_,_} -> ok + end, + + process_flag(trap_exit, OldFl). + %% Verify that sys:get_state correctly returns gen_server state get_state(Config) when is_list(Config) -> State = self(), @@ -1985,8 +2172,8 @@ init({state,State}) -> handle_call(started_p, _From, State) -> io:format("FROZ"), {reply,ok,State}; -handle_call({delayed_answer, T}, From, _State) -> - {noreply,{reply_to,From},T}; +handle_call({delayed_answer, T}, From, State) -> + {noreply,{reply_to,From,State},T}; handle_call({call_within, T}, _From, _) -> {reply,ok,call_within,T}; handle_call(next_call, _From, call_within) -> @@ -2042,9 +2229,9 @@ handle_cast({From, stop}, State) -> io:format("BAZ"), {stop, {From,stopped}, State}. -handle_info(timeout, {reply_to, From}) -> +handle_info(timeout, {reply_to, From, State}) -> gen_server:reply(From, delayed), - {noreply, []}; + {noreply, State}; handle_info(timeout, hibernate_me) -> % Arrive here from % handle_info(hibernate_later,...) {noreply, [], hibernate}; @@ -2124,6 +2311,8 @@ terminate(_, {undef_in_terminate, {Mod, Fun}}) -> terminate(_Reason, _State) -> ok. +format_status(_, [_PDict, Fun] = S) when is_function(Fun) -> + Fun(S); format_status(terminate, [_PDict, State]) -> {formatted, State}; format_status(normal, [_PDict, _State]) -> diff --git a/lib/stdlib/test/gen_server_SUITE_data/format_status_server.erl b/lib/stdlib/test/gen_server_SUITE_data/format_status_server.erl new file mode 100644 index 0000000000..91f8951a42 --- /dev/null +++ b/lib/stdlib/test/gen_server_SUITE_data/format_status_server.erl @@ -0,0 +1,55 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017. 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(format_status_server). + +-behaviour(gen_server). + +%% API +-export([start/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, format_status/1]). + +start(Arg) -> + gen_server:start(?MODULE, ok, Arg). +init(Args) -> + gen_server_SUITE:init(Args). +handle_call(Call, From, State) -> + gen_server_SUITE:handle_call(Call, From, State). +handle_cast(Cast, State) -> + gen_server_SUITE:handle_cast(Cast, State). +handle_info(Info, State) -> + gen_server_SUITE:handle_info(Info, State). +terminate(Reason, State) -> + gen_server_SUITE:terminate(Reason, State). + +format_status(#{ state := Fun } = S) when is_function(Fun) -> + Fun(S); +format_status(#{ state := {_,_,Fun} } = S) when is_function(Fun) -> + Fun(S); +format_status(#{ message := Msg } = S) when not is_map_key(state, S) -> + S#{message := {message,Msg}}; +format_status(#{ reason := _, state := State } = Map) -> + ct:pal("format_status(~p)",[Map]), + Map#{ state => {formatted, State}}; +format_status(Map) -> + ct:pal("format_status(~p)",[Map]), + Map#{ state => format_status_called }. diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index d216a8fdf0..3cde4a74b6 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -53,6 +53,7 @@ groups() -> {abnormal, [], tcs(abnormal)}, {abnormal_handle_event, [], tcs(abnormal)}, {sys, [], tcs(sys)}, + {format_status, [], tcs(format_status)}, {sys_handle_event, [], tcs(sys)}, {undef_callbacks, [], tcs(undef_callbacks)}, {format_log, [], tcs(format_log)}]. @@ -66,9 +67,10 @@ tcs(abnormal) -> [abnormal1, abnormal1clean, abnormal1dirty, abnormal2, abnormal3, abnormal4]; tcs(sys) -> - [sys1, call_format_status, - error_format_status, terminate_crash_format, - get_state, replace_state]; + [sys1, {group, format_status}, get_state, replace_state]; +tcs(format_status) -> + [call_format_status, error_format_status, terminate_crash_format, + format_all_status]; tcs(undef_callbacks) -> [undef_code_change, undef_terminate1, undef_terminate2, pop_too_many]; @@ -90,6 +92,9 @@ init_per_group(undef_callbacks, Config) -> {fail,{Class,Reason,Stacktrace}} end, Config; +init_per_group(sys, Config) -> + compile_format_status_statem(Config), + Config; init_per_group(_GroupName, Config) -> Config. @@ -119,6 +124,12 @@ compile_oc_statem(Config) -> {ok, oc_statem} = compile:file(StatemPath), ok. +compile_format_status_statem(Config) -> + DataDir = ?config(data_dir, Config), + StatemPath = filename:join(DataDir, "format_status_statem.erl"), + {ok, format_status_statem} = compile:file(StatemPath), + ok. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -define(EXPECT_FAILURE(Code, Reason), try begin Code end of @@ -1240,20 +1251,24 @@ code_change(_Config) -> stop_it(Pid). call_format_status(Config) -> - {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + call_format_status(Config,?MODULE,format_status_called), + call_format_status(Config, format_status_statem, + {data,[{"State",{format_status_called,format_data}}]}). +call_format_status(Config, Module, Match) -> + {ok,Pid} = gen_statem:start(Module, start_arg(Config, []), []), Status = sys:get_status(Pid), {status,Pid,_Mod,[_PDict,running,_,_, Data]} = Status, - [format_status_called|_] = lists:reverse(Data), + [Match|_] = lists:reverse(Data), stop_it(Pid), %% check that format_status can handle a name being an atom (pid is %% already checked by the previous test) {ok, Pid2} = gen_statem:start( - {local, gstm}, ?MODULE, start_arg(Config, []), []), + {local, gstm}, Module, start_arg(Config, []), []), Status2 = sys:get_status(gstm), {status,Pid2,Mod,[_PDict2,running,_,_,Data2]} = Status2, - [format_status_called|_] = lists:reverse(Data2), + [Match|_] = lists:reverse(Data2), stop_it(Pid2), %% check that format_status can handle a name being a term other than a @@ -1261,47 +1276,54 @@ call_format_status(Config) -> GlobalName1 = {global,"CallFormatStatus"}, {ok,Pid3} = gen_statem:start( - GlobalName1, ?MODULE, start_arg(Config, []), []), + GlobalName1, Module, start_arg(Config, []), []), Status3 = sys:get_status(GlobalName1), {status,Pid3,Mod,[_PDict3,running,_,_,Data3]} = Status3, - [format_status_called|_] = lists:reverse(Data3), + [Match|_] = lists:reverse(Data3), stop_it(Pid3), GlobalName2 = {global,{name, "term"}}, {ok,Pid4} = gen_statem:start( - GlobalName2, ?MODULE, start_arg(Config, []), []), + GlobalName2, Module, start_arg(Config, []), []), Status4 = sys:get_status(GlobalName2), {status,Pid4,Mod,[_PDict4,running,_,_, Data4]} = Status4, - [format_status_called|_] = lists:reverse(Data4), + [Match|_] = lists:reverse(Data4), stop_it(Pid4), %% check that format_status can handle a name being a term other than a %% pid or atom dummy_via:reset(), ViaName1 = {via,dummy_via,"CallFormatStatus"}, - {ok,Pid5} = gen_statem:start(ViaName1, ?MODULE, start_arg(Config, []), []), + {ok,Pid5} = gen_statem:start(ViaName1, Module, start_arg(Config, []), []), Status5 = sys:get_status(ViaName1), {status,Pid5,Mod, [_PDict5,running,_,_, Data5]} = Status5, - [format_status_called|_] = lists:reverse(Data5), + [Match|_] = lists:reverse(Data5), stop_it(Pid5), ViaName2 = {via,dummy_via,{name,"term"}}, {ok, Pid6} = gen_statem:start( - ViaName2, ?MODULE, start_arg(Config, []), []), + ViaName2, Module, start_arg(Config, []), []), Status6 = sys:get_status(ViaName2), {status,Pid6,Mod,[_PDict6,running,_,_,Data6]} = Status6, - [format_status_called|_] = lists:reverse(Data6), + [Match|_] = lists:reverse(Data6), stop_it(Pid6). - - error_format_status(Config) -> error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), + try + error_format_status(Config,?MODULE,{formatted,idle,"called format_status"}), + error_format_status(Config,format_status_statem, + {{formatted,idle},{formatted,"called format_status"}}) + after + process_flag(trap_exit, OldFl), + error_logger_forwarder:unregister() + end. +error_format_status(Config,Module,Match) -> Data = "called format_status", {ok,Pid} = gen_statem:start( - ?MODULE, start_arg(Config, {data,Data}), []), + Module, start_arg(Config, {data,Data}), []), %% bad return value in the gen_statem loop {{{bad_return_from_state_function,badreturn},_},_} = ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), @@ -1310,18 +1332,15 @@ error_format_status(Config) -> {Pid, "** State machine"++_, [Pid,{{call,_},badreturn}, - {formatted,idle,Data}, + Match, error,{bad_return_from_state_function,badreturn}|_]}} -> ok; Other when is_tuple(Other), element(1, Other) =:= error -> - error_logger_forwarder:unregister(), ct:fail({unexpected,Other}) after 1000 -> - error_logger_forwarder:unregister(), - ct:fail(timeout) + ct:fail({timeout,(fun F() -> receive M -> [M|F()] after 0 -> [] end end)()}) end, - process_flag(trap_exit, OldFl), - error_logger_forwarder:unregister(), + receive %% Comes with SASL {error_report,_,{Pid,crash_report,_}} -> @@ -1332,12 +1351,24 @@ error_format_status(Config) -> ok = verify_empty_msgq(). terminate_crash_format(Config) -> + dbg:tracer(), error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), + try + terminate_crash_format(Config,?MODULE,{formatted,idle,crash_terminate}), + terminate_crash_format(Config,format_status_statem, + {{formatted,idle},{formatted,crash_terminate}}) + after + dbg:stop_clear(), + process_flag(trap_exit, OldFl), + error_logger_forwarder:unregister() + end. + +terminate_crash_format(Config, Module, Match) -> Data = crash_terminate, {ok,Pid} = gen_statem:start( - ?MODULE, start_arg(Config, {data,Data}), []), + Module, start_arg(Config, {data,Data}), []), stop_it(Pid), Self = self(), receive @@ -1346,18 +1377,14 @@ terminate_crash_format(Config) -> "** State machine"++_, [Pid, {{call,{Self,_}},stop}, - {formatted,idle,Data}, - exit,{crash,terminate}|_]}} -> + Match,exit,{crash,terminate}|_]}} -> ok; Other when is_tuple(Other), element(1, Other) =:= error -> - error_logger_forwarder:unregister(), ct:fail({unexpected,Other}) after 1000 -> - error_logger_forwarder:unregister(), - ct:fail(timeout) + ct:fail({timeout,flush()}) end, - process_flag(trap_exit, OldFl), - error_logger_forwarder:unregister(), + receive %% Comes with SASL {error_report,_,{Pid,crash_report,_}} -> @@ -1367,6 +1394,67 @@ terminate_crash_format(Config) -> end, ok = verify_empty_msgq(). +%% We test that all of the different status items can be +%% formatted by the format_status/1 callback. +format_all_status(Config) -> + error_logger_forwarder:register(), + OldFl = process_flag(trap_exit, true), + + Data = fun(M) -> + maps:map( + fun(Key, Values) when Key =:= log; + Key =:= queue; + Key =:= postponed; + Key =:= timeouts -> + [{Key, Value} || Value <- Values]; + (Key, Value) -> + {Key, Value} + end, M) + end, + {ok,Pid} = + gen_statem:start( + format_status_statem, start_arg(Config, {data,Data}), []), + sys:log(Pid, true), + ok = gen_statem:cast(Pid, postpone_event), + ok = gen_statem:cast(Pid, {timeout, 100000}), + + {status,Pid, _, [_,_,_,_,Info]} = sys:get_status(Pid), + [{header, _Hdr}, + {data, [_Status,_Parent,_Modules, + {"Time-outs",{1,[{timeouts,_}]}}, + {"Logged Events",[{log,_}|_]}, + {"Postponed",[{postponed,_}]}]}, + {data, [{"State",{{state,idle},{data,Data}}}]}] = Info, + + %% bad return value in the gen_statem loop + {{{bad_return_from_state_function,badreturn},_},_} = + ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), + Self = self(), + receive + {error,_GroupLeader, + {Pid, + "** State machine"++_, + [Pid, + {queue,{{call,{Self,_}},badreturn}}, + {{state,idle},{data,Data}},error, + {reason,{bad_return_from_state_function,badreturn}}, + __Modules,_StateFunctions, + [{postponed,{cast,postpone_event}}], + [_|_] = _Stacktrace, + {1,[{timeouts,{timeout,idle}}]}, + [{log,_}|_] |_]}} -> + ok; + Other when is_tuple(Other), element(1, Other) =:= error -> + ct:fail({unexpected,Other}) + after 1000 -> + ct:fail({timeout,flush()}) + end, + receive + {error_report,_,_} -> ok + end, + error_logger_forwarder:unregister(), + process_flag(trap_exit, OldFl), + ok. get_state(Config) -> State = self(), @@ -2473,6 +2561,11 @@ idle({call,From}, {timeout,Time}, _Data) -> AbsTime = erlang:monotonic_time(millisecond) + Time, {next_state,timeout,{From,Time}, {timeout,AbsTime,idle,[{abs,true}]}}; +idle(cast, {timeout,Time}, _Data) -> + AbsTime = erlang:monotonic_time(millisecond) + Time, + {keep_state_and_data,{timeout,AbsTime,idle,[{abs,true}]}}; +idle(cast, postpone_event, _Data) -> + {keep_state_and_data,postpone}; idle(cast, next_event, _Data) -> {next_state,next_events,[a,b,c], [{next_event,internal,a}, diff --git a/lib/stdlib/test/gen_statem_SUITE_data/format_status_statem.erl b/lib/stdlib/test/gen_statem_SUITE_data/format_status_statem.erl new file mode 100644 index 0000000000..987e2594b3 --- /dev/null +++ b/lib/stdlib/test/gen_statem_SUITE_data/format_status_statem.erl @@ -0,0 +1,40 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017. 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(format_status_statem). + +%% gen_statem callbacks +-export(['$handle_undefined_function'/2, terminate/3, format_status/1]). + +'$handle_undefined_function'(format_status, [_,_]) -> + erlang:error(undef); +'$handle_undefined_function'(Func, Args) -> + apply(gen_statem_SUITE, Func, Args). + +terminate(Reason, State, Data) -> + gen_statem_SUITE:terminate(Reason, State, Data). + +format_status(#{ data := Fun } = S) when is_function(Fun) -> + Fun(S); +format_status(#{ reason := _, state := State, data := Data } = Map) -> + ct:pal("format_status(~p)",[Map]), + Map#{ state := {formatted, State}, data := {formatted, Data}}; +format_status(Map) -> + ct:pal("format_status(~p)",[Map]), + Map#{ data := format_data, state := format_status_called }. -- 2.31.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor