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