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 相關的內容,這些會單獨拉出來分析.html

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 函數.node

gen_server start

gen_server start 是由外部進程調用gen_server module 的start/start_link 函數,用以建立新的gen_server behavior 的進程. 在標準的Erlang application 中,通常是由supervisor 角色發起. start 的流程見下圖app

如圖中所示, 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 進程的初始化.ide

都成說,gen_server behavior 的user module init 函數儘量快的返回, 不要作任何阻塞性的操做.函數

在gen_server 的init_it 函數中, Mod:init 返回以後, 會調用proc_lib:init_ack/2, 用於向 start 的調用者返回結果oop

 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中作任何阻塞以及耗時性的操做.spa

可是,不少狀況下,在Mod:init 處理過程當中,是用於與外部資源(如:DB,MQ等)建立連接,而這些操做很難肯定其耗時性,咋辦?牛逼的Erlang大神 Ferd 在其 Erlang in Anger (國內有翻譯版:硝煙中的Erlang——Erlang 生產系統問題診斷、調試、解決指南) 中提供了一種方式hibernate

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: debug

 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/

相關文章
相關標籤/搜索