As your third example shows if any process does an exit(self(), normal)
then it crashes while doing exit(AnotherPid, normal)
does not crash the other process. I have verified it on an R15B. I personally think that this is a bug as sending exit signal normal
to any process should not result in its crashing.
Erlang exit/2 bug in R15B03
-
30-11-2021 - |
Question
Short copy from here:
exit(Pid, Reason) -> true
Types:
Pid =
pid()
Reason =term()
Sends an exit signal with exit reason
Reason
to the processPid
.The following behavior apply if Reason is any term except
normal
orkill
:If
Pid
is not trapping exits,Pid
itself will exit with exit reasonReason
. IfPid
is trapping exits, the exit signal is transformed into a message{'EXIT', From, Reason}
and delivered to the message queue ofPid
.From
is the pid of the process which sent the exit signal. See alsoprocess_flag/2
.If
Reason
is the atomnormal
,Pid
will not exit. If it is trapping exits, the exit signal is transformed into a message{'EXIT', From, normal}
and delivered to its message queue.If
Reason
is the atomkill
, that is ifexit(Pid, kill)
is called, an untrappable exit signal is sent toPid
which will unconditionally exit with exit reasonkilled
.
I am playing around with the exit/2
function and its behavior when self()
is used as a Pid
and normal
as a Reason
.
Erlang R15B03 (erts-5.9.3) [source] [64-bit] [smp:8:8] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.3 (abort with ^G)
1> self().
<0.32.0>
2> exit(self(), normal).
** exception exit: normal
3> self().
<0.35.0>
Shouldn't it be the case that only a 'normal' exit message is sent to the shell process, so there is no reason to exit?
Similarly:
4> spawn(fun() -> receive Pid -> Pid ! ok end end).
<0.38.0>
5> exit(v(4), normal).
true
6> v(4) ! self().
<0.35.0>
7> flush().
Shell got ok
ok
But:
8> spawn(fun() -> exit(self(), normal), receive _ -> ok end end).
<0.43.0>
9> is_process_alive(v(8)).
false
La solution 2
Autres conseils
It looks like the Erlang shell (shell.erl) doesn't handle 'EXIT'
messages of type normal
any differently than other exit messages, which means that it sends an error and restarts the shell. If you really want to find out this you can trace the program flow using the debugger in this way:
- Download shell.erl
- Change the filename to
shell2.erl
- Open the file and change the module name to
shell2
as well. You need to do this because the compiler will complain aboutshell
being in a sticky directory. - Start the
erl
prompt. c(shell2, [debug_info]).
debugger:start().
Module -> Interpret
and pick shell2.erlshell2:start().
- Trace away!
I think the reason can be found from the source code 'stdlib-1.18.2/src/shell.erl'.
get_command(Prompt, Eval, Bs, RT, Ds) ->
Parse = fun() -> exit(io:parse_erl_exprs(Prompt)) end,
Pid = spawn_link(Parse),
get_command1(Pid, Eval, Bs, RT, Ds).
get_command1(Pid, Eval, Bs, RT, Ds) ->
receive
{'EXIT', Pid, Res} ->
{Res, Eval};
{'EXIT', Eval, {Reason,Stacktrace}} ->
report_exception(error, {Reason,Stacktrace}, RT),
get_command1(Pid, start_eval(Bs, RT, Ds), Bs, RT, Ds);
{'EXIT', Eval, Reason} ->
report_exception(error, {Reason,[]}, RT),
get_command1(Pid, start_eval(Bs, RT, Ds), Bs, RT, Ds)
end.
report_exception(Class, Reason, RT) ->
report_exception(Class, serious, Reason, RT).
report_exception(Class, Severity, {Reason,Stacktrace}, RT) ->
Tag = severity_tag(Severity),
I = iolist_size(Tag) + 1,
PF = fun(Term, I1) -> pp(Term, I1, RT) end,
SF = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end,
io:requests([{put_chars, Tag},
{put_chars,
lib:format_exception(I, Class, Reason, Stacktrace, SF, PF)},
nl]).
start_eval(Bs, RT, Ds) ->
Self = self(),
Eval = spawn_link(fun() -> evaluator(Self, Bs, RT, Ds) end), %%<========start a new shell pid
put(evaluator, Eval),
Eval.
severity_tag(fatal) -> <<"*** ">>;
severity_tag(serious) -> <<"** ">>;
severity_tag(benign) -> <<"* ">>.
For your first case (sending message to self()), the signal meets the 2nd situation of get_command1/5
, it will give error message firstly, and spawn a new shell pid. Please pay attention to start_eval
function.
For your second case (sending message to a spawn pid
), Going back to your first part of post about the exit
function, it is logical. Your spawned pid
doesn't trap the exit message, and ignore the (normal) exit
message. so the shell
will receive exit message only when your spawn pid
's actual exit. It will go to the 1st condition of get_command1/5
. Because start_evel
is not been called, shell pid
will keep the same.
For your third case, I don't know why. I also think is_process_alive(v(8))
should return true.
For the third case, the key point here is that self() is the pid of spawned process not the shell's.
see code below:
Eshell V5.9 (abort with ^G)
1> self().
<0.32.0>
2> spawn(fun() -> io:format("This is ~p~n",[self()]),exit(self(), normal), receive _ -> ok end end).
This is <0.35.0> <0.35.0>
3> is_process_alive(v(2)).
false
4>
It's all right there in the documentation that you quoted:
If Pid is not trapping exits, Pid itself will exit with exit reason Reason.
If you don't trap exits then your process will exit.
1> self().
<0.32.0>
2> process_flag(trap_exit, true).
false
3> exit(self(), normal).
true
4> self().
<0.32.0>
5> flush().
Shell got {'EXIT',<0.32.0>,normal}
ok
There is no "message" being sent about exiting if you don't trap exits. The process just dies. As does any process linked to it. This is what trap_exit
is for.