zoukankan      html  css  js  c++  java
  • [erlang 002]gen_server中何时会跑到terminate函数

    一、从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)


    二、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


    三、一些结论和补充

    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 .

    (完)

    本文版权归作者(https://www.cnblogs.com/harrymore/)和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,如有问题, 可邮件(harrymore@126.com)咨询.
  • 相关阅读:
    吐槽接口文档(二)——什么是优秀的接口文档
    吐槽接口文档(一)——什么是合格的接口文档
    Java 串口通讯库
    Istio快速入门
    Neo4j删除节点和关系、彻底删除节点标签名
    [Neo4j] 在neo4j中批量创建节点和关系
    neo4j︱Cypher完整案例csv导入、关系联通、高级查询(三)
    neo4j︱Cypher 查询语言简单案例(二)
    neo4j︱图数据库基本概念、操作罗列与整理(一)
    neo4j设置节点或者边的显示,包括颜色、属性、大小
  • 原文地址:https://www.cnblogs.com/harrymore/p/4462669.html
Copyright © 2011-2022 走看看