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

  • 相关阅读:
    正经学C#_循环[do while,while,for]:[c#入门经典]
    Vs 控件错位 右侧资源管理器文件夹点击也不管用,显示异常
    asp.net core 获取当前请求的url
    在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be
    用orchard core和asp.net core 3.0 快速搭建博客,解决iis 部署https无法登录后台问题
    System.Data.Entity.Core.EntityCommandExecution The data reader is incompatible with the specified
    初探Java设计模式3:行为型模式(策略,观察者等)
    MySQL教程77-CROSS JOIN 交叉连接
    MySQL教程76-HAVING 过滤分组
    MySQL教程75-使用GROUP BY分组查询
  • 原文地址:https://www.cnblogs.com/--00/p/4272549.html
Copyright © 2011-2022 走看看