GenServer не trap_exit при запуске с start_link - PullRequest
1 голос
/ 17 мая 2019

Я обнаружил, что при попытке перехватить сигнал выхода, начиная GenServer с GenServer.start, затем Process.link, 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 не прекращается. Я также заметил, что Process.info этих двух способов запуска GenServer идентичны.

Я пытался сделать то же самое с spawn_link и spawn, но все они ведут себя, как и ожидалось - сигнал EXIT все ловится - нет никакой разницы:

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

Кстати, я использую Elixir 1.8.1 на Erlang / OTP 21, если это имеет значение.

Я хочу знать, что вызывает разницу в поведении, это ошибка или дизайн, и как я могу правильно перехватить EXIT, если я хочу вызвать start + link атомарно.

1 Ответ

3 голосов
/ 17 мая 2019

handle_info не вызывается, потому что тот, который посылает сигнал выхода, является родительским процессом.GenServer и все другие варианты поведения обрабатывают родительский сигнал выхода и всегда выключаются, когда родитель делает это, главным образом потому, что если вы находитесь в дереве контроля и ваш супервизор выключается, вы также хотите немедленно прекратить, так как в этот момент все ставки выключены.Если вы замените это:

Process.exit(pid, :reason)

By:

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

Вы можете увидеть handle_info, вызванный, поскольку другой процесс посылает сигнал выхода.

...