原文地址: https://blog.cndenis.com/erlang/2014/06/pitfall_on_gen_server_terminate.html
gen_server进程结束时, 会调用terminate
函数, 但这并不是在任何情况下都成立的.
当gen_server进程主动关闭时, 也就是在回调函数 handle_xxx
中返回 {stop...}
的时候, terminate
是必然被调用的.
当gen_server进程处于监控树中, 被其监控进程关闭时, 情况就不一样了.
只有在这个进程设置了捕获退出信号, 即 process_flag(trap_exit, true)
,
并且其开启选项中设置了关闭超时时间, 而不是 brutal_kill
, terminate
才会被执行.
换句话说, 不捕获退出信号的gen_server被其监控进程关闭时, 会直接死掉,
不执行terminate
函数!
看一下 gen_server
的源代码关于消息处理的部分:
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.
可以看出, 当它的父进程执行 erlang:exit(Pid, Reason)
时,
也就是给它发送 {'EXIT', Parent, Reason}
,
gen_server会调用 terminate
并结束自己.
gen_server源代码中并没有设置 trap_exit
, 也就是默认是不捕获退出信号的,
如果用户不自己设置, 进程一旦接收到 {'EXIT', Parent, Reason}
就会立即退出,
没有机会运行 terminate
.
从这段代码也可以看出, 设置 trap_exit
后, 用户并不需要而且没办法在 handle_info
里处理来自监控树的父进程的退出信号, gen_server在这个消息在到达 handle_info
之前就已经处理了, 直接调用了 terminate
.
因此, 要确保监控树中的gen_server在进程结束时执行 terminate
,
需要设置 process_flag(trap_exit, true)
,
并且要在启动参数中, 设置足够长的结束等待时间.
参考:
-
9 Erlang pitfalls you should know about, 其中第7条讲的就是这个问题, 有试验代码.
-
Erlang OTP之terminate 深入分析, 庆亮写的分析, 也有试验代码. 其中对于
simple_one_for_one
的描述不尽正确, 在目前的R16B中,simple_one_for_one
的子进程信息是存在#state.dynamics
中的, 只要处理时间足够, 关闭监控树是会正常关闭所有子进程的.
原文地址: https://blog.cndenis.com/erlang/2014/06/pitfall_on_gen_server_terminate.html