Erlang:gen_server

时间:2015-12-18 06:24:50

标签: erlang otp

假设我有一个gen_server回调模块,g,代码片段如下所示:

start_link(Args) ->
    gen_server:start_link(?MODULE, [Args], []).

process_packet(Ref, Packet) ->
    gen_server:call(Ref, MsgPacket={process_packet, Packet}).

init(Args) ->
    gen_server:cast(self(), MsgInit={init, Args}), %% delayed initialization
    {ok, state_not_initialized}.

handle_call({process_packet, Packet}, #g_state{}=S) ->
    {reply, Packet, S}.

handle_cast({init, Args}, _) ->
    State = #g_state{} = do_init(Args),
    {noreply, State}.

和另一个gen_server,t,其工作是收听套接字, 如果收到特定的数据包,请启动g以对数据包执行某些操作, 所以,t中的一些代码看起来像这样:

handle_info({tcp, _Socket, Packet}, #t_state{}) ->
    case g:start_link(WhatEver) of
        {ok, Pid} ->
            g:process_packet(Pid, Packet);
        _ ->
            not_interested
    end.

g的pid为PidGt的pid为PidT

我的问题是,MsgPacket(由PidT发送到PidG)是否有可能在PidG之前到达MsgInit(由{{1}发送自己)?如果发生这种情况,PidG会崩溃,因为PidGstate_not_initialized #g_state{} g中的handle_call不匹配。

我的猜测是完全有可能,但我没有想出一种方法来产生这种情况。理想情况下,您可以减慢消息传输速度MsgInit,但我怀疑Erlang允许我做这种事情。知道如何让MsgPacketMsgInit之前到达吗?

修复相对容易,(假设我的猜测是正确的),receive ack PidG do_init在{{ 1}}在PidT启动之后,在进行gen调用之前。

更新

假设我的猜测是正确的,为了使问题更具体,如何使g崩溃启动其中一个进程? (根据zxq9'示例修改)

kickoff_many/1

2 个答案:

答案 0 :(得分:0)

是的,有可能。您可以知道相对于进程A和B的消息序列,但是您无法知道相对于任何其他两个进程 A和B的消息序列(意思是,您可以& “知道各种消息流将被交错的顺序”,这也意味着你不知道从A到B,从B到B的消息的相对顺序。

如何避免这种情况?最直接的方法是让你的进程T 生成进程G,如果我还不存在。或者总是生成G来处理处理作业。或者处理本身的处理。或者使套接字监听器不是gen_server ,而是一个纯粹的Erlang OTP进程(查找proc_lib - 我发现套接字监听器更加顺畅)和gen_server世界之间的奇怪冲突并且可以使初始化套接字处理程序进程的某些方面的必要性消失。

总结一下:

  • 您认为存在的排序问题确实存在。在实践中,它几乎不会发生 - 当然,直到生产中最不合时宜的时间。
  • 套接字处理程序除了处理套接字外什么都不做。
  • "处理插座"意味着在Erlang消息和网络消息之间进行转换(如果它的TCP 它们不是数据包它是一个流 - 要小心),并调度到内部gen_ *进程。
  • 您的初始化问题可以解决,但最好完全消除这种需求。几乎总有一种方法可以做到这一点 - 如果没有,则有proc_lib

<强>更新

如果您没有刷新邮箱并且选择接收搜索您神奇的第一条消息,可以发生。

这里我们有一个垃圾邮件流程,它会向即将推出的gen_server发送一条bajillion消息:

-module(spawn_spammer).
-export([kickoff/0]).

kickoff() ->
    Spammer = start(),
    Catcher = spawn_catcher:start(),
    {Spammer, Catcher}.

start() ->
    ok = io:format("~tp ~tp: Starting up.~n", [self(), ?MODULE]),
    spawn(fun() -> loop() end).

loop() ->
    try
        spawn_catcher ! {self(), test_spam}
    catch
        _:_ -> io:format("~tp ~tp: Missed.~n", [self(), ?MODULE])
    end,
    receive
        cut_it_out ->
            ok;
        Unexpected ->
            io:format("~tp ~tp: Unexpected message ~tp~n", [self(), ?MODULE, Unexpected]),
            loop()
        after 0 ->
            loop()
    end.

这里我们有gen_server,尝试用自己进行(假设)安全的延迟初始化:

-module(spawn_catcher).
-behavior(gen_server).
-export([start/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

start() ->
    ok = io:format("~tp ~tp: Starting up.~n", [self(), ?MODULE]),
    gen_server:start({local, ?MODULE}, ?MODULE, [], []).

init(_) ->
    ok = io:format("~tp ~tp: Blank initialization.~n", [self(), ?MODULE]),
    gen_server:cast(self(), get_ready),
    {ok, not_ready}.

handle_call(Message, From, State) ->
    ok = io:format("~tp ~tp: Unexpected call: ~tp from ~tp~n", [self(), ?MODULE, Message, From]),
    {noreply, State}.

handle_cast(get_ready, not_ready) ->
    ok = io:format("~tp ~tp: Getting ready~n", [self(), ?MODULE]),
    {noreply, ready};
handle_cast(Message, State) ->
    ok = io:format("~tp ~tp: Unexpected call: ~tp~n", [self(), ?MODULE, Message]),
    {noreply, State}.

handle_info(Message, not_ready) ->
    ok = io:format("~tp ~tp: DANGEROUS MESSAGE: ~tp~n", [self(), ?MODULE, Message]),
    {noreply, not_ready};
handle_info({Spammer, test_spam}, ready) ->
    ok = io:format("~tp ~tp: Got first proper message. Sending reply.~n", [self(), ?MODULE]),
    Spammer ! cut_it_out,
    {stop, normal, ready};
handle_info(Message, ready) ->
    ok = io:format("~tp ~tp: Unexpected message: ~tp~n", [self(), ?MODULE, Message]),
    {noreply, ready}.

terminate(_, _) -> ok.

code_change(_, State, _) -> {ok, State}.

以下是shell中的表现:

1> spawn_spammer:kickoff().
<0.33.0> spawn_spammer: Starting up.
<0.33.0> spawn_catcher: Starting up.
<0.99.0> spawn_spammer: Missed.
<0.99.0> spawn_spammer: Missed.
<0.100.0> spawn_catcher: Blank initialization.
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
{<0.99.0>,{ok,<0.100.0>}}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: Getting ready
<0.100.0> spawn_catcher: Got first proper message. Sending reply.

这非常不可能可以发生。

答案 1 :(得分:0)

简短回答。不,这是不可能的。

这种技术有时称为延迟初始化,经常使用。

答案很长。不,如果您使用gen_server而不使用选择性接收,则无法实现。

来自gen_server docs:

  

gen_server进程调用Module:init / 1进行初始化。为确保同步启动过程,在Module:init / 1返回之前,start_link / 3,4不会返回。

你应该考虑的事情如下。

当g:init / 1正在运行时,谁知道PidG?只有PidG过程本身。启动PidG的进程只有在PidG从g:init / 1返回后才会从gen_server:start / x返回。

确定后,我们知道{init,Args}是PidG消息队列中的第一条消息。由于我们正在使用gen_server(通常)逐个处理消息,因此您可以确保PidG也将处理消息{init,Args}作为第一个消息。

此时PidG和PidT之间没有互动。

编辑:删除有关返回{ok,State,0}和接收'timeout'的提及,因为它会因为创建竞争条件而失败(请参阅gen_server.erl)。只有一种安全的方式:self()!如果您没有在Module:init / x。

中发送pid,请执行以下操作