[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, 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 .

时间: 2024-10-13 00:09:11

[erlang 002]gen_server中何时会跑到terminate函数的相关文章

Erlang网络编程中的一个特别的函数prim_inet:async_accept/2(转)

为了研究怎么用Erlang写一个游戏服务器,我很幸运的下到了一份英雄远征的服务器Erlang源码,这两天花了点时间看代码,其中看到做TCP的accept动作时,它是用的一个函数prim_inet:async_accept/2,这个可跟书上说的不一样(一般来说书上教的是用gen_tcp:accept/1),于是我google了一下,发现找不到文档,再翻一下发现已经有不少人问为什么这是一个undocumented的函数,也就是说Erlang就没想让你去用这个函数,所以文档自然没提供.一般来说undo

Eclipse中让XGSDKCocos2d_Demo跑起来

进金山的第二个需要记录的任务就是在Eclipse中让XGSDKCocos2d_Demo跑起来,XGSDKCocos2d_Demo是前辈们已经写好了的一个简单的cocos2d游戏(简单到只有menu)并且已经在游戏的代码里,接入了XGSDK所必须的一些接口(登陆,登出,支付,用户中心,切换账号,退出).Demo的其中一个作用是测试上帝渠道(所谓的测试母包)的接入,上帝渠道是一个人工渠道SDK,用于提供给游戏开发人员验证其是否接入XGSDK所要求接入的接口.我的任务就是完善上帝渠道,从中我学到了XG

Erlang的gen_server的terminate()/2未执行

官方资料参考: Module:terminate(Reason, State) Types: Reason = normal | shutdown | {shutdown,term()} | term() State = term() This function is called by a gen_server when it is about to terminate. It should be the opposite of Module:init/1 and do any necessa

专访探探DBA张文升:PG在互联网应用中同样也跑的很欢畅

张文升认为,PG无论在可靠性和性能方面都不输其它任何关系型数据库 张文升,探探DBA,负责探探的数据库架构.运维和调优的工作.拥有8年开发经验,曾任去哪儿网DBA. 9月24日,张文升将参加在北京举办的线下活动,分享PostgreSQL在互联网应用的一些经验.值此,他分享了个人的一些经历,以及对PG的一些看法. 想和这些大咖面对面聊PG吗?点击这里>>>免费报名 正文: 初接触PG,“What?什么是PG?”一脸懵圈——张文升用时下比较流行的一个词儿形容当时的心情,而且他们的Team也是

erlang取列表中某个值的位置

有个需求,比如在一个列表中,取出一个元素的位置,如果出现重复都取出.例如:List = [2,3,10,324,88,29,12],可以求大于某个值的位置,也可以取某个值的位置. 废话少说,直接上代码: %%测试用例 enter() -> A = [true,false,true,false,true,false,true,true], %A = [10,11,20,3,9.2,8.23,10.4,9.2], N = lists:foldr(fun(X,Y) -> case lists:nth

中琅领跑条码标签打印软件简单使用说明

作为一款专业的条码标签打印软件,中琅领跑条码标签打印软件能帮助您轻松灵活地设计和打印条码和标签.通过本条码标签打印工具,您可以轻松地整合条码标签打印,无线射频识别(RFID)以及相关业务流程,不断增强产品及业务可控性的同时大大提高生产管理效率.本篇文章就将从刚接触条码标签设计软件的新手的角度出发,介绍一下软件的基本使用方法. 软件的安装 软件安装在中琅领跑条码标签打印软件的官网有详细的介绍,这里就不多累述,具体请参考: 中琅条码打印软件安装教程 软件的使用 按照上述教程安装完毕后,已经购买了软件

中琅领跑标签条码打印软件如何连接sql server数据库

使用中琅领跑标签条码打印软件制作商品条码过程中,如果数据储存在excel表或文本文档中时,根据之前教程,我们可以轻松连接使用其中的数据.但如果数据是存储在远方主机或数据库中,我们如何获取使用呢?这里小编就简单介绍下中琅领跑标签条码打印软件数据库连接的基本操作步骤,以sql server 2000为例,其他数据库如mysql,oracle等连接步骤大致相同.首先,无论是您的数据库是安装在本机还是远方服务器上,请确保sql server是可以连接的.需要注意的是,针对sql server 2000的

中琅领跑条码打印软件打开时找不到Sentinel锁的解决方案

在使用中琅领跑条码打印软件的过程中有些朋友可能会遇到这样的问题:昨天用着好好的,今天一打开缺报错了,错误信息为"未找到Sentinel锁,请联系官网www.ew8.cn寻求帮助!(H0007)".为什么会出现这个情况呢?这是因为条码打印软件运行所需要的一个服务(Sentinel LDK license Manager)没有启动或找不到了,而出现这种情况的原因有很多,其中一个是您可能没有设置该服务自动启动.找到了原因就很简单了,遇到这种问题时,我们只需要在本地服务中启动这个服务就可以了.

中琅领跑条码打印软件如何导入CDR文件

在使用中琅领跑条码打印软件过程中,很多时候我们可能会需要导入一些高保真的矢量图.最常见的就是CDR文件了,当我们打开软件导入文件的时候,会发现软件内好像没有导入CDR文件这个选项,这个时候怎么办呢?其实也很简单的,具体的思路就是把CDR文件转化成pdf输出,然后通过矢量图导入就可以了.具体操作如下: 首先,需要先把我们的CDR文件转换成pdf,如何操作大家可以具体百度一下,很多这样的教程,这里小编使用的是coreldraw,直接把制作好的CDR文件,使用pdf导出即可.具体操作这里简单的说下,首