%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-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(proc_lib_SUITE). %% %% Define to run outside of test server %% %%-define(STANDALONE,1). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, crash/1, stacktrace/1, sync_start_nolink/1, sync_start_link/1, spawn_opt/1, sp1/0, sp2/0, sp3/1, sp4/2, sp5/1, '\x{447}'/0, hibernate/1, stop/1, t_format/1, t_format_arbitrary/1]). -export([ otp_6345/1, init_dont_hang/1]). -export([hib_loop/1, awaken/1]). -export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2]). -export([otp_6345_init/1, init_dont_hang_init/1]). -export([system_terminate/4]). -ifdef(STANDALONE). -define(line, noop, ). -else. -include_lib("common_test/include/ct.hrl"). -endif. suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [crash, stacktrace, {group, sync_start}, spawn_opt, hibernate, {group, tickets}, stop, t_format, t_format_arbitrary]. groups() -> [{tickets, [], [otp_6345, init_dont_hang]}, {sync_start, [], [sync_start_nolink, sync_start_link]}]. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. %%----------------------------------------------------------------- %% We don't have to test that spwn and spawn_link actually spawns %% new processes - if they don't we can't run this suite! %% But we want to test that start and start_link really is %% synchronous, and we want to test that the crash report is ok. %%----------------------------------------------------------------- crash(Config) when is_list(Config) -> ok = application:unset_env(kernel, error_logger_format_depth), crash_1(Config), ok = application:set_env(kernel, error_logger_format_depth, 30), crash_1(Config), ok = application:unset_env(kernel, error_logger_format_depth), ok. crash_1(_Config) -> error_logger:add_report_handler(?MODULE, self()), %% Make sure that we don't get a crash report if a process %% terminates with reason 'shutdown' or reason {shutdown,Reason}. process_flag(trap_exit, true), Pid0 = proc_lib:spawn_link(erlang, apply, [fun() -> exit(shutdown) end,[]]), Pid1 = proc_lib:spawn_link(erlang, apply, [fun() -> exit({shutdown,{a,b,c}}) end,[]]), receive {'EXIT',Pid0,shutdown} -> ok end, receive {'EXIT',Pid1,{shutdown,{a,b,c}}} -> ok end, process_flag(trap_exit, false), %% We expect any unexpected messages to be caught below, %% so we don't have explicitly wait some time to be sure. %% Spawn export function. Pid2 = proc_lib:spawn(?MODULE, sp1, []), Pid2 ! die, Exp2 = [{initial_call,{?MODULE,sp1,[]}}, {ancestors,[self()]}, {error_info,{exit,die,{stacktrace}}}], analyse_crash(Pid2, Exp2, []), %% Spawn fun. F = fun sp1/0, Pid3 = proc_lib:spawn(node(), F), Pid3 ! die, {module,?MODULE} = erlang:fun_info(F, module), {name,Fname} = erlang:fun_info(F, name), Exp3 = [{initial_call,{?MODULE,Fname,[]}}, {ancestors,[self()]}, {error_info,{exit,die,{stacktrace}}}], analyse_crash(Pid3, Exp3, []), %% Spawn function with neighbour. Pid4 = proc_lib:spawn(?MODULE, sp2, []), ct:sleep(100), {?MODULE,sp2,[]} = proc_lib:initial_call(Pid4), {?MODULE,sp2,0} = proc_lib:translate_initial_call(Pid4), Pid4 ! die, Exp4 = [{initial_call,{?MODULE,sp2,[]}}, {ancestors,[self()]}, {error_info,{exit,die,{stacktrace}}}], Links4 = [[{initial_call,{?MODULE,sp1,[]}}, {ancestors,[Pid4,self()]}]], analyse_crash(Pid4, Exp4, Links4), %% Make sure that we still get a crash report if the %% process dictionary have been tampered with. Pid5 = proc_lib:spawn(erlang, apply, [fun() -> erase(), exit(abnormal) end,[]]), Exp5 = [{initial_call,absent}, {ancestors,[]}, {error_info,{exit,abnormal,{stacktrace}}}], analyse_crash(Pid5, Exp5, []), %% Unicode atom Pid6 = proc_lib:spawn(?MODULE, '\x{447}', []), Pid6 ! die, Exp6 = [{initial_call,{?MODULE,'\x{447}',[]}}, {ancestors,[self()]}, {error_info,{exit,die,{stacktrace}}}], analyse_crash(Pid6, Exp6, []), error_logger:delete_report_handler(?MODULE), ok. analyse_crash(Pid, Expected0, ExpLinks) -> Expected = [{pid,Pid}|Expected0], receive {crash_report, Pid, Report} -> _ = proc_lib:format(Report), %Smoke test. [Crash,Links] = Report, analyse_crash_1(Expected, Crash), analyse_links(ExpLinks, Links); Unexpected -> io:format("~p\n", [Unexpected]), ct:fail(unexpected_message) after 5000 -> ct:fail(no_crash_report) end. analyse_links([H|Es], [{neighbour,N}|Links]) -> analyse_crash_1(H, N), analyse_links(Es, Links); analyse_links([], []) -> ok. analyse_crash_1([{Key,absent}|T], Report) -> false = lists:keymember(Key, 1, Report), analyse_crash_1(T, Report); analyse_crash_1([{Key,Pattern}|T], Report) -> case lists:keyfind(Key, 1, Report) of false -> io:format("~p", [Report]), ct:fail({missing_key,Key}); {Key,Info} -> try match_info(Pattern, Info) catch no_match -> io:format("key: ~p", [Key]), io:format("pattern: ~p", [Pattern]), io:format("actual: ~p", [Report]), ct:fail(no_match) end, analyse_crash_1(T, Report) end; analyse_crash_1([], _Report) -> []. match_info(T, T) -> ok; match_info({stacktrace}, Stk) when is_list(Stk) -> ok; match_info([H1|T1], [H2|T2]) -> match_info(H1, H2), match_info(T1, T2); match_info(Tuple1, Tuple2) when tuple_size(Tuple1) =:= tuple_size(Tuple2) -> match_info(tuple_to_list(Tuple1), tuple_to_list(Tuple2)); match_info(_, _) -> throw(no_match). stacktrace(Config) when is_list(Config) -> process_flag(trap_exit, true), %% Errors. Pid1 = proc_lib:spawn_link(fun() -> 1 = 2 end), receive {'EXIT',Pid1,{{badmatch,2},_Stack1}} -> ok after 500 -> ct:fail(error) end, %% Exits. Pid2 = proc_lib:spawn_link(fun() -> exit(bye) end), receive {'EXIT',Pid2,bye} -> ok after 500 -> ct:fail(exit) end, %% Throws. Pid3 = proc_lib:spawn_link(fun() -> throw(ball) end), receive {'EXIT',Pid3,{{nocatch,ball},_Stack3}} -> ok after 500 -> ct:fail(throw) end, ok. sync_start_nolink(Config) when is_list(Config) -> _Pid = spawn_link(?MODULE, sp5, [self()]), receive {sync_started, F} -> exit(F, kill), ct:fail(async_start) after 1000 -> ok end, receive {Pid2, init} -> Pid2 ! go_on end, receive {sync_started, _} -> ok after 1000 -> exit(Pid2, kill), ct:fail(no_sync_start) end, ok. sync_start_link(Config) when is_list(Config) -> _Pid = spawn_link(?MODULE, sp3, [self()]), receive {sync_started, _} -> ct:fail(async_start) after 1000 -> ok end, receive {Pid2, init} -> Pid2 ! go_on end, receive {sync_started, _} -> ok after 1000 -> ct:fail(no_sync_start) end, ok. spawn_opt(Config) when is_list(Config) -> F = fun sp1/0, {name,Fname} = erlang:fun_info(F, name), FunMFArgs = {?MODULE,Fname,[]}, FunMFArity = {?MODULE,Fname,0}, Pid1 = proc_lib:spawn_opt(node(), F, [{priority,low}]), Pid = proc_lib:spawn_opt(F, [{priority,low}]), ct:sleep(100), FunMFArgs = proc_lib:initial_call(Pid), FunMFArity = proc_lib:translate_initial_call(Pid), Pid ! die, FunMFArgs = proc_lib:initial_call(Pid1), FunMFArity = proc_lib:translate_initial_call(Pid1), Pid1 ! die, ok. sp1() -> receive die -> exit(die); _ -> sp1() end. sp2() -> _Pid = proc_lib:spawn_link(?MODULE, sp1, []), receive die -> exit(die); _ -> sp1() end. sp3(Tester) -> Pid = proc_lib:start_link(?MODULE, sp4, [self(), Tester]), Tester ! {sync_started, Pid}. sp5(Tester) -> Pid = proc_lib:start(?MODULE, sp4, [self(), Tester]), Tester ! {sync_started, Pid}. sp4(Parent, Tester) -> Tester ! {self(), init}, receive go_on -> ok end, proc_lib:init_ack(Parent, self()). '\x{447}'() -> receive die -> exit(die); _ -> sp1() end. hibernate(Config) when is_list(Config) -> Ref = make_ref(), Self = self(), LoopData = {Ref,Self}, Pid = proc_lib:spawn_link(?MODULE, hib_loop, [LoopData]), %% Just check that the child process can process and answer messages. Pid ! {Self,loop_data}, receive {loop_data,LoopData} -> ok; Unexpected0 -> io:format("Unexpected: ~p\n", [Unexpected0]), ct:fail(failed) after 1000 -> io:format("Timeout"), ct:fail(failed) end, %% Hibernate the process. Pid ! hibernate, erlang:yield(), io:format("~p\n", [process_info(Pid, heap_size)]), %% Send a message to the process... Pid ! {Self,loop_data}, %% ... expect first a wake up message from the process... receive {awaken,LoopData} -> ok; Unexpected1 -> io:format("Unexpected: ~p\n", [Unexpected1]), ct:fail(failed) after 1000 -> io:format("Timeout"), ct:fail(failed) end, %% ... followed by the answer to the actual request. receive {loop_data,LoopData} -> ok; Unexpected2 -> io:format("Unexpected: ~p\n", [Unexpected2]), ct:fail(failed) after 1000 -> io:format("Timeout"), ct:fail(failed) end, %% Test that errors are handled correctly after wake up from hibernation... process_flag(trap_exit, true), error_logger:add_report_handler(?MODULE, self()), Pid ! crash, %% We should receive two messages. Especially in the SMP emulator, %% we can't be sure of the message order, so sort the messages before %% matching. Messages = lists:sort(hib_receive_messages(2)), io:format("~p", [Messages]), [{'EXIT',Pid,i_crashed},{crash_report,Pid,[Report,[]]}] = Messages, %% Check that the initial_call has the expected format. {value,{initial_call,{?MODULE,hib_loop,[_]}}} = lists:keysearch(initial_call, 1, Report), error_logger:delete_report_handler(?MODULE), ok. hib_loop(LoopData) -> receive hibernate -> proc_lib:hibernate(?MODULE, awaken, [LoopData]); {Pid,loop_data} -> Pid ! {loop_data,LoopData}; crash -> exit(i_crashed) end, hib_loop(LoopData). awaken({_,Parent}=LoopData) -> Parent ! {awaken,LoopData}, hib_loop(LoopData). hib_receive_messages(0) -> []; hib_receive_messages(N) -> receive Any -> [Any|hib_receive_messages(N-1)] end. %% 'monitor' spawn_opt option. otp_6345(Config) when is_list(Config) -> Opts = [link,monitor], {'EXIT', {badarg,[{proc_lib,check_for_monitor,_,_}|_Stack]}} = (catch proc_lib:start(?MODULE, otp_6345_init, [self()], 1000, Opts)), ok. otp_6345_init(Parent) -> proc_lib:init_ack(Parent, {ok, self()}), otp_6345_loop(). otp_6345_loop() -> receive _Msg -> otp_6345_loop() end. %% OTP-9803. Check that proc_lib:start() doesn't hang if spawned process %% crashes before proc_lib:init_ack/2. init_dont_hang(Config) when is_list(Config) -> %% Start should behave as start_link process_flag(trap_exit, true), StartLinkRes = proc_lib:start_link(?MODULE, init_dont_hang_init, [self()]), try StartLinkRes = proc_lib:start(?MODULE, init_dont_hang_init, [self()], 1000), StartLinkRes = proc_lib:start(?MODULE, init_dont_hang_init, [self()], 1000, []), ok catch _:Error:Stacktrace -> io:format("Error ~p /= ~p ~n",[Stacktrace, StartLinkRes]), exit(Error) end. init_dont_hang_init(_Parent) -> error(bad_init). %% Test proc_lib:stop/1,3 stop(_Config) -> Parent = self(), SysMsgProc = fun() -> receive {system,From,Request} -> sys:handle_system_msg(Request,From,Parent,?MODULE,[],[]) end end, %% Normal case: %% Process handles system message and terminated with given reason Pid1 = proc_lib:spawn(SysMsgProc), ok = proc_lib:stop(Pid1), false = erlang:is_process_alive(Pid1), %% Process does not exit {'EXIT',noproc} = (catch proc_lib:stop(Pid1)), %% Badly handled system message DieProc = fun() -> receive {system,_From,_Request} -> exit(die) end end, Pid2 = proc_lib:spawn(DieProc), {'EXIT',{die,_}} = (catch proc_lib:stop(Pid2)), %% Hanging process => timeout HangProc = fun() -> receive {system,_From,_Request} -> timer:sleep(5000) end end, Pid3 = proc_lib:spawn(HangProc), {'EXIT',timeout} = (catch proc_lib:stop(Pid3,normal,1000)), %% Success case with other reason than 'normal' Pid4 = proc_lib:spawn(SysMsgProc), ok = proc_lib:stop(Pid4,other_reason,infinity), false = erlang:is_process_alive(Pid4), %% System message is handled, but process dies with other reason %% than the given (in system_terminate/4 below) Pid5 = proc_lib:spawn(SysMsgProc), {'EXIT',{{badmatch,2},_Stacktrace}} = (catch proc_lib:stop(Pid5,crash,infinity)), false = erlang:is_process_alive(Pid5), %% Local registered name Pid6 = proc_lib:spawn(SysMsgProc), register(to_stop,Pid6), ok = proc_lib:stop(to_stop), undefined = whereis(to_stop), false = erlang:is_process_alive(Pid6), %% Remote registered name {ok,Node} = test_server:start_node(proc_lib_SUITE_stop,slave,[]), Dir = filename:dirname(code:which(?MODULE)), rpc:call(Node,code,add_path,[Dir]), Pid7 = spawn(Node,SysMsgProc), true = rpc:call(Node,erlang,register,[to_stop,Pid7]), Pid7 = rpc:call(Node,erlang,whereis,[to_stop]), ok = proc_lib:stop({to_stop,Node}), undefined = rpc:call(Node,erlang,whereis,[to_stop]), false = rpc:call(Node,erlang,is_process_alive,[Pid7]), %% Local and remote registered name, but non-existing {'EXIT',noproc} = (catch proc_lib:stop(to_stop)), {'EXIT',noproc} = (catch proc_lib:stop({to_stop,Node})), true = test_server:stop_node(Node), %% Remote registered name, but non-existing node {'EXIT',{{nodedown,Node},_}} = (catch proc_lib:stop({to_stop,Node})), ok. system_terminate(crash,_Parent,_Deb,_State) -> error({badmatch,2}); system_terminate(Reason,_Parent,_Deb,_State) -> exit(Reason). t_format(_Config) -> {ok,#{level:=Level}} = logger:get_handler_config(default), logger:set_handler_config(default,level,none), error_logger:add_report_handler(?MODULE, self()), try t_format() after error_logger:delete_report_handler(?MODULE), logger:set_handler_config(default,level,Level) end, ok. t_format() -> Pid = proc_lib:spawn(fun '\x{aaa}t_format_looper'/0), HugeData = gb_sets:from_list(lists:seq(1, 100)), SomeData1 = list_to_atom([246]), SomeData2 = list_to_atom([1024]), Pid ! {SomeData1,SomeData2}, Pid ! {die,{HugeData,SomeData1,SomeData2}}, Report = receive {crash_report, Pid, Report0} -> Report0 end, Usz = do_test_format(Report, latin1, unlimited), Tsz = do_test_format(Report, latin1, 20), if Tsz >= Usz -> ct:fail(failed); true -> ok end, UszU = do_test_format(Report, unicode, unlimited), TszU = do_test_format(Report, unicode, 20), if TszU >= UszU -> ct:fail(failed); true -> ok end, ok. t_format_arbitrary(_Config) -> {ok,#{level:=Level}} = logger:get_handler_config(default), logger:set_handler_config(default,level,none), try t_format_arbitrary() after logger:set_handler_config(default,level,Level) end, ok. t_format_arbitrary() -> A = list_to_atom([1024]), do_test_format([fake_report, A], unlimited), do_test_format([fake_report, A], 20), do_test_format([fake_report, foo], unlimited), do_test_format([fake_report, foo], 20), do_test_format([fake_report, []], unlimited), do_test_format([fake_report, []], 20). do_test_format(Report, Depth) -> do_test_format(Report, latin1, Depth), do_test_format(Report, unicode, Depth). do_test_format(Report, Encoding, Depth) -> io:format("*** Depth = ~p, Encoding = ~p", [Depth, Encoding]), S0 = proc_lib:format(Report, Encoding, Depth), S = lists:flatten(S0), case Encoding of latin1 -> io:format("~s\n", [S]); _ -> io:format("~ts\n", [S]) end, length(S). '\x{aaa}t_format_looper'() -> receive {die,Data} -> exit(Data); M -> put(M, M), '\x{aaa}t_format_looper'() end. %%----------------------------------------------------------------- %% The error_logger handler used. %%----------------------------------------------------------------- init(Tester) -> {ok, Tester}. handle_event({error_report, _GL, {Pid, crash_report, Report}}, Tester) -> io:format("~ts\n", [proc_lib:format(Report)]), Tester ! {crash_report, Pid, Report}, {ok, Tester}; handle_event(_Event, State) -> {ok, State}. handle_info(_, State) -> {ok, State}. handle_call(_Query, State) -> {ok, {error, bad_query}, State}. terminate(_Reason, State) -> State.