Reputation: 3083
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.
Upvotes: 1
Views: 1072
Reputation: 51349
handle_info
is not being called because the one sending the exit signal is the parent process. GenServer and all other behaviours handles the parent exit signal and always shuts down when the parent does, mainly because if you are in a supervision tree and your supervisor shuts down, you want to terminate immediately too as at that moment all bets are off. If you replace this:
Process.exit(pid, :reason)
By:
spawn fn -> Process.exit(pid, :reason) end
You can see handle_info
called as another process is sending the exit signal.
Upvotes: 6