1. 从start方法产出的独立gen_server进程
实验代码:
%%%--------------------------------------
%%% @Module :
%%% @Author :
%%% @Email :
%%% @Created :
%%% @Description:
%%%--------------------------------------
-module(ter_a).
-behaviour(gen_server).
%% gen_server callbacks exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% gen_server api exports
-export([start/1, link/1, is_alive/0, mistake/0, stop/1]).
-record(state, {}).
%%====================================================================
%% API
%%====================================================================
start(Flag) ->
gen_server:start({local, ?MODULE}, ?MODULE, [Flag], []).
link(Flag) ->
gen_server:call(?MODULE, {link, Flag}).
is_alive() ->
gen_server:call(?MODULE, is_alive).
mistake() ->
gen_server:cast(?MODULE, mistake).
stop(Reason) ->
gen_server:cast(?MODULE, {stop, Reason}).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Flag]) ->
case Flag of
0 -> skip;
_ -> process_flag(trap_exit, true)
end,
{ok, #state{}}.
handle_call({link, Flag}, _From, State) ->
{ok, Pid} = ter_b:start_link(Flag),
io:format("handle_call, link to ter_b, LinkPid:~p~n", [Pid]),
{reply, Pid, State};
handle_call(is_alive, _From, State) ->
io:format("yes, i‘m alive~n"),
{reply, alive, State};
handle_call(Request, _From, State) ->
io:format("handle_call, Request:~p~n", [Request]),
Reply = ok,
{reply, Reply, State}.
handle_cast(mistake, State) ->
A = 0,
_B = 1 / A,
{noreply, State};
handle_cast({stop, Reason}, State) ->
io:format("handle_cast, stop, Reason:~p~n", [Reason]),
{stop, Reason, State};
handle_cast(Msg, State) ->
io:format("handle_cast, Msg:~p~n", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
io:format("handle_info, Info:~p~n", [Info]),
{noreply, State}.
terminate(Reason, _State) ->
io:format("terminate, Reason:~p~n", [Reason]),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
1) 无论有没有在初始化的时候捕捉退出即process_flag(trap_exit, true),只要回调函数以{stop, Reson, State}或者{stop, Reson, State}都会跑到terminate/2,特别的如果Reson为normal、shutdown或者{shutdown, ElseInfo},进程会正常退出,否则进程会报错并退出。部分实验数据如下:
1> ter_a:start(0). {ok,<0.33.0>} 2> ter_a:stop(normal). handle_cast, stop, Reason:normal terminate, Reason:normal ok 3> ter_a:start(1). {ok,<0.36.0>} 4> ter_a:stop(else). handle_cast, stop, Reason:else terminate, Reason:else ok 5> =ERROR REPORT==== 28-Apr-2015::10:38:42 === ** Generic server ter_a terminating ** Last message in was {‘$gen_cast‘,{stop,else}} ** When Server state == {state} ** Reason for termination == ** else
实际上,可以通过gen_server的源码了解到这些东西:
dispatch({‘$gen_cast‘, Msg}, Mod, State) -> Mod:handle_cast(Msg, State); dispatch(Info, Mod, State) -> Mod:handle_info(Info, State). handle_msg({‘$gen_call‘, From, Msg}, Parent, Name, State, Mod) -> case catch Mod:handle_call(Msg, From, State) of ... {stop, Reason, Reply, NState} -> {‘EXIT‘, R} = (catch terminate(Reason, Name, Msg, Mod, NState, [])), reply(From, Reply), exit(R); Other -> handle_common_reply(Other, Parent, Name, Msg, Mod, State) end; handle_msg(Msg, Parent, Name, State, Mod) -> Reply = (catch dispatch(Msg, Mod, State)), handle_common_reply(Reply, Parent, Name, Msg, Mod, State). handle_msg({‘$gen_call‘, From, Msg}, Parent, Name, State, Mod, Debug) -> case catch Mod:handle_call(Msg, From, State) of ... {stop, Reason, Reply, NState} -> {‘EXIT‘, R} = (catch terminate(Reason, Name, Msg, Mod, NState, Debug)), _ = reply(Name, From, Reply, NState, Debug), exit(R); Other -> handle_common_reply(Other, Parent, Name, Msg, Mod, State, Debug) end; handle_msg(Msg, Parent, Name, State, Mod, Debug) -> Reply = (catch dispatch(Msg, Mod, State)), handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug). handle_common_reply(Reply, Parent, Name, Msg, Mod, State) -> case Reply of ... {stop, Reason, NState} -> terminate(Reason, Name, Msg, Mod, NState, []); {‘EXIT‘, What} -> terminate(What, Name, Msg, Mod, State, []); _ -> terminate({bad_return_value, Reply}, Name, Msg, Mod, State, []) end. handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug) -> case Reply of ... {stop, Reason, NState} -> terminate(Reason, Name, Msg, Mod, NState, Debug); {‘EXIT‘, What} -> terminate(What, Name, Msg, Mod, State, Debug); _ -> terminate({bad_return_value, Reply}, Name, Msg, Mod, State, Debug) end.
terminate(Reason, Name, Msg, Mod, State, Debug) ->
case catch Mod:terminate(Reason, State) of
{‘EXIT‘, R} ->
FmtState = format_status(terminate, Mod, get(), State),
error_info(R, Name, Msg, FmtState, Debug),
exit(R);
_ ->
case Reason of
normal ->
exit(normal);
shutdown ->
exit(shutdown);
{shutdown,_}=Shutdown ->
exit(Shutdown);
_ ->
FmtState = format_status(terminate, Mod, get(), State),
error_info(Reason, Name, Msg, FmtState, Debug),
exit(Reason)
end
end.
2) 从外部用exit(Pid, Reason)去杀死这个进程的情况
a) 只要Reason不是normal或者kill,如果进程没有捕捉退出,则进程会以Reason的理由直接退出,不会跑到terminate/2;如果有捕捉退出,进程不会退出,而且这个操作会转换成一条法外消息{‘EXIT‘, From, Reason}发到进程,由handle_info处理:
1> {ok, Pid} = ter_a:start(0). {ok,<0.33.0>} 2> exit(Pid, else). true 3> ter_a:is_alive(). ** exception exit: {noproc,{gen_server,call,[ter_a,is_alive]}} in function gen_server:call/2 (gen_server.erl, line 182) 4> {ok, Pid1} = ter_a:start(1). {ok,<0.38.0>} 5> exit(Pid1, else). handle_info, Info:{‘EXIT‘,<0.36.0>,else} true 6> ter_a:is_alive(). yes, i‘m alive alive
b) Reason是normal,如果进程没有捕捉退出,则进程不会退出;如果有捕捉退出,进程也不会退出,只会转为{‘EXIT‘, From, normal}消息投递到Info的消息{‘EXIT‘, From, normal}消息投递到进程信箱,由handle_info处理:
1> {ok, Pid0} = ter_a:start(0). {ok,<0.33.0>} 2> exit(Pid0, normal). true 3> ter_a:is_alive(). yes, i‘m alive alive 4> ter_a:stop(normal). handle_cast, stop, Reason:normal terminate, Reason:normal ok 5> {ok, Pid1} = ter_a:start(1). {ok,<0.38.0>} 6> exit(Pid1, normal). handle_info, Info:{‘EXIT‘,<0.31.0>,normal} true 7> ter_a:is_alive(). yes, i‘m alive alive
c) Reason是kill,无论进程有没有捕捉退出,进程都会无条件退出,而且不会跑到terminate/2:
1> {ok, Pid0} = ter_a:start(0). {ok,<0.33.0>} 2> exit(Pid0, kill). true 3> ter_a:is_alive(). ** exception exit: {noproc,{gen_server,call,[ter_a,is_alive]}} in function gen_server:call/2 (gen_server.erl, line 182) 4> {ok, Pid1} = ter_a:start(1). {ok,<0.38.0>} 5> exit(Pid1, kill). true 6> ter_a:is_alive(). ** exception exit: {noproc,{gen_server,call,[ter_a,is_alive]}} in function gen_server:call/2 (gen_server.erl, line 182)
2. gen_server进程相互链接的情况(假如有进程A、B)
进程B的代码如下:
%%%-------------------------------------- %%% @Module : %%% @Author : %%% @Email : %%% @Created : %%% @Description: %%%-------------------------------------- -module(ter_b). -behaviour(gen_server). %% gen_server callbacks exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server api exports -export([start_link/1, is_alive/0]). -record(state, {}). %%==================================================================== %% API %%==================================================================== start_link(Flag) -> gen_server:start_link({local, ?MODULE}, ?MODULE, [Flag], []). is_alive() -> gen_server:call(?MODULE, is_alive). %%==================================================================== %% gen_server callbacks %%==================================================================== init([Flag]) -> case Flag of 0 -> skip; _ -> process_flag(trap_exit, true) end, {ok, #state{}}. handle_call(is_alive, _From, State) -> io:format("ter_b, i‘m alive~n"), {reply, alive, State}; handle_call(_Request, _From, State) -> io:format("ter_b, handle_call, _Request:~p~n", [_Request]), Reply = ok, {reply, Reply, State}. handle_cast(_Msg, State) -> io:format("ter_b, handle_cast, Msg:~p~n", [_Msg]), {noreply, State}. handle_info(_Info, State) -> io:format(ter_b, "handle_info, Msg:~p~n", [_Info]), {noreply, State}. terminate(_Reason, _State) -> io:format("ter_b, terminate~n"), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}.
1) B不捕捉退出,如果A异常退出,则B也会随之退出,A进程会跑到terminate/2而B进程不会;如果A正常退出,则B不做任何处理:
异常退出:
1> ter_a:start(0). {ok,<0.33.0>} 2> ter_a:link(0). handle_call, link to ter_b, LinkPid:<0.35.0> <0.35.0> 3> ter_a:mistake(). terminate, Reason:{badarith, [{ter_a,handle_cast,2,[{file,"ter_a.erl"},{line,63}]}, {gen_server,handle_msg,5, [{file,"gen_server.erl"},{line,599}]}, {proc_lib,init_p_do_apply,3, [{file,"proc_lib.erl"},{line,237}]}]} ok 4> =ERROR REPORT==== 28-Apr-2015::11:35:25 === ** Generic server ter_a terminating ** Last message in was {‘$gen_cast‘,mistake} ** When Server state == {state} ** Reason for termination == ** {badarith,[{ter_a,handle_cast,2,[{file,"ter_a.erl"},{line,63}]}, {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,599}]}, {proc_lib,init_p_do_apply,3, [{file,"proc_lib.erl"},{line,237}]}]} 4> ter_a:is_alive(). ** exception exit: {noproc,{gen_server,call,[ter_a,is_alive]}} in function gen_server:call/2 (gen_server.erl, line 182) 5> ter_b:is_alive(). ** exception exit: {noproc,{gen_server,call,[ter_b,is_alive]}} in function gen_server:call/2 (gen_server.erl, line 182)
正常退出:
1> ter_a:start(0). {ok,<0.33.0>} 2> ter_a:link(0). handle_call, link to ter_b, LinkPid:<0.35.0> <0.35.0> 3> ter_a:stop(normal). handle_cast, stop, Reason:normal terminate, Reason:normal ok 4> ter_a:is_alive(). ** exception exit: {noproc,{gen_server,call,[ter_a,is_alive]}} in function gen_server:call/2 (gen_server.erl, line 182) 5> ter_b:is_alive(). ter_b, i‘m alive alive
2) B捕捉退出,只要A退出,B都会随着退出,两个进程都会跑到terminate/2:
异常退出:
Eshell V6.2 (abort with ^G) 1> ter_a:start(0). {ok,<0.33.0>} 2> ter_a:link(1). handle_call, link to ter_b, LinkPid:<0.35.0> <0.35.0> 3> ter_a:mistake(). terminate, Reason:{badarith, [{ter_a,handle_cast,2,[{file,"ter_a.erl"},{line,63}]}, {gen_server,handle_msg,5, [{file,"gen_server.erl"},{line,599}]}, {proc_lib,init_p_do_apply,3, [{file,"proc_lib.erl"},{line,237}]}]} ter_b, terminate ok 4> =ERROR REPORT==== 28-Apr-2015::11:40:11 === ** Generic server ter_a terminating ** Last message in was {‘$gen_cast‘,mistake} ** When Server state == {state} ** Reason for termination == ** {badarith,[{ter_a,handle_cast,2,[{file,"ter_a.erl"},{line,63}]}, {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,599}]}, {proc_lib,init_p_do_apply,3, [{file,"proc_lib.erl"},{line,237}]}]} =ERROR REPORT==== 28-Apr-2015::11:40:11 === ** Generic server ter_b terminating ** Last message in was {‘EXIT‘,<0.33.0>, {badarith, [{ter_a,handle_cast,2, [{file,"ter_a.erl"},{line,63}]}, {gen_server,handle_msg,5, [{file,"gen_server.erl"},{line,599}]}, {proc_lib,init_p_do_apply,3, [{file,"proc_lib.erl"},{line,237}]}]}} ** When Server state == {state} ** Reason for termination == ** {badarith,[{ter_a,handle_cast,2,[{file,"ter_a.erl"},{line,63}]}, {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,599}]}, {proc_lib,init_p_do_apply,3, [{file,"proc_lib.erl"},{line,237}]}]} 4> ter_a:is_alive(). ** exception exit: {noproc,{gen_server,call,[ter_a,is_alive]}} in function gen_server:call/2 (gen_server.erl, line 182) 5> ter_b:is_alive(). ** exception exit: {noproc,{gen_server,call,[ter_b,is_alive]}} in function gen_server:call/2 (gen_server.erl, line 182)
正常退出:
1> ter_a:start(0). {ok,<0.33.0>} 2> ter_a:link(1). handle_call, link to ter_b, LinkPid:<0.35.0> <0.35.0> 3> ter_a:stop(normal). handle_cast, stop, Reason:normal terminate, Reason:normal ok ter_b, terminate
3. 一些结论和补充
1) 在gen_server中,进程结束时不是什么情况都会跑到terminat/2函数;
2) 如果有两个gen_server进程相互链接,需要让两个进程同时存在同时消亡(无论原因),并且在消亡的时候都保证要跑到terminate/2去,则需要给每个进程捕获退出消息process_flag(trap_exit, true);
3) 如果gen_server进程是supervision tree的一部分,并且由supervision去终止,只要符合以下条件terminate/2就会以shutdown作为Reason被调用
a) gen_server进程捕获退出;
b) 在supervision关于这个gen_server子策略中,Shutdown的值是一个整数,而非brutal_kill .