zoukankan      html  css  js  c++  java
  • Erlang generic standard behaviours -- gen_server terminate

    gen_server 主体 module 已经分析完了(http://www.cnblogs.com/--00/p/4271982.html),接着,分析下gen_server 中的terminate .首先分析一个问题, 这个问题是之前在weibo 上和别人讨论过的一个问题: Why will a rpc:call started gen_server process terminate with normal reason?

    注:被call 的gen_server 进程 trap_exit 为true .

    rpc call

    首先, 得先去看看rpc call 是怎样的行为流程.

    1 call(N,M,F,A,infinity) when node() =:= N ->  %% Optimize local call
    2     local_call(M,F,A);
    3 call(N,M,F,A,infinity) ->
    4     do_call(N, {call,M,F,A,group_leader()}, infinity);
    5 call(N,M,F,A,Timeout) when is_integer(Timeout), Timeout >= 0 ->
    6     do_call(N, {call,M,F,A,group_leader()}, Timeout).

    以上, 会对local node 的call 进行optimize 处理, 对于remote node 来说, 继续调用do_call 函数

     1 do_call(Node, Request, infinity) ->
     2     rpc_check(catch gen_server:call({?NAME,Node}, Request, infinity));
     3 do_call(Node, Request, Timeout) ->
     4     Tag = make_ref(),
     5     {Receiver,Mref} =
     6         erlang:spawn_monitor(
     7           fun() ->
     8                   %% Middleman process. Should be unsensitive to regular
     9                   %% exit signals.
    10                   process_flag(trap_exit, true),
    11                   Result = gen_server:call({?NAME,Node}, Request, Timeout),
    12                   exit({self(),Tag,Result})
    13           end),
    14     receive
    15         {'DOWN',Mref,_,_,{Receiver,Tag,Result}} ->
    16             rpc_check(Result);
    17         {'DOWN',Mref,_,_,Reason} ->
    18             %% The middleman code failed. Or someone did 
    19             %% exit(_, kill) on the middleman process => Reason==killed
    20             rpc_check_t({'EXIT',Reason})
    21     end.

    do_call 的时候会对Timeout 为infinity 做直接调用gen_server:call 处理;而对于Timeout 不为infinity的情况,使用Middleware process(和gen_server module 中的multi_call 相似),不论gen_server:call 调用(L11)的返回结果是什么,Middleware process 都会使用exit(L12).

    在此,如果rpc:call 的执行参数是 TargetNode,GenServerMod, start_link, Args, Middleware process 就是GenServerMod 进程的Parent,而在调用结束之后,Middleware process exit了.至此,得到的信息:

    1, 调用GenServerMod:start_link(Args) 的进程会在执行结束后exit;

    2, 调用进程是GenServerMod的Parent

    gen_server terminate

    首先,需要对terminate 进行区分, 一个是GenServerMod 的callback 方法terminate, 一个是gen_server module中的函数, 此处主要是对gen_server module 中的terminate 函数进行分析.

    然后,检索gen_server module 代码中, 哪些地方哪些情况下调用了terminate 函数:

    1, define terminate func

     1 %%% ---------------------------------------------------
     2 %%% Terminate the server.
     3 %%% ---------------------------------------------------
     4 
     5 -spec terminate(_, _, _, _, _, _) -> no_return().
     6 terminate(Reason, Name, Msg, Mod, State, Debug) ->
     7     terminate(Reason, Reason, Name, Msg, Mod, State, Debug).
     8 
     9 -spec terminate(_, _, _, _, _, _, _) -> no_return().
    10 terminate(ExitReason, ReportReason, Name, Msg, Mod, State, Debug) ->
    11     Reply = try_terminate(Mod, ExitReason, State),
    12     case Reply of
    13       {'EXIT', ExitReason1, ReportReason1} ->
    14           FmtState = format_status(terminate, Mod, get(), State),
    15           error_info(ReportReason1, Name, Msg, FmtState, Debug),
    16           exit(ExitReason1);
    17       _ ->
    18           case ExitReason of
    19             normal ->
    20                 exit(normal);
    21             shutdown ->
    22                 exit(shutdown);
    23             {shutdown,_}=Shutdown ->
    24                 exit(Shutdown);
    25             _ ->
    26                 FmtState = format_status(terminate, Mod, get(), State),
    27                 error_info(ReportReason, Name, Msg, FmtState, Debug),
    28                 exit(ExitReason)
    29           end
    30     end.

    terminate func 会先调用try_terminate(即GenServerMod:terminate),然后report 错误信息,最后exit .

    2, handle_msg 调用terminate

    handle_msg 函数中,当遇到'EXIT' 退出,GenServerMod callback 方法返回'stop'时,都会调用terminate函数. 如:

    1     {ok, {stop, Reason, NState}} ->
    2         terminate(Reason, Name, Msg, Mod, NState, []);
    3     {'EXIT', ExitReason, ReportReason} ->
    4         terminate(ExitReason, ReportReason, Name, Msg, Mod, State, []);

    3, receive Parent 'EXIT' message

     1 decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) ->
     2     case Msg of
     3         {system, From, Req} ->
     4             sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
     5                                   [Name, State, Mod, Time], Hib);
     6         {'EXIT', Parent, Reason} ->
     7             terminate(Reason, Name, Msg, Mod, State, Debug);
     8         _Msg when Debug =:= [] ->
     9             handle_msg(Msg, Parent, Name, State, Mod);
    10         _Msg ->
    11             Debug1 = sys:handle_debug(Debug, fun print_event/3,
    12                                       Name, {in, Msg}),
    13             handle_msg(Msg, Parent, Name, State, Mod, Debug1)
    14     end.

    在L6,当收到Parent 'EXIT' 的消息时, gen_server module 会调用terminate 函数, 使GenServerMod 以相同于 Parent 的Reason 退出.

    以上两种情况,gen_server module 都会调用terminate 函数, 继而回调GenServerMod 的terminate callback, 最后使GenServerMod 进程退出.

    但是,为什么trap_exit 为false 或者是 start 的时候就不会发生此种情况? (因为进程不会收到L6处的消息)

    link VS monitor

    既然都已经说到了trap_exit了,而且之前的gen_server module 和 gen 分析中,都看到了大量的start_link和erlang:monitor,就简单说下Erlang 中link 和monitor 的却别.

    对于一个进程died 这件事, link 和monitor 是两种不同的通知方式. 当使用link 时, 某进程died 之后, 其他与之link 的进程会收到  Exit Signals , 而使用monitor时, 某进程died之后, monitor 它的进程会收到 message 而不是 signals.

    回到最开始的问题

    这样的话, 这个问题的原因就很清楚了.

    how to fix?

    1, 使用start 代替 start_link ,并在 spawn_opts 的参数中添加'monitor' option;

    2, 直接修改gen_server module 的decode_msg 函数, 去掉{'EXIT', Parent, Reason} branch 的处理. (好像有点粗鲁^^)

    参考

    http://marcelog.github.io/articles/erlang_link_vs_monitor_difference.html

  • 相关阅读:
    [每天解决一问题系列
    [每天解决一问题系列
    [每天解决一问题系列
    nodejs&mongo&angularjs
    [转]Express框架
    [转]Use HandleBars in Express
    10 Tips for Optimizing Your Website’s Speed
    One difference between AngularJS' $location and window.location
    Why does Http header contains "X-SourceFiles"?
    JavaScript数组常用方法
  • 原文地址:https://www.cnblogs.com/--00/p/4272549.html
Copyright © 2011-2022 走看看