diff options
author | Raimo Niskanen <raimo@erlang.org> | 2023-02-23 12:58:25 +0100 |
---|---|---|
committer | Raimo Niskanen <raimo@erlang.org> | 2023-02-23 12:59:43 +0100 |
commit | b0afc29474f9625aa0b466b6672e0bbcefd07535 (patch) | |
tree | 5ef8a33e407c6d048e1658a8213534c56febe736 /lib/stdlib | |
parent | b31b8ad1225caed2944f6f1eb4a9d98fc7af9709 (diff) | |
parent | 381648e4c054405083967a2ada779fd5f8adf37d (diff) | |
download | erlang-b0afc29474f9625aa0b466b6672e0bbcefd07535.tar.gz |
Merge branch 'raimo/gen-sync-start-fail/GH-6339' OTP-18471
* raimo/gen-sync-start-fail/GH-6339:
Update documentation after review
Fix bug - start_link should not return an unwrapped error Reason
Change to a more coherent behaviour for init_fail and exit in init
Clarify documentation
Update doc with proc_lib:init_fail/2,3
Simplify error propagation
Implement synchronous init failure for gen processes
Test looping over gen_server init failure
Diffstat (limited to 'lib/stdlib')
-rw-r--r-- | lib/stdlib/doc/src/proc_lib.xml | 235 | ||||
-rw-r--r-- | lib/stdlib/src/gen.erl | 3 | ||||
-rw-r--r-- | lib/stdlib/src/gen_fsm.erl | 26 | ||||
-rw-r--r-- | lib/stdlib/src/gen_server.erl | 45 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 20 | ||||
-rw-r--r-- | lib/stdlib/src/proc_lib.erl | 91 | ||||
-rw-r--r-- | lib/stdlib/test/gen_server_SUITE.erl | 77 | ||||
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 60 | ||||
-rw-r--r-- | lib/stdlib/test/supervisor_SUITE.erl | 35 |
9 files changed, 442 insertions, 150 deletions
diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml index aa649a280a..1d52463481 100644 --- a/lib/stdlib/doc/src/proc_lib.xml +++ b/lib/stdlib/doc/src/proc_lib.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1996</year><year>2020</year> + <year>1996</year><year>2023</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -178,16 +178,34 @@ <name name="init_ack" arity="2" since=""/> <fsummary>Used by a process when it has started.</fsummary> <desc> - <p>This function must be used by a process that has been started by - a <seemfa marker="#start/3"><c>start[_link]/3,4,5</c></seemfa> + <p> + This function must only be used by a process + that has been started by a + <seemfa marker="#start/3"><c>start[_link|_monitor]/3,4,5</c></seemfa> function. It tells <c><anno>Parent</anno></c> that the process has - initialized itself, has started, or has failed to initialize - itself.</p> + initialized itself and started. + </p> <p>Function <c>init_ack/1</c> uses the parent value previously stored by the start function used.</p> - <p>If this function is not called, the start function - returns an error tuple (if a link and/or a time-out is used) or - hang otherwise.</p> + <p> + If neither this function nor + <seemfa marker="#init_fail/2"><c>init_fail/2,3</c></seemfa> + is called by the started process, the start function + returns an error tuple when the started process exits, + or when the start function time-out (if used) has passed, + see <seemfa marker="#start/3"><c>start/3,4,5</c></seemfa>. + </p> + <warning> + <p> + Do not use this function to return an error indicating + that the process start failed. When doing so + the start function can return before the failing + process has exited, which may block VM resources + required for a new start attempt to succeed. Use + <seemfa marker="#init_fail/2"><c>init_fail/2,3</c></seemfa> + for that purpose. + </p> + </warning> <p>The following example illustrates how this function and <c>proc_lib:start_link/3</c> are used:</p> <code type="none"> @@ -212,6 +230,76 @@ init(Parent) -> </func> <func> + <name since="OTP-26.0">init_fail(Ret, Exception) -> no_return()</name> + <name since="OTP-26.0">init_fail(Parent, Ret, Exception) -> no_return()</name> + <fsummary>Used by a process that fails to start.</fsummary> + <type> + <v>Parent = <seetype marker="erts:erlang#pid">pid()</seetype></v> + <v>Ret = <seetype marker="erts:erlang#term">term()</seetype></v> + <v>Exception = {Class, Reason} | {Class, Reason, Stacktrace}</v> + </type> + <desc> + <p> + This function must only be used by a process + that has been started by a + <seemfa marker="#start/3"><c>start[_link|_monitor]/3,4,5</c></seemfa> + function. It tells <c>Parent</c> that the process has failed to + initialize, and immediately raises an exception + according to <c>Exception</c>. + The start function then returns <c>Ret</c>. + </p> + <p> + See + <seemfa marker="erts:erlang#raise/3"><c>erlang:raise/3</c></seemfa> + for a description of <c>Class</c>, <c>Reason</c> + and <c>Stacktrace</c>. + </p> + <p> + Function <c>init_fail/2</c> uses the parent value + previously stored by the start function used. + </p> + <warning> + <p> + Do not consider catching the exception from this function. + That would defeat its purpose. A process started by a + <seemfa marker="#start/3"><c>start[_link|_monitor]/3,4,5</c></seemfa> + function should end in a value (that will be ignored) + or an exception that will be handled by this module. + See <seeerl marker="#description">Description</seeerl>. + </p> + </warning> + <p> + If neither this function nor + <seemfa marker="#init_ack/1"><c>init_ack/1,2</c></seemfa> + is called by the started process, the start function + returns an error tuple when the started process exits, + or when the start function time-out (if used) has passed, + see <seemfa marker="#start/3"><c>start/3,4,5</c></seemfa>. + </p> + <p>The following example illustrates how this function and + <c>proc_lib:start_link/3</c> can be used:</p> + <code type="none"> +-module(my_proc). +-export([start_link/0]). +-export([init/1]). + +start_link() -> + proc_lib:start_link(my_proc, init, [self()]). + +init(Parent) -> + case do_initialization() of + ok -> + proc_lib:init_ack(Parent, {ok, self()}); + {error, Reason} = Error -> + proc_lib:init_fail(Parent, Error, {exit, normal}) + end, + loop(). + +...</code> + </desc> + </func> + + <func> <name name="initial_call" arity="1" since=""/> <fsummary>Extract the initial call of a <c>proc_lib</c>spawned process. </fsummary> @@ -304,17 +392,42 @@ init(Parent) -> <name name="start" arity="5" since=""/> <fsummary>Start a new process synchronously.</fsummary> <desc> - <p>Starts a new process synchronously. Spawns the process and - waits for it to start. When the process has started, it - <em>must</em> call + <p> + Starts a new process synchronously. Spawns the process and + waits for it to start. + </p> + <p> + To indicate a succesful start, + the started process <em>must</em> call <seemfa marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seemfa> - or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>, - where <c>Parent</c> is the process that evaluates this - function. At this time, <c>Ret</c> is returned.</p> + where <c>Parent</c> is the process that evaluates this function, + or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>. + <c>Ret</c> is then returned by this function. + </p> + <p> + If the process fails to start, it <em>must</em> fail; + preferably by calling + <seemfa marker="#init_fail/3"> + <c>init_fail(Parent, Ret, Exception)</c> + </seemfa> + where <c>Parent</c> is the process that evaluates this function, + or <seemfa marker="#init_fail/2"><c>init_fail(Ret, Exception)</c></seemfa>. + <c>Ret</c> is then returned by this function, + and the started process fails with <c>Exception</c>. + </p> + <p> + If the process instead fails before calling + <c>init_ack/1,2</c> or <c>init_fail/2,3</c>, + this function returns <c>{error, Reason}</c> + where <c>Reason</c> depends a bit on the exception + just like for a process link <c>{'EXIT',Pid,Reason}</c> + message. + </p> <p>If <c><anno>Time</anno></c> is specified as an integer, this function waits for <c><anno>Time</anno></c> milliseconds for the - new process to call <c>init_ack</c>, or <c>Ret = {error, timeout}</c> - will be returned, and the process is killed.</p> + new process to call <c>init_ack/1,2</c> or <c>init_fail/2,3</c>, + otherwise the process gets killed + and <c>Ret = {error, timeout}</c> is returned.</p> <p>Argument <c><anno>SpawnOpts</anno></c>, if specified, is passed as the last argument to the <seemfa marker="erts:erlang#spawn_opt/2"> <c>spawn_opt/2,3,4,5</c></seemfa> BIF.</p> @@ -322,6 +435,11 @@ init(Parent) -> <p>Using spawn option <c>monitor</c> is not allowed. It causes the function to fail with reason <c>badarg</c>.</p> + <p> + Using spawn option <c>link</c> will set a link to + the spawned process, just like + <seemfa marker="#start_link/3">start_link/3,4,5</seemfa>. + </p> </note> </desc> </func> @@ -335,22 +453,31 @@ init(Parent) -> <p> Starts a new process synchronously. Spawns the process and waits for it to start. A link is atomically set on the - newly spawned process. When the process has started, it - <em>must</em> call - <seemfa marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seemfa> - or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>, - where <c>Parent</c> is the process that evaluates this - function. At this time, <c>Ret</c> is returned.</p> - <p>If <c><anno>Time</anno></c> is specified as an integer, this - function waits for <c><anno>Time</anno></c> milliseconds for the - new process to call <c>init_ack</c>, or <c>Ret = {error, timeout}</c> - will be returned, and the process is killed.</p> - <p>If the process crashes before it has called <c>init_ack/1,2</c>, - <c>Ret = {error, <anno>Reason</anno>}</c> will be returned if - the calling process traps exits.</p> - <p>Argument <c><anno>SpawnOpts</anno></c>, if specified, is passed - as the last argument to the <seemfa marker="erts:erlang#spawn_opt/2"> - <c>spawn_opt/2,3,4,5</c></seemfa> BIF.</p> + newly spawned process. + </p> + <note> + <p> + If the started process gets killed or crashes with a reason + that is not `normal`, the process link will kill the calling + process so this function does not return, + unless the calling process traps exits. + For example, if this function times out it will kill + the spawned process, and then the link might kill + the calling process. + </p> + </note> + <p> + Besides setting a link on the spawned process + this function behaves like + <seemfa marker="#start/3">start/3,4,5</seemfa>. + </p> + <p> + When the calling process traps exits; + if this function returns due to the spawned process exiting + (any error return), this function receives (consumes) + the <c>'EXIT'</c> message, also when this function times out + and kills the spawned process. + </p> <note> <p>Using spawn option <c>monitor</c> is not allowed. It causes the function to fail with reason @@ -368,34 +495,36 @@ init(Parent) -> <p> Starts a new process synchronously. Spawns the process and waits for it to start. A monitor is atomically set on the - newly spawned process. When the process has started, it - <em>must</em> call - <seemfa marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seemfa> - or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>, - where <c>Parent</c> is the process that evaluates this - function. At this time, <c>Ret</c> is returned.</p> - <p>If <c><anno>Time</anno></c> is specified as an integer, this - function waits for <c><anno>Time</anno></c> milliseconds for the - new process to call <c>init_ack</c>, or <c>Ret = {error, timeout}</c> - will be returned, and the process is killed.</p> + newly spawned process. + </p> + <p> + Besides setting a monitor on the spawned process + this function behaves like + <seemfa marker="#start/3">start/3,4,5</seemfa>. + </p> <p> The return value is <c>{Ret, Mon}</c> where <c>Ret</c> corresponds - to the <c>Ret</c> argument in the call to <c>init_ack()</c>, and - <c>Mon</c> is the monitor reference of the monitor that has been - set up. + to the <c>Ret</c> argument in the call to <c>init_ack/1,2</c> + or <c>init_fail/2,3</c>, and <c>Mon</c> is the monitor reference + of the monitor that has been set up. </p> <p> - A <c>'DOWN'</c> message will be delivered to the caller if - this function returns, and the spawned process terminates. This is - true also in the case when the operation times out. + If this function returns due to the spawned process exiting, + that is returns any error value, + a <c>'DOWN'</c> message will be delivered to the calling process, + also when this function times out and kills the spawned process. </p> - <p>Argument <c><anno>SpawnOpts</anno></c>, if specified, is passed - as the last argument to the <seemfa marker="erts:erlang#spawn_opt/2"> - <c>spawn_opt/2,3,4,5</c></seemfa> BIF.</p> <note> - <p>Using spawn option <c>monitor</c> is not + <p> + Using spawn option <c>monitor</c> is not allowed. It causes the function to fail with reason - <c>badarg</c>.</p> + <c>badarg</c>. + </p> + <p> + Using spawn option <c>link</c> will set a link to + the spawned process, just like + <seemfa marker="#start_link/3">start_link/3,4,5</seemfa>. + </p> </note> </desc> </func> diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl index b308c8ea73..d03ca78e7c 100644 --- a/lib/stdlib/src/gen.erl +++ b/lib/stdlib/src/gen.erl @@ -195,7 +195,8 @@ init_it(GenMod, Starter, Parent, Name, Mod, Args, Options) -> true -> init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options); {false, Pid} -> - proc_lib:init_ack(Starter, {error, {already_started, Pid}}) + proc_lib:init_fail( + Starter, {error, {already_started, Pid}}, {exit, normal}) end. init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options) -> diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl index 68ff5e2d4d..6dba530b85 100644 --- a/lib/stdlib/src/gen_fsm.erl +++ b/lib/stdlib/src/gen_fsm.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2021. All Rights Reserved. +%% Copyright Ericsson AB 1996-2023. 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. @@ -329,30 +329,26 @@ init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, Parent, Name0, Mod, Args, Options) -> Name = gen:name(Name0), Debug = gen:debug_options(Name, Options), - HibernateAfterTimeout = gen:hibernate_after(Options), - case catch Mod:init(Args) of + HibernateAfterTimeout = gen:hibernate_after(Options), + case catch Mod:init(Args) of {ok, StateName, StateData} -> - proc_lib:init_ack(Starter, {ok, self()}), + proc_lib:init_ack(Starter, {ok, self()}), loop(Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, Debug); {ok, StateName, StateData, Timeout} -> - proc_lib:init_ack(Starter, {ok, self()}), + proc_lib:init_ack(Starter, {ok, self()}), loop(Parent, Name, StateName, StateData, Mod, Timeout, HibernateAfterTimeout, Debug); {stop, Reason} -> - gen:unregister_name(Name0), - proc_lib:init_ack(Starter, {error, Reason}), - exit(Reason); + gen:unregister_name(Name0), + exit(Reason); ignore -> gen:unregister_name(Name0), - proc_lib:init_ack(Starter, ignore), - exit(normal); + proc_lib:init_fail(Starter, ignore, {exit, normal}); {'EXIT', Reason} -> gen:unregister_name(Name0), - proc_lib:init_ack(Starter, {error, Reason}), - exit(Reason); + exit(Reason); Else -> - Error = {bad_return_value, Else}, - proc_lib:init_ack(Starter, {error, Error}), - exit(Error) + Reason = {bad_return_value, Else}, + exit(Reason) end. %%----------------------------------------------------------------- diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index 46b2de9c23..c574f2aeb2 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -916,16 +916,22 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> CbCache = create_callback_cache(Mod), case init_it(Mod, Args) of {ok, {ok, State}} -> - proc_lib:init_ack(Starter, {ok, self()}), - loop(Parent, Name, State, CbCache, infinity, HibernateAfterTimeout, Debug); - {ok, {ok, State, TimeoutOrHibernate}} + proc_lib:init_ack(Starter, {ok, self()}), + loop( + Parent, Name, State, CbCache, infinity, + HibernateAfterTimeout, Debug); + {ok, {ok, State, TimeoutOrHibernate}} when ?is_timeout(TimeoutOrHibernate); TimeoutOrHibernate =:= hibernate -> - proc_lib:init_ack(Starter, {ok, self()}), - loop(Parent, Name, State, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, Debug); + proc_lib:init_ack(Starter, {ok, self()}), + loop( + Parent, Name, State, CbCache, TimeoutOrHibernate, + HibernateAfterTimeout, Debug); {ok, {ok, State, {continue, _}=Continue}} -> - proc_lib:init_ack(Starter, {ok, self()}), - loop(Parent, Name, State, CbCache, Continue, HibernateAfterTimeout, Debug); + proc_lib:init_ack(Starter, {ok, self()}), + loop( + Parent, Name, State, CbCache, Continue, + HibernateAfterTimeout, Debug); {ok, {stop, Reason}} -> %% For consistency, we must make sure that the %% registered name (if any) is unregistered before @@ -934,28 +940,22 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> %% an 'already_started' error if it immediately %% tried starting the process again.) gen:unregister_name(Name0), - proc_lib:init_ack(Starter, {error, Reason}), - exit(Reason); + exit(Reason); {ok, {error, _Reason} = ERROR} -> %% The point of this clause is that we shall have a silent/graceful %% termination. The error reason will be returned to the %% 'Starter' ({error, Reason}), but *no* crash report. gen:unregister_name(Name0), - proc_lib:init_ack(Starter, ERROR), - exit(normal); + proc_lib:init_fail(Starter, ERROR, {exit, normal}); {ok, ignore} -> gen:unregister_name(Name0), - proc_lib:init_ack(Starter, ignore), - exit(normal); + proc_lib:init_fail(Starter, ignore, {exit, normal}); {ok, Else} -> - Error = {bad_return_value, Else}, - proc_lib:init_ack(Starter, {error, Error}), - exit(Error); + gen:unregister_name(Name0), + exit({bad_return_value, Else}); {'EXIT', Class, Reason, Stacktrace} -> gen:unregister_name(Name0), - ActualReason = terminate_reason(Class, Reason, Stacktrace), - proc_lib:init_ack(Starter, {error, ActualReason}), - erlang:raise(Class, Reason, Stacktrace) + erlang:raise(Class, Reason, Stacktrace) end. init_it(Mod, Args) -> try @@ -1318,7 +1318,7 @@ terminate(Class, Reason, Stacktrace, Name, From, Msg, Mod, State, Debug) -> -spec terminate(_, _, _, _, _, _, _, _, _, _) -> no_return(). terminate(Class, Reason, Stacktrace, ReportStacktrace, Name, From, Msg, Mod, State, Debug) -> - Reply = try_terminate(Mod, terminate_reason(Class, Reason, Stacktrace), State), + Reply = try_terminate(Mod, catch_result(Class, Reason, Stacktrace), State), case Reply of {'EXIT', C, R, S} -> error_info(R, S, Name, From, Msg, Mod, State, Debug), @@ -1341,8 +1341,9 @@ terminate(Class, Reason, Stacktrace, ReportStacktrace, Name, From, Msg, Mod, Sta erlang:raise(Class, Reason, Stacktrace) end. -terminate_reason(error, Reason, Stacktrace) -> {Reason, Stacktrace}; -terminate_reason(exit, Reason, _Stacktrace) -> Reason. +%% What an old style `catch` would return +catch_result(error, Reason, Stacktrace) -> {Reason, Stacktrace}; +catch_result(exit, Reason, _Stacktrace) -> Reason. 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 diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 39cba737ef..ff808ef458 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -1031,12 +1031,12 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> Name, Debug, HibernateAfterTimeout); Class:Reason:Stacktrace -> gen:unregister_name(ServerRef), - proc_lib:init_ack(Starter, {error,Reason}), error_info( Class, Reason, Stacktrace, Debug, #params{parent = Parent, name = Name, modules = [Module]}, #state{}, []), - erlang:raise(Class, Reason, Stacktrace) + proc_lib:init_fail( + Starter, {error,Reason}, {Class,Reason,Stacktrace}) end. %%--------------------------------------------------------------------------- @@ -1058,28 +1058,24 @@ init_result( State, Data, Actions); {stop,Reason} -> gen:unregister_name(ServerRef), - proc_lib:init_ack(Starter, {error,Reason}), - exit(Reason); + exit(Reason); {error, _Reason} = ERROR -> %% The point of this clause is that we shall have a *silent* %% termination. The error reason will be returned to the %% 'Starter' ({error, Reason}), but *no* crash report. gen:unregister_name(ServerRef), - proc_lib:init_ack(Starter, ERROR), - exit(normal); + proc_lib:init_fail(Starter, ERROR, {exit,normal}); ignore -> gen:unregister_name(ServerRef), - proc_lib:init_ack(Starter, ignore), - exit(normal); + proc_lib:init_fail(Starter, ignore, {exit,normal}); _ -> gen:unregister_name(ServerRef), - Error = {bad_return_from_init,Result}, - proc_lib:init_ack(Starter, {error,Error}), + Reason = {bad_return_from_init,Result}, error_info( - error, Error, ?STACKTRACE(), Debug, + error, Reason, ?STACKTRACE(), Debug, #params{parent = Parent, name = Name, modules = [Module]}, #state{}, []), - exit(Error) + exit(Reason) end. %%%========================================================================== diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl index a95d75b9f6..9c7235b954 100644 --- a/lib/stdlib/src/proc_lib.erl +++ b/lib/stdlib/src/proc_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2022. All Rights Reserved. +%% Copyright Ericsson AB 1996-2023. 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. @@ -31,6 +31,7 @@ start_monitor/3, start_monitor/4, start_monitor/5, hibernate/3, init_ack/1, init_ack/2, + init_fail/2, init_fail/3, init_p/3,init_p/5,format/1,format/2,format/3,report_cb/2, initial_call/1, translate_initial_call/1, @@ -273,6 +274,7 @@ exit_reason(exit, Reason, _Stacktrace) -> exit_reason(throw, Reason, Stacktrace) -> {{nocatch, Reason}, Stacktrace}. + -spec start(Module, Function, Args) -> Ret when Module :: module(), Function :: atom(), @@ -309,14 +311,20 @@ sync_start({Pid, Ref}, Timeout) -> {ack, Pid, Return} -> erlang:demonitor(Ref, [flush]), Return; + {nack, Pid, Return} -> + flush_EXIT(Pid), + _ = await_DOWN(Pid, Ref), + Return; {'DOWN', Ref, process, Pid, Reason} -> + flush_EXIT(Pid), {error, Reason} after Timeout -> - erlang:demonitor(Ref, [flush]), - kill_flush(Pid), + kill_flush_EXIT(Pid), + _ = await_DOWN(Pid, Ref), {error, timeout} end. + -spec start_link(Module, Function, Args) -> Ret when Module :: module(), Function :: atom(), @@ -334,7 +342,7 @@ start_link(M, F, A) when is_atom(M), is_atom(F), is_list(A) -> Ret :: term() | {error, Reason :: term()}. start_link(M, F, A, Timeout) when is_atom(M), is_atom(F), is_list(A) -> - sync_start_link(?MODULE:spawn_link(M, F, A), Timeout). + sync_start(?MODULE:spawn_opt(M, F, A, [link,monitor]), Timeout). -spec start_link(Module, Function, Args, Time, SpawnOpts) -> Ret when Module :: module(), @@ -346,18 +354,9 @@ start_link(M, F, A, Timeout) when is_atom(M), is_atom(F), is_list(A) -> start_link(M,F,A,Timeout,SpawnOpts) when is_atom(M), is_atom(F), is_list(A) -> ?VERIFY_NO_MONITOR_OPT(M, F, A, Timeout, SpawnOpts), - sync_start_link(?MODULE:spawn_opt(M, F, A, [link|SpawnOpts]), Timeout). + sync_start( + ?MODULE:spawn_opt(M, F, A, [link,monitor|SpawnOpts]), Timeout). -sync_start_link(Pid, Timeout) -> - receive - {ack, Pid, Return} -> - Return; - {'EXIT', Pid, Reason} -> - {error, Reason} - after Timeout -> - kill_flush(Pid), - {error, timeout} - end. -spec start_monitor(Module, Function, Args) -> {Ret, Mon} when Module :: module(), @@ -393,29 +392,54 @@ start_monitor(M,F,A,Timeout,SpawnOpts) when is_atom(M), is_atom(F), is_list(A) -> ?VERIFY_NO_MONITOR_OPT(M, F, A, Timeout, SpawnOpts), - sync_start_monitor(?MODULE:spawn_opt(M, F, A, [monitor|SpawnOpts]), - Timeout). + sync_start_monitor( + ?MODULE:spawn_opt(M, F, A, [monitor|SpawnOpts]), Timeout). sync_start_monitor({Pid, Ref}, Timeout) -> receive {ack, Pid, Return} -> {Return, Ref}; + {nack, Pid, Return} -> + flush_EXIT(Pid), + self() ! await_DOWN(Pid, Ref), + {Return, Ref}; {'DOWN', Ref, process, Pid, Reason} = Down -> + flush_EXIT(Pid), self() ! Down, {{error, Reason}, Ref} after Timeout -> - kill_flush(Pid), + kill_flush_EXIT(Pid), + self() ! await_DOWN(Pid, Ref), {{error, timeout}, Ref} end. --spec kill_flush(Pid) -> 'ok' when - Pid :: pid(). -kill_flush(Pid) -> +%% We regard the existence of an {'EXIT', Pid, _} message +%% as proof enough that there was a link that fired and +%% we had process_flag(trap_exit, true), +%% so the message should be flushed. +%% It is the best we can do. +%% +%% After an unlink(Pid) an {'EXIT', Pid, _} link message +%% cannot arrive so receive after 0 will work, + +flush_EXIT(Pid) -> + unlink(Pid), + receive {'EXIT', Pid, _} -> ok after 0 -> ok end. + +kill_flush_EXIT(Pid) -> + %% unlink/1 has to be called before exit/2 + %% or we might be killed by the link unlink(Pid), exit(Pid, kill), - receive {'EXIT', Pid, _} -> ok after 0 -> ok end, - ok. + receive {'EXIT', Pid, _} -> ok after 0 -> ok end. + +await_DOWN(Pid, Ref) -> + receive + {'DOWN', Ref, process, Pid, _} = Down -> + Down + end. + -spec init_ack(Parent, Ret) -> 'ok' when Parent :: pid(), @@ -432,6 +456,27 @@ init_ack(Return) -> [Parent|_] = get('$ancestors'), init_ack(Parent, Return). +-spec init_fail(_, _, _) -> no_return(). +init_fail(Parent, Return, Exception) -> + _ = Parent ! {nack, self(), Return}, + case Exception of + {error, Reason} -> + erlang:error(Reason); + {exit, Reason} -> + erlang:exit(Reason); + {throw, Reason} -> + erlang:throw(Reason); + {Class, Reason, Stacktrace} -> + erlang:error( + erlang:raise(Class, Reason, Stacktrace), + [Parent, Return, Exception]) + end. + +-spec init_fail(_, _) -> no_return(). +init_fail(Return, Exception) -> + [Parent|_] = get('$ancestors'), + init_fail(Parent, Return, Exception). + %% ----------------------------------------------------- %% Fetch the initial call of a proc_lib spawned process. %% ----------------------------------------------------- diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl index dc0ee98b01..52f6a2ceb9 100644 --- a/lib/stdlib/test/gen_server_SUITE.erl +++ b/lib/stdlib/test/gen_server_SUITE.erl @@ -26,7 +26,7 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2]). --export([start/1, crash/1, call/1, send_request/1, +-export([start/1, crash/1, loop_start_fail/1, call/1, send_request/1, send_request_receive_reqid_collection/1, send_request_wait_reqid_collection/1, send_request_check_reqid_collection/1, @@ -84,7 +84,7 @@ suite() -> {timetrap,{minutes,1}}]. all() -> - [start, {group,stop}, crash, call, send_request, + [start, {group,stop}, crash, loop_start_fail, call, send_request, send_request_receive_reqid_collection, send_request_wait_reqid_collection, send_request_check_reqid_collection, cast, cast_fast, info, abcast, continue, @@ -159,7 +159,10 @@ end_per_testcase(_Case, Config) -> undefined -> ok; Peer -> - peer:stop(Peer) + try peer:stop(Peer) + catch exit : noproc -> + ok + end end, ok. @@ -518,6 +521,55 @@ crash(Config) when is_list(Config) -> ok. + +loop_start_fail(Config) -> + _ = process_flag(trap_exit, true), + loop_start_fail( + Config, + [{start, []}, {start, [link]}, + {start_link, []}, + {start_monitor, [link]}, {start_monitor, []}]). + +loop_start_fail(_Config, []) -> + ok; +loop_start_fail(Config, [{Start, Opts} | Start_Opts]) -> + loop_start_fail( + fun gen_server:Start/3, + {ets, {return, {stop, failed_to_start}}}, Opts, + fun ({error, failed_to_start}) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {return, ignore}}, Opts, + fun (ignore) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {return, 4711}}, Opts, + fun ({error, {bad_return_value, 4711}}) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {crash, error, bailout}}, Opts, + fun ({error, {bailout, ST}}) when is_list(ST) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {crash, exit, bailout}}, Opts, + fun ({error, bailout}) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {wait, 1000, void}}, [{timeout, 200} | Opts], + fun ({error, timeout}) -> ok end), + loop_start_fail(Config, Start_Opts). + +loop_start_fail(GenStartFun, Arg, Opts, ValidateFun) -> + loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, 5). +%% +loop_start_fail(_GenStartFun, _Arg, _Opts, _ValidateFun, 0) -> + ok; +loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N) -> + ok = ValidateFun(GenStartFun(?MODULE, Arg, Opts)), + loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N - 1). + + + %% -------------------------------------- %% Test gen_server:call and handle_call. %% Test all different return values from @@ -2327,11 +2379,10 @@ undef_init(_Config) -> {error, {undef, [{oc_init_server, init, [_], _}|_]}} = (catch gen_server:start_link(oc_init_server, [], [])), receive - {'EXIT', Server, - {undef, [{oc_init_server, init, [_], _}|_]}} when is_pid(Server) -> + Msg -> + ct:fail({unexpected_msg, Msg}) + after 500 -> ok - after 1000 -> - ct:fail(expected_exit_msg) end. %% The upgrade should fail if code_change is expected in the callback module @@ -2782,7 +2833,17 @@ init({continue, Pid}) -> {ok, [], {continue, {message, Pid}}}; init({state,State}) -> io:format("init(state) -> ~p~n", [State]), - {ok,State}. + {ok,State}; +init({ets,InitResult}) -> + ?MODULE = ets:new(?MODULE, [named_table]), + case InitResult of + {return, Value} -> + Value; + {crash, Class, Reason} -> + erlang:Class(Reason); + {wait, Time, Value} -> + receive after Time -> Value end + end. handle_call(started_p, _From, State) -> io:format("FROZ"), diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 8f18c42dc1..6bff254b0b 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -37,7 +37,7 @@ all() -> {group, stop_handle_event}, {group, abnormal}, {group, abnormal_handle_event}, - shutdown, stop_and_reply, state_enter, event_order, + shutdown, loop_start_fail, stop_and_reply, state_enter, event_order, state_timeout, timeout_cancel_and_update, event_types, generic_timers, code_change, {group, sys}, @@ -686,6 +686,53 @@ shutdown(Config) -> end. +loop_start_fail(Config) -> + _ = process_flag(trap_exit, true), + loop_start_fail( + Config, + [{start, []}, {start, [link]}, + {start_link, []}, + {start_monitor, [link]}, {start_monitor, []}]). + +loop_start_fail(_Config, []) -> + ok; +loop_start_fail(Config, [{Start, Opts} | Start_Opts]) -> + loop_start_fail( + fun gen_statem:Start/3, + {ets, {return, {stop, failed_to_start}}}, Opts, + fun ({error, failed_to_start}) -> ok end), + loop_start_fail( + fun gen_statem:Start/3, + {ets, {return, ignore}}, Opts, + fun (ignore) -> ok end), + loop_start_fail( + fun gen_statem:Start/3, + {ets, {return, 4711}}, Opts, + fun ({error, {bad_return_from_init, 4711}}) -> ok end), + loop_start_fail( + fun gen_statem:Start/3, + {ets, {crash, error, bailout}}, Opts, + fun ({error, bailout}) -> ok end), + loop_start_fail( + fun gen_statem:Start/3, + {ets, {crash, exit, bailout}}, Opts, + fun ({error, bailout}) -> ok end), + loop_start_fail( + fun gen_statem:Start/3, + {ets, {wait, 1000, void}}, [{timeout, 200} | Opts], + fun ({error, timeout}) -> ok end), + loop_start_fail(Config, Start_Opts). + +loop_start_fail(GenStartFun, Arg, Opts, ValidateFun) -> + loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, 5). +%% +loop_start_fail(_GenStartFun, _Arg, _Opts, _ValidateFun, 0) -> + ok; +loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N) -> + ok = ValidateFun(GenStartFun(?MODULE, Arg, Opts)), + loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N - 1). + + stop_and_reply(_Config) -> process_flag(trap_exit, true), @@ -2804,6 +2851,17 @@ init({map_statem,#{init := Init}=Machine,Modes}) -> Other -> init_sup(Other) end; +init({ets, InitResult}) -> + ?MODULE = ets:new(?MODULE, [named_table]), + init_sup( + case InitResult of + {return, Value} -> + Value; + {crash, Class, Reason} -> + erlang:Class(Reason); + {wait, Time, Value} -> + receive after Time -> Value end + end); init([]) -> io:format("init~n", []), init_sup({ok,idle,data}). diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl index 26422a8d2a..bc36c2acb7 100644 --- a/lib/stdlib/test/supervisor_SUITE.erl +++ b/lib/stdlib/test/supervisor_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2021. All Rights Reserved. +%% Copyright Ericsson AB 1996-2023. 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. @@ -200,7 +200,7 @@ start_link(InitResult) -> %% Simulate different supervisors callback. init(fail) -> - erlang:error({badmatch,2}); + erlang:error(fail); init(InitResult) -> InitResult. @@ -227,7 +227,7 @@ sup_start_normal(Config) when is_list(Config) -> sup_start_ignore_init(Config) when is_list(Config) -> process_flag(trap_exit, true), ignore = start_link(ignore), - check_exit_reason(normal). + check_no_exit(100). %%------------------------------------------------------------------------- %% Tests what happens if init-callback returns ignore. @@ -325,15 +325,20 @@ sup_start_ignore_permanent_child_start_child_simple(Config) %% Tests what happens if init-callback returns a invalid value. sup_start_error_return(Config) when is_list(Config) -> process_flag(trap_exit, true), - {error, Term} = start_link(invalid), - check_exit_reason(Term). + %% The bad return is processed in supervisor:init/1 + InitResult = invalid, + {error, {bad_return, {?MODULE, init, InitResult}}} = + start_link(InitResult), + check_no_exit(100). %%------------------------------------------------------------------------- %% Tests what happens if init-callback fails. sup_start_fail(Config) when is_list(Config) -> process_flag(trap_exit, true), - {error, Term} = start_link(fail), - check_exit_reason(Term). + %% The exception is processed in gen_server:init_it/2 + ErrorReason = fail, + {error, {ErrorReason, _Stacktrace}} = start_link(ErrorReason), + check_no_exit(100). %%------------------------------------------------------------------------- %% Test what happens when the start function for a child returns @@ -3749,18 +3754,18 @@ check_exit([Pid | Pids], Timeout) -> error end. -check_exit_reason(Reason) -> +check_exit_reason(Pid, Reason) when is_pid(Pid) -> receive - {'EXIT', _, Reason} -> + {'EXIT', Pid, Reason} -> ok; - {'EXIT', _, Else} -> + {'EXIT', Pid, Else} -> ct:fail({bad_exit_reason, Else}) end. -check_exit_reason(Pid, Reason) -> +check_no_exit(Timeout) -> receive - {'EXIT', Pid, Reason} -> - ok; - {'EXIT', Pid, Else} -> - ct:fail({bad_exit_reason, Else}) + {'EXIT', Pid, _} = Exit when is_pid(Pid) -> + ct:fail({unexpected_message, Exit}) + after Timeout -> + ok end. |