使用start_link启动时,GenServer不会trap_exit

时间:2019-05-17 04:55:34

标签: erlang elixir otp gen-server

我发现,当尝试捕获退出信号时,使用GenServer.start然后Process.link启动GenServer会导致pid与运行GenServer.start_link的结果大不相同。

这是我用来演示该问题的实验代码:

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

对于版本1,EXIT信号导致Server退出而没有陷入其handle_info块中,但是对于版本2,该信号被正确拦截并在handle_info块中进行了处理,因此Server不终止。我还注意到,这两种启动GenServer的方法中的Process.info是相同的。

我使用spawn_linkspawn尝试了相同的操作,但是它们的行为均符合预期-退出信号全部被捕获-没什么区别:

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

如果有关系的话,我在Erlang / OTP 21上使用Elixir 1.8.1的方式。

我想知道是什么原因造成了行为上的差异,这是错误还是特意设计的,以及如果我想自动调用start + link,如何正确捕获EXIT。

1 个答案:

答案 0 :(得分:3)

handle_info没有被调用,因为发送退出信号的是父进程。 GenServer和所有其他行为会处理父级退出信号,并在父级退出时始终关闭,这主要是因为如果您在监视树中并且您的主管也关闭了,那么您也想立即终止,因为那时候所有赌注都没有了。如果您将其替换:

Process.exit(pid, :reason)

通过:

spawn fn -> Process.exit(pid, :reason) end

您可以看到handle_info被称为另一个进程正在发送退出信号。