无监督 gen_server 在收到退出信号时不调用终止

Unsupervised gen_server doesn't call terminate when it receives exit signal

gen_server 关于 Module:terminate 回调的文档说:

Even if the gen_server process is not part of a supervision tree, this function is called if it receives an 'EXIT' message from its parent. Reason is the same as in the 'EXIT' message.

这是我的 handle_infoterminate 函数:

handle_info(UnknownMessage, State) ->
    io:format("Got unknown message: ~p~n", [UnknownMessage]),
    {noreply, State}.

terminate(Reason, State) ->
    io:format("Terminating with reason: ~p~n", [Reason]).

我使用 gen_server:start 启动此服务器。我假设当我调用 erlang:exit(Pid, fuckoff) 时,它应该调用 terminate 回调函数。但它显示:

Got unknown message: {'EXIT',<0.33.0>,fuckoff}

表示正在调用handle_info。但是当我调用 gen_server:stop 时,一切都按照文档中提到的那样工作。我正在从 shell 呼叫我的 gen_server。你能澄清一下吗?

[更新]

Heregen_serverdecode_msg函数的源代码。如果它收到任何 'EXIT' 消息,它应该调用 terminate 函数:

decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) ->
    case Msg of
    {system, From, Req} ->
        sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
                  [Name, State, Mod, Time], Hib);
    {'EXIT', Parent, Reason} ->
        terminate(Reason, Name, Msg, Mod, State, Debug);
    _Msg when Debug =:= [] ->
        handle_msg(Msg, Parent, Name, State, Mod);
    _Msg ->
        Debug1 = sys:handle_debug(Debug, fun print_event/3,
                      Name, {in, Msg}),
        handle_msg(Msg, Parent, Name, State, Mod, Debug1)
end.

在我的例子中它没有调用 terminate 函数。

[更新]

当我使用 gen_server:start_link() 启动 gen_server 时,使用 erlang:exit(Pid, Reason) 发送退出信号将导致调用 terminate 回调函数,这是预期的行为。无论进程是否链接到其父进程,在解释退出信号时似乎有所不同。

当您启动 gen_server 时,这是一个简单的过程,因此,erlang:exit/1 or erlang:exit/2 按预期工作。

  • If Pid is not trapping exits, Pid itself exits with exit reason Reason.
  • If Pid is trapping exits, the exit signal is transformed into a message {'EXIT', From, Reason} and delivered to the message queue of Pid.

所以,目前您的代码陷阱 'EXIT' 信号是因为这封邮件像任何其他邮件一样发送到邮箱并匹配 handle_info/2 通配符模式。

如果您想了解更多相关信息,可以阅读此代码中描述的 gen_server source code and see how it works. You can also find your problem

简答:

如果您从 gen_server actor 内部调用 exit/2 函数,它会根据文档按预期运行,并且会调用 terminate/2 回调。

长答案:

当您从 shell 发送退出消息时,退出元组的 Parent 值设置为 shell 进程 ID,另一方面,当您启动 gen_server 进程形式 shell 它的 Parent 值设置为它自己的进程 ID,而不是 shell 进程 ID,因此当它收到退出消息时它与第二个不匹配decode_msg/8 函数中接收块的子句,因此不调用 terminate/6 函数,最后匹配调用 handle_msg/5 函数的下一个子句。

建议:

为了通过向 gen_server 进程发送退出消息来调用 terminate/3 回调,您可以将退出消息捕获在 handle_info/2 中,然后 return 中停止元组如下:

init([]) ->
    process_flag(trap_exit, true),
    {ok, #state{}}.

handle_info({'EXIT', _From, Reason}, State) ->
    io:format("Got exit message: ~p~n", []),
    {stop, Reason, State}.

terminate(Reason, State) ->
    io:format("Terminating with reason: ~p~n", [Reason]),
    %% do cleanup ...
    ok.