I found that when trying to trap exit signal, starting a GenServer with GenServer.start then Process.link the pid has very different outcome than running GenServer.start_link.
Here's the experiment code I used to demonstrate the problem:
defmodule Foo do
defmodule Server do
def init(_) do
Process.flag(:trap_exit, true)
{:ok, nil}
end
def handle_info({:EXIT, from, reason}, _) do
IO.inspect({:kill_signal, from, reason})
{:noreply, nil}
end
end
def foo() do
Process.flag(:trap_exit, true)
# version 1
{:ok, pid} = GenServer.start_link(Server, nil)
# version 2
# {:ok, pid} = GenServer.start(Server, nil)
# Process.link(pid)
# print process info
IO.inspect({self(), pid, Process.info(pid)})
Process.exit(pid, :reason)
:timer.sleep(200)
end
end
Foo.foo
With version 1, the EXIT signal cause the Server to exit without being caught in its handle_info block, but with version 2, the signal was correctly intercepted and processed in handle_info block, and therefore Server is not terminated. I also notice that the Process.info of these two ways to start a GenServer are identical.
I tried the same thing with spawn_link and spawn, but they all behaves as expected - EXIT signal all being caught - there's no difference:
defmodule Foo do
def loop do
Process.flag(:trap_exit, true)
receive do
msg -> IO.inspect(msg)
end
loop
end
def foo() do
Process.flag(:trap_exit, true)
# version 1
pid = spawn_link(&loop/0)
# version 2
# pid = spawn(&loop/0)
# Process.link(pid)
# print process info
IO.inspect({self(), pid, Process.info(pid)})
Process.exit(pid, :reason)
:timer.sleep(200)
end
end
Foo.foo
By the way I'm using Elixir 1.8.1 on Erlang/OTP 21, if it matters.
I want to know what causes the difference in behaviors, is this a bug or by design, and how I can trap EXIT correctly if I want to call start+link atomically.