Process.exit(pid, :normal) not triggering :DOWN on monitors

If you execute Process.exit(pid, :normal), for a monitored process, the :DOWN message is not received. For example,

child = spawn(fn -> Process.sleep(:infinity) end)
Process.monitor(child)
Process.exit(child, :normal)

receive do
  {:DOWN, _ref, :process, _monitored_pid, reason} ->
    IO.puts("Monitored process down with reason #{reason}")
  after
    5000 ->
      IO.puts("Timeout")
end

prints “Timeout”.

Why is that? Do you see it documented anywhere in Elixir or Erlang?

Interestingly, Process.exit(pid, :kill) does trigger the :DOWN message, though.

Exiting a process with a reason of :normal flags that the termination is normal (for example the process is done processing and everything went OK). Because this is not considered an error state the :DOWN message is not sent. Exiting a process with any other exit code generates the exit message.

:normal and :kill are are special to exit. Normal means everything went OK and kill is not trappable meaning the process will die and you can’t do anything about it. Any other reason is for informational purposes and can be trapped.

This behaviour is documented (although perhaps not the why) in Process.exit in elixir and exit/2 in erlang.

More information can be found under 12.4 in http://erlang.org/doc/reference_manual/processes.html which further links to more information about this.

Another take on it is: https://learnyousomeerlang.com/errors-and-processes

1 Like

Ah, I connected the dots.

The process does not exit because :normal is ignored. That function is sending an exit signal, not making the process terminate.

You do not get :DOWN because at the end of the script the process is alive.

1 Like

BTW, note that a :normal exit reason when the process terminates (for example, by having no more code to execute) does send :DOWN messages. Monitors monitor when processes terminate with any reason, they are not only monitoring abnormal termination.

The real problem in my script is that the process does not terminate.

Exactly, Process.exit(pid, :normal) is silently ignored if pid is different from self() (the caller pid). Note that it still terminates the process if called like Process.exit(self(), :normal), or if called with any pid and a reason different than :normal.

2 Likes