shouya
shouya

Reputation: 3083

GenServer doesn't trap_exit when started with start_link

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

Answers (1)

José Valim
José Valim

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

Related Questions