使用gen_server:start_link创建的进程未随父进程终止问题排查
Erlang gen_server未随父进程终止的问题排查与解决
问题场景
编写了如下trivial模块的gen_server:
-module(trivial). -behaviour(gen_server). -export([ init/1, handle_call/3, handle_cast/2 ]). init([]) - {ok, []}. handle_call(_Msg, _From, State) - {reply, ok, State}. handle_cast(_Msg, State) - {noreply, State}.
接着在一个会立即终止的进程中启动它:
c(trivial). Before = length([ Proc || Proc <- processes(), is_process_alive(Proc) ]). Pid = spawn(fun() - gen_server:start_link(trivial, [], []) end). false = is_process_alive(Pid). Before = length([ Proc || Proc <- processes(), is_process_alive(Proc) ]).
执行后最后一个断言不成立:存活进程数多了1个。预期gen_server:start_link会在spawn出的进程与服务器进程间创建链接,当spawn进程终止时服务器也随之终止,但实际并未发生。
原因分析
gen_server:start_link/3确实会在调用进程(即spawn出的进程)和gen_server进程之间建立链接,但Erlang的链接机制有个关键规则:只有当链接的进程因非正常原因(如崩溃、被exit(kill)杀死)终止时,才会触发关联进程的退出;如果进程是正常终止(退出原因为normal),链接的进程不会收到退出信号,会继续存活。- 这里spawn出的进程在
gen_server:start_link成功返回后,没有后续代码执行,会以normal原因正常终止,因此gen_server进程不会被触发退出,导致存活进程数增加。 - 另外,你的gen_server模块缺少了
handle_info/2、terminate/2等必要回调的导出,虽然不影响当前问题,但不符合gen_server的规范实现。
解决方法
要实现“spawn进程终止时gen_server随之终止”的需求,有两种可行方式:
方式1:让spawn进程以非正常原因终止
在spawn的函数体中,调用gen_server:start_link后主动让进程以非normal原因退出,比如使用exit(kill):
Pid = spawn(fun() -> gen_server:start_link(trivial, [], []), exit(kill) end).
此时spawn进程以kill原因终止,链接的gen_server进程会收到退出信号,随之终止。
方式2:让gen_server监听父进程退出事件
修改gen_server的实现,开启退出陷阱并处理父进程的退出消息,确保父进程无论以何种原因终止,gen_server都随之退出:
-module(trivial). -behaviour(gen_server). -export([ init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2 ]). init([]) -> % 开启退出陷阱,让进程能收到EXIT消息 process_flag(trap_exit, true), % 获取并链接父进程 Parent = erlang:group_leader(), link(Parent), {ok, []}. handle_call(_Msg, _From, State) -> {reply, ok, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info({'EXIT', _ParentPid, _Reason}, State) -> % 父进程退出,终止自身 {stop, normal, State}; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok.
这种方式更优雅,能处理父进程正常或非正常终止的所有情况,确保gen_server随父进程一起退出。
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

