[erlang-questions] [erlang-bugs] Strange behaviour of exit(kill)

Loïc Hoguin essen@REDACTED
Fri Oct 9 21:13:29 CEST 2015

On 10/09/2015 10:35 AM, Roland Karlsson wrote:
> Woa!
> Thanx for explaining it so well!
> This seems so wrong that I am ashamed being an Erlang evangelist.
> I just want to lay down, curl together and cry.
> I have always assumed exit/1 just was implemented as
> exit(Reason) -> exit(self(),Reason).
> Or, at least, that they had the same semantics.
> Looking in the manual, I see that exit/1 and exit/2 are
> correctly described, and I do understand the need
> for both (maybe) as exit/1 is synchronous.
> Personally I think exit/2 shall be
> deprecated and renamed to send_exit_signal/2 or maybe kill/2.

Considering it is called an "exit signal", the name makes sense, 
although it is confusing.

Another clue is that exit/2 does not have error/2 or throw/2 equivalents.

Perhaps kill/2 would be better (and would also match the kill command on 
unix) but I don't see it improve things much. You still have the kill 
special case with all attached problems.

On the other hand, if you had exit/2 (or kill/2) just sending an exit 
signal (even if reason is kill), and kill/1 sending a kill signal, then 
things would become much clearer.

A change like this would take many years though.

> To be frank, I have never really liked the exit/2 name as
> it seems counter intuitive to exit someone else.
> /Roland
>> On 10/09, Roland Karlsson wrote:
>>> So Robert, tell me who missed it.
>>> What is the difference in behaviour when using exit/1 or exit/2?
>>> And is the difference still there if the process kills itself with
>>> exit/2?
>>> /Roland
>> exit/1 raises a 'kill' exception (you can do it with erlang:raise(exit,
>> kill, Stacktrace)) that will unwind the stack and can be caught by try
>> ... catch. In the case where the exception is not caught, the process is
>> terminated and the reason of its death forwarded as a signal.
>> exit/2 sends an exit signal asynchronously.
>> The confusing aspect is that a 'kill' signal sent by exit/2 is
>> untrappable, but a 'kill' signal that comes from a process terminating
>> after an exception doesn't.
>> This means the underlying secret is you have 2 levels of signals (at
>> least):
>> - termination signals (uncaught exception create one of these, dying
>>    from any other signal does the same)
>> - kill signals (untrappable)
>> The interesting bit then is why is there a need to translate 'kill' into
>> 'killed'? From what I can tell, it's just so you can know the process
>> was brutally killed -- if you only had 'kill' then you know it came from
>> an exception. Buuuut here's the kicker:
>> 1> spawn_link(fun() -> exit(kill) end), flush().
>> Shell got {'EXIT',<0.87.0>,kill}
>> ok
>> 2> spawn_link(fun() -> spawn_link(fun() -> exit(kill) end),
>> timer:sleep(infinity) end), flush().
>> Shell got {'EXIT',<0.89.0>,killed}
>> 3> spawn_link(fun() -> process_flag(trap_exit, true), spawn_link(fun()
>> -> exit(kill) end), timer:sleep(infinity) end), flush().
>> ok
>> woops. There's no real consistency there. In the end:
>> - a special 'kill' signal that cannot be trapped will unconditionally
>>    kill a process and produce a signal 'killed' for peer processes
>> - an exception 'kill' bubbling up is reported as a trappable exit signal
>>    with the reason 'kill'
>> - that 'kill' exit signal is converted to 'killed' for other processes,
>>    regardless of the source, as long as it's not from a local stack.
>> This just sounds like a leaky abstraction where the conversion of kill
>> -> killed only takes place on incoming signals being converted,
>> unconditionally. Still, you have two types of signals: untrappable
>> (exit(Pid, kill)), and trappable (anything else, even with a reason
>> kill, if produced from a stacktrace).
>> It's confusing and always has been so.

Loïc Hoguin
Author of The Erlanger Playbook,
A book about software development using Erlang

More information about the erlang-questions mailing list