以 start_link 启动时,GenServer 不会 trap_exit
GenServer doesn't trap_exit when started with start_link
我发现当尝试捕获退出信号时,用 GenServer.start
启动 GenServer 然后 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 中,信号在 [=18= 中被正确拦截和处理] 块,因此 Server
不会终止。我还注意到这两种启动 GenServer 的方式 Process.info
是相同的。
我用 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
顺便说一下,我在 Erlang/OTP 21 上使用 Elixir 1.8.1,如果它重要的话。
我想知道是什么导致了行为上的差异,这是一个错误还是设计使然,以及如果我想以原子方式调用 start+link,我如何才能正确捕获 EXIT。
handle_info
没有被调用,因为发送退出信号的是父进程。 GenServer 和所有其他行为处理父级退出信号并始终在父级退出时关闭,主要是因为如果您在监督树中并且您的监督员关闭,您也想立即终止,因为在那一刻所有的赌注都关闭了。如果你替换这个:
Process.exit(pid, :reason)
作者:
spawn fn -> Process.exit(pid, :reason) end
您可以看到 handle_info
在另一个进程发送退出信号时被调用。
我发现当尝试捕获退出信号时,用 GenServer.start
启动 GenServer 然后 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 中,信号在 [=18= 中被正确拦截和处理] 块,因此 Server
不会终止。我还注意到这两种启动 GenServer 的方式 Process.info
是相同的。
我用 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
顺便说一下,我在 Erlang/OTP 21 上使用 Elixir 1.8.1,如果它重要的话。
我想知道是什么导致了行为上的差异,这是一个错误还是设计使然,以及如果我想以原子方式调用 start+link,我如何才能正确捕获 EXIT。
handle_info
没有被调用,因为发送退出信号的是父进程。 GenServer 和所有其他行为处理父级退出信号并始终在父级退出时关闭,主要是因为如果您在监督树中并且您的监督员关闭,您也想立即终止,因为在那一刻所有的赌注都关闭了。如果你替换这个:
Process.exit(pid, :reason)
作者:
spawn fn -> Process.exit(pid, :reason) end
您可以看到 handle_info
在另一个进程发送退出信号时被调用。