exit/2

Erlang provides as well a function to, in theory, send an exit signal to a process without affecting the current process (as stated here): exit/2. This function has many corner cases and gotchas.

Intuitive Scenarios

Let’s start with the expected scenario: you use exit(Pid, Reason) , Reason is not normal nor kill and either Pid is trapping exits or it’s not linked to the calling process:

1> process_flag(trap_exit, true).

false

2> Self = self().

<0.67.0>

3> Pid = spawn_link(fun() -> exit(Self, bye),

3> timer:sleep(60000)

3> end).

<0.71.0>

4> flush().

Shell got {'EXIT',<0.71.0>,bye}

ok

5> is_process_alive(Pid).

true

6>

As you can see, since Self (a.k.a. the console) is trapping exits, it receives an exit signal from the spawned process but the process remains very much alive.

1> Pid = spawn(fun() -> receive Shell -> exit(Shell, bye) end,

1> timer:sleep(60000)

1> end).

<0.69.0>

2> Pid ! self().

** exception exit: bye

3> is_process_alive(Pid).

true

4>

As you can see, I had to do a trick here since Exit Signals travel faster than evaluation results and I would not have been able to assign anything to Pid otherwise. In the end, same thing happened, the shell got the exit signal and died, but the other process remained alive.

The Boomerang Situation

Now let me show you a very very similar example…

1> Pid = spawn_link(fun() -> receive Shell -> exit(Shell, bye) end,

1> timer:sleep(60000)

1> end).

<0.69.0>

2> Pid ! self().

** exception exit: bye

3> is_process_alive(Pid).

false

4>

Woah! What happened there? Well… since Pid was linked to the shell and not trapping exits, and the shell died with reason bye … Pid died with reason bye as well. As… ehm… expected.

The Unconditional Killer

You can also use the atom kill as the second argument on exit/2. According to the docs…

If Reason is the atom kill, that is, if exit(Pid, kill) is called, an untrappable exit signal is sent to Pid, which unconditionally exits with exit reason killed.

…and also…

An exception to [the rule about trapping exits] is if the exit reason is kill, that is if exit(Pid,kill) has been called. This unconditionally terminates the process, regardless of if it is trapping exit signals.

1> process_flag(trap_exit, true).

false

2> Self = self().

<0.67.0>

3> spawn(fun() -> exit(Self, kill) end).

** exception exit: killed

4>

That looks consistent with the above, but wait… because there is another way to generate an exit signal with reason kill …

1> process_flag(trap_exit, true).

false

2> Self = self().

<0.67.0>

3> spawn_link(fun() -> exit(kill) end).

<0.71.0>

4> flush().

Shell got {'EXIT',<0.71.0>,kill}

ok

5>

A-ha! So… exit(Pid, kill) is not just sending an exit signal with reason kill . It is actually doing something else: It’s sending an untrappable exit signal. The reason is actually irrelevant, we will never have access to it anyway.

The “normal” Situation

So what happens when your Reason is normal ? Well, if you don’t try to send the exit signal to yourself, you’re fine…

1> Self = self().

<0.67.0>

2> Pid = spawn_link(fun() -> exit(Self, normal),

2> timer:sleep(60000)

2> end).

<0.70.0>

3> process_flag(trap_exit, true).

false

4> Pid2 = spawn_link(fun() -> exit(Self, normal),

timer:sleep(60000)

end).

<0.73.0>

5> flush().

Shell got {'EXIT',<0.73.0>,normal}

ok

6> is_process_alive(Pid).

true

7> is_process_alive(Pid2).

true

8>

Nothing different than what’s described in the docs. But what if you send an exit signal to yourself?

Suicidal Tendencies

Finally, we arrive to the core of Robert Virding’s email. What if, for some mysterious reason you decide to send an exit signal to yourself, as if you have terminated? Let’s first try to check the exit signals, by trapping them.

1> process_flag(trap_exit, true).

false

2> exit(self(), bye).

true

3> flush().

Shell got {'EXIT',<0.67.0>,bye}

ok

4> exit(self(), normal).

true

5> flush().

Shell got {'EXIT',<0.67.0>,normal}

ok

6> exit(self(), kill).

** exception exit: killed

7>

If you’re trapping exit signals, nothing out of the ordinary happens: unless the reason is kill , you just receive the corresponding messages and keep moving on. But if you are not trapping exits…