summaryrefslogtreecommitdiff
path: root/lib/stdlib
diff options
context:
space:
mode:
authorRaimo Niskanen <raimo@erlang.org>2023-02-23 12:58:25 +0100
committerRaimo Niskanen <raimo@erlang.org>2023-02-23 12:59:43 +0100
commitb0afc29474f9625aa0b466b6672e0bbcefd07535 (patch)
tree5ef8a33e407c6d048e1658a8213534c56febe736 /lib/stdlib
parentb31b8ad1225caed2944f6f1eb4a9d98fc7af9709 (diff)
parent381648e4c054405083967a2ada779fd5f8adf37d (diff)
downloaderlang-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.xml235
-rw-r--r--lib/stdlib/src/gen.erl3
-rw-r--r--lib/stdlib/src/gen_fsm.erl26
-rw-r--r--lib/stdlib/src/gen_server.erl45
-rw-r--r--lib/stdlib/src/gen_statem.erl20
-rw-r--r--lib/stdlib/src/proc_lib.erl91
-rw-r--r--lib/stdlib/test/gen_server_SUITE.erl77
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl60
-rw-r--r--lib/stdlib/test/supervisor_SUITE.erl35
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.