gen_server的enter_loop分析

在看ranch user guide的過程當中,發現實現protocol handler須要使用特殊的gen_server形式,也就是enter_loop函數調用,事例代碼以下: socket

-module(echo_protocol).
-behaviour(ranch_protocol).
 
-export([start_link/4]).
-export([init/4]).
 
start_link(ListenerPid, Socket, Transport, Opts) ->
    Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport, Opts]),
    {ok, Pid}.
 
init(ListenerPid, Socket, Transport, _Opts = []) ->
    ok = ranch:accept_ack(ListenerPid),
    loop(Socket, Transport).
 
loop(Socket, Transport) ->
    case Transport:recv(Socket, 0, 5000) of
        {ok, Data} ->
            Transport:send(Socket, Data),
            loop(Socket, Transport);
        _ ->
            ok = Transport:close(Socket)
    end.
實現ranch的protocol handler只須要實現start_link函數便可,函數中須要啓動一個新的進程,新的進程須要調用accept_ack函數來綁定socket的owner進程。

若是回調要實現gen_server行爲模式的話,Listener進程調用模塊的start_link方法,內部同步的啓動gen_server進程,而且等待gen_server進程調用init函數返回,若是這個時候在init的方法中調用accept_ack方法,就會形成循環調用,形成死鎖。 ide

ranch提供的方法以下,使用gen_server的enter_loop方法。 函數

-module(my_protocol).
-behaviour(gen_server).
-behaviour(ranch_protocol).
 
-export([start_link/4]).
-export([init/1]).
%% Exports of other gen_server callbacks here.
 
start_link(ListenerPid, Socket, Transport, Opts) ->
    proc_lib:start_link(?MODULE, [[ListenerPid, Socket, Transport, Opts]]).

init(ListenerPid, Socket, Transport, _Opts = []) ->
    ok = proc_lib:init_ack({ok, self()}),
    %% Perform any required state initialization here.
    ok = ranch:accept_ack(ListenerPid),
    ok = Transport:setopts(Socket, [{active, once}]),
    gen_server:enter_loop(?MODULE, [], {state, Socket, Transport}).

%% Other gen_server callbacks here.
一般狀況下啓動gen_server,須要調用gen_server:start_link(),參數中設置回調模塊,這樣的話像上面的分析,是會有死鎖的問題的。

規避這個問題就是使用enter_loop方法。這個方法能夠讓已經存在的獨立進程成爲gen_server進程,在進入普通的gen_server循環以前執行設定的邏輯。 oop

Listener進程調用proc_lib:start_link方法,建立新進程A,A執行init方法,調用proc_lib:init_ack()方法,告訴listener進程A進程已經啓動,此時Listener進程就能夠返回了。而後A進程再執行accept_ack()方法,最後調用enter_loop方法,讓本身進入gen_server的執行循環。 ui

相關文章
相關標籤/搜索