[erlang 002]gen_server中何时会跑到terminate函数

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, Reason, State}或者{stop, Reason, 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 .

你可能感兴趣的:(erlang)