I wouldn’t call it a deadlock, but rather a race condition. Too see why it happens, let’s first name these processes:
def sample do
# process A
lab_pid = self()
spawn_link(fn ->
# process B
spawn_link(fn ->
# process C
Process.flag(:trap_exit, true)
receive do
{:EXIT, _pid, :normal} ->
send(lab_pid, :done)
end
end)
end)
receive do
msg ->
msg
end
end
So without a timeout, it’s possible that process B stops before Process.flag in process C has been invoked. This happens because spawn and spawn_link are asynchronous. When these functions return, the process has been created, but it’s possible that they still haven’t executed any instruction.
So in this scenario, if process C starts trapping exits after process B had stopped, the corresponding exit signal won’t be converted into a message. So now, you have process C waiting forever for a message which will never arrive, and consequently, process A is also waiting forever for a message which process C will never send.
The solution would be to use synchronous start, e.g. with :proc_lib:
def sample do
lab_pid = self()
spawn_link(fn ->
:proc_lib.start_link(Kernel, :apply, [fn ->
Process.flag(:trap_exit, true)
# `:proc_lib.start_link` will return after this function is invoked
:proc_lib.init_ack({:ok, self()})
receive do
{:EXIT, _pid, :normal} ->
send(lab_pid, :done)
end
end, []])
end)
receive do
msg ->
msg
end
end






















