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

    在分析完gen module (http://www.cnblogs.com/--00/p/4271386.html)之后,就可以开始进入gen_server 的主体module 了.gen_server 的主体 module 暂不涵括terminate, hibernate, debug trace 相关的内容,这些会单独拉出来分析.

    gen_server 主要包括start 初始化部分, MAIN loop. 其中MAIN loop 为gen_server的主体结构, 其中,处理Label 为'$gen_call' (也就是handle_call)的消息使用handle_msg 处理, Label为'$gen_cast'(也就是handle_cast)的消息以及无Label(handle_info)的消息经由handle_msg最终交给try_dispatch 函数.

    gen_server start

    gen_server start 是由外部进程调用gen_server module 的start/start_link 函数,用以创建新的gen_server behavior 的进程. 在标准的Erlang application 中,一般是由supervisor 角色发起. start 的流程见下图

    如图中所示, ProcessA 是gen_server start 的调用者, 通过user module 的start/start_link 函数, 调用gen_server module 中的start, 继而调用gen module 中的start. 在gen module中, 由proc_lib:spawn 创建新的进程(ProcessB), 并以此调用init_it(gen); init_it(gen_server) ; init(user module) 完成gen_server behavior 进程的初始化.

    都成说,gen_server behavior 的user module init 函数尽可能快的返回, 不要做任何阻塞性的操作.

    在gen_server 的init_it 函数中, Mod:init 返回之后, 会调用proc_lib:init_ack/2, 用于向 start 的调用者返回结果

     1 init_it(Starter, self, Name, Mod, Args, Options) ->
     2     init_it(Starter, self(), Name, Mod, Args, Options); %% 注意, 如果使用nolink start 时, Parent 就是自己
     3 init_it(Starter, Parent, Name0, Mod, Args, Options) ->
     4     Name = name(Name0),
     5     Debug = debug_options(Name, Options),
     6     case catch Mod:init(Args) of
     7     {ok, State} ->
     8         proc_lib:init_ack(Starter, {ok, self()}),    %% 向调用者返回结果    
     9         loop(Parent, Name, State, Mod, infinity, Debug);
    10     {ok, State, Timeout} ->
    11         proc_lib:init_ack(Starter, {ok, self()}),         
    12         loop(Parent, Name, State, Mod, Timeout, Debug);
    13         …………

    所以,尽可能快的返回,不在Mod:init中做任何阻塞以及耗时性的操作.

    但是,很多情况下,在Mod:init 处理过程中,是用于与外部资源(如:DB,MQ等)创建链接,而这些操作很难确定其耗时性,咋办?牛逼的Erlang大神 Ferd 在其 Erlang in Anger (国内有翻译版:硝烟中的Erlang——Erlang 生产系统问题诊断、调试、解决指南) 中提供了一种方式

    The following code attempts to guarantee a connection as part of the process’ state: 

     1 init(Args) ->
     2     Opts = parse_args(Args),
     3     {ok, Port} = connect(Opts),   %% 这种在init 函数中执行connect的方式不可取        
     4     {ok, #state{sock=Port, opts=Opts}}.
     5 [...]
     6 handle_info(reconnect, S = #state{sock=undefined, opts=Opts}) ->
     7     %% try reconnecting in a loop
     8     case connect(Opts) of
     9         {ok, New} -> {noreply, S#state{sock=New}};
    10          _ -> self() ! reconnect, {noreply, S}
    11 end;

    Instead, consider rewriting it as: 

     1 init(Args) ->
     2     Opts = parse_args(Args),
     3     %% you could try connecting here anyway, for a best
     4     %% effort thing, but be ready to not have a connection.
     5     self() ! reconnect,   %% 给self 发送消息,替代在init 时connect的耗时/阻塞操作
     6     {ok, #state{sock=undefined, opts=Opts}}.
     7 [...]
     8 handle_info(reconnect, S = #state{sock=undefined, opts=Opts}) ->
     9     %% try reconnecting in a loop
    10     case connect(Opts) of
    11         {ok, New} -> {noreply, S#state{sock=New}};
    12         _ -> self() ! reconnect, {noreply, S}
    13     end;

    注意: 第一种方式不可取.

    gen_server MAIN loop

    使用proc_lib:init_ack 之后, gen_server init_it 会调用loop 进入gen_server 的MAIN loop 流程中. MAIN loop 使用receive 用以接收'$gen_call', '$gen_cast' 以及其他的message, 紧接着交由 decode_msg 函数进行处理.

     1 %%% ---------------------------------------------------
     2 %%% The MAIN loop.
     3 %%% ---------------------------------------------------
     4 loop(Parent, Name, State, Mod, hibernate, Debug) ->
     5     %% 这个坑在说hibernate 的时候再填
     6     proc_lib:hibernate(?MODULE,wake_hib,[Parent, Name, State, Mod, Debug]);
     7 loop(Parent, Name, State, Mod, Time, Debug) ->
     8     Msg = receive
     9            Input ->
    10                 Input
    11          after Time ->
    12                timeout
    13          end,
    14     decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, false).
    15 
    16 wake_hib(Parent, Name, State, Mod, Debug) ->
    17     Msg = receive
    18           Input ->
    19             Input
    20       end,
    21     decode_msg(Msg, Parent, Name, State, Mod, hibernate, Debug, true).
    22 
    23 decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) ->
    24     case Msg of
    25         %% 这个坑在说sys trace/get_status 的时候填
    26       {system, From, Req} ->
    27           sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
    28                     [Name, State, Mod, Time], Hib);
    29     %% 这个坑在说 terminate 的时候填
    30       {'EXIT', Parent, Reason} ->
    31           terminate(Reason, Name, Msg, Mod, State, Debug);
    32       _Msg when Debug =:= [] ->
    33           handle_msg(Msg, Parent, Name, State, Mod);
    34       _Msg ->
    35           Debug1 = sys:handle_debug(Debug, fun print_event/3,
    36                         Name, {in, Msg}),
    37           handle_msg(Msg, Parent, Name, State, Mod, Debug1)
    38     end.

    在上面的代码片段中, L8 正是receive self 或外部进程的message, L23 是decode_msg 函数的入口, 在L33和L37 处调用handle_msg 函数进一步对msg消息进行处理.代码比较简单,没必要一行一行分析了.

     gen_server multi_call

    call 在上一篇blog中已经提到, cast以及abcast 的实质就是调用erlang:send bif, 最终调用erts beam 下的dist.c .

    multi_call 牵扯到多node , Erlang stdlib 中pg2 module 就主要使用multi_call 同步各自node 上ets 表的信息. 如:

     1 create(Name) ->
     2     _ = ensure_started(),
     3     case ets:member(pg2_table, {group, Name}) of
     4         false ->
     5             global:trans({{?MODULE, Name}, self()},
     6                          fun() ->
     7                                  gen_server:multi_call(?MODULE, {create, Name})
     8                          end),
     9             ok;
    10         true ->
    11             ok
    12     end.

    multi_call 调用 do_multi_call 函数, do_multi_call 使用Middleman process . Middleman process 负责给各node 发送 Label 为 '$gen_call' 的消息并等待各node 的结果返回. 

    1 %% Middleman process. Should be unsensitive to regular
    2 %% exit signals. The sychronization is needed in case
    3 %% the receiver would exit before the caller started
    4 %% the monitor.

    最终, 通过exit 的方式返回给主调用进程, 而主调用进程会通过monitor/receive {'DOWN' ...} 的方式接收结果.

    注意: Middleman process 需要monitor 目标node, 如果nodedown, 即会采取 call 失败的流程进行处理. 

    参考: http://www.erlang-in-anger.com/

  • 相关阅读:
    开启Nginx代理HTTPS功能
    Linux查找运行程序主目录
    Linux命令记录
    Eclipse 安装 阿里P3C编码规范插件
    Elasticsearch(ES)(版本7.x)数据更新后刷新策略RefreshPolicy
    JS小技巧
    改变窗口或屏幕大小时调用function
    毛玻璃效果 | fifter
    position: sticky;不一样失效原因
    mysql 修改密码问题 5.6,5.7 (配置方式的skip-grant-tables可能不行,推荐命令行方式)
  • 原文地址:https://www.cnblogs.com/--00/p/4271982.html
Copyright © 2011-2022 走看看