Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/elixir/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Erlang Elixir:Genserver.call未初始化句柄\调用_Erlang_Elixir_Otp_Gen Server_Gossip - Fatal编程技术网

Erlang Elixir:Genserver.call未初始化句柄\调用

Erlang Elixir:Genserver.call未初始化句柄\调用,erlang,elixir,otp,gen-server,gossip,Erlang,Elixir,Otp,Gen Server,Gossip,我正在实现八卦算法,其中多个参与者同时并行传播八卦。当每个演员都听了10次流言时,系统停止 现在,我有一个场景,在发送流言蜚语之前,我检查接收者演员的收听次数。如果侦听计数已为10,则不会将八卦发送给收件人演员。我使用同步调用来获取侦听计数 def get_message(server, msg) do GenServer.call(server, {:get_message, msg}) end def handle_call({:get_message, msg}, _from,

我正在实现
八卦算法
,其中多个参与者同时并行传播八卦。当每个演员都听了10次流言时,系统停止

现在,我有一个场景,在发送流言蜚语之前,我检查接收者演员的收听次数。如果侦听计数已为10,则不会将八卦发送给收件人演员。我使用同步调用来获取侦听计数

def get_message(server, msg) do
    GenServer.call(server, {:get_message, msg})
end

def handle_call({:get_message, msg}, _from, state) do
    listen_count = hd(state) 
    {:reply, listen_count, state}
end
该程序在启动时运行良好,但一段时间后,
Genserver.call
停止,出现如下超时错误。经过一些调试,我意识到
Genserver.call
处于休眠状态,无法启动相应的
handle\u call
方法。使用同步调用时是否会出现这种行为?既然所有参与者都是独立的,那么
Genserver.call
方法不应该在不等待彼此响应的情况下独立运行吗

02:28:05.634 [error] GenServer #PID<0.81.0> terminating
    ** (stop) exited in: GenServer.call(#PID<0.79.0>, {:get_message, []}, 5000)
    ** (EXIT) time out
    (elixir) lib/gen_server.ex:774: GenServer.call/3
打开iex外壳并将其加载到模块上方。使用以下命令启动两个进程:

a = RumourActor.start_link(["", 3])
b = RumourActor.start_link(["", 5])
通过调用Dogbert在注释中提到的死锁条件产生错误。在没有太大时差的情况下运行以下命令

cb = RumourActor.set_message(elem(a,0), [], elem(b,0))
ca = RumourActor.set_message(elem(b,0), [], elem(a,0))

等待5秒钟。将出现错误。

八卦协议是一种处理异步、未知、未配置(随机)网络的方法,这些网络可能会出现间歇性中断和分区,并且不存在前导或默认结构。(请注意,这种情况在现实世界中有些不寻常,带外控制总是以某种方式强加给系统。)

考虑到这一点,让我们将其更改为一个异步系统(使用
cast
),以便我们遵循闲聊式通信概念的精神

我们需要计算给定消息接收次数的消息摘要、已接收且已超过神奇数字的消息摘要(因此,如果太晚,我们不会重新发送消息),以及系统中注册的进程列表,以便我们知道向谁广播:

(下面的例子是在Erlang中,因为自从我停止使用Elixir语法后,我就被它绊倒了…)

这里我用的是a,但它可以是任何东西。对于一个测试用例来说是不错的,但是由于流言在Erlang集群中没有用处,并且引用在原始系统之外是不安全的,所以我只想假设这是针对网络系统的

我们需要一个接口函数,它允许我们告诉进程将新消息注入系统。我们还需要一个接口函数,一旦消息已经在系统中,它就可以在两个进程之间发送消息。然后,我们需要一个内部函数,将消息广播到所有已知(订阅的)对等方。啊,这意味着我们需要一个问候界面,以便对等进程可以相互通知它们的存在

我们还需要一种方法,让一个过程告诉自己,随着时间的推移保持广播。设置重传间隔的时间实际上不是一个简单的决定——它与网络拓扑、延迟、可变性等有关(实际上,您可能会偶尔ping对等点,并根据延迟开发一些启发式方法,删除似乎没有响应的对等点,等等——但我们不会陷入这种疯狂状态)。这里我只将其设置为1秒,因为对于观察系统的人来说,这是一个易于解释的间隔

请注意,下面的所有内容都是异步的

接口

insert(Pid, Message) ->
    gen_server:cast(Pid, {insert, Message}).

relay(Pid, ID, Message) ->
    gen_server:cast(Pid, {relay, ID, Message}).

greet(Pid) ->
    gen_server:cast(Pid, {greet, self()}).

make_introduction(Pid, PeerPid) ->
    gen_server:cast(Pid, {make_introduction, PeerPid}).
最后一个功能将是我们作为系统测试人员的方式,使其中一个进程在某个目标Pid上调用
greet/1
,从而开始构建对等网络。在现实世界中,通常会发生一些稍微不同的事情

在我们的gen_服务器回调中,我们将获得:

handle_cast({insert, Message}, State) ->
    NewState = do_insert(Message, State);
    {noreply, NewState};
handle_cast({relay, ID, Message}, State) ->
    NewState = do_relay(ID, Message, State),
    {noreply, NewState};
handle_cast({greet, Peer}, State) ->
    NewState = do_greet(Peer, State),
    {noreply, NewState};
handle_cast({make_introduction, Peer}, State) ->
    NewState = do_make_introduction(Peer, State),
    {noreply, NewState}.
非常简单的东西

上面我提到,我们需要一种方法让这个东西在延迟后重新发送。为此,我们将在延迟后使用
erlang:send\u after/3
向“redo\u relay”发送一条裸消息,因此我们需要一个句柄\u info/2来处理它:

handle_info({redo_relay, ID, Message}, State) ->
    NewState = do_relay(ID, Message, State),
    {noreply, NewState}.
消息位的实现是有趣的部分,但这一切都不是非常棘手的。请原谅下面的
do_relay/3
——它可能更简洁,但我是在头顶的浏览器中写的,所以

do_insert(Message, State = #s{peers = Peers, digest = Digest}) ->
    MessageID = zuuid:v1(),
    NewDigest = maps:put(MessageID, 1, Digest),
    ok = broadcast(Message, Peers),
    ok = schedule_resend(MessageID, Message),
    State#s{digest = NewDigest}.

do_relay(ID,
         Message,
         State = #s{peers = Peers, digest = Digest, dead = Dead}) ->
    case maps:find(ID, Digest) of
        {ok, Count} when Count >= 10 ->
            NewDigest = maps:remove(ID, Digest),
            NewDead = sets:add_element(ID, Dead),
            ok = broadcast(Message, Peers),
            State#s{digest = NewDigest, dead = NewDead};
        {ok, Count} ->
            NewDigest = maps:put(ID, Count + 1),
            ok = broadcast(ID, Message, Peers),
            ok = schedule_resend(ID, Message),
            State#s{digest = NewDigest};
        error ->
            case set:is_element(ID, Dead) of
                true ->
                    State;
                false ->
                    NewDigest = maps:put(ID, 1),
                    ok = broadcast(Message, Peers),
                    ok = schedule_resend(ID, Message),
                    State#s{digest = NewDigest}
            end
    end.

broadcast(ID, Message, Peers) ->
    Forward = fun(P) -> relay(P, ID, Message),
    lists:foreach(Forward, Peers).

schedule_resend(ID, Message) ->
    _ = erlang:send_after(1000, self(), {redo_relay, ID, Message}),
    ok.
而现在我们需要社交方面的东西

do_greet(Peer, State = #s{peers = Peers}) ->
    case lists:member(Peer, Peers) of
        false -> State#s{peers = [Peer | Peers]};
        true  -> State
    end.

do_make_introduction(Peer, State = #s{peers = Peers}) ->
    ok = greet(Peer),
    do_greet(Peer, State).
那么,上面所有可怕的非特定类型的东西都做了什么

它避免了任何死锁的可能性。死锁在对等系统中如此致命的原因是,每当你有两个相同的进程(或参与者,或其他什么)同步通信时,你就创造了一个潜在死锁的教科书案例

任何时候
A
都有一个指向
B
的同步消息,
B
有一个指向
A
的同步消息,同时你现在有一个死锁。如果不产生潜在的死锁,就无法创建同步调用彼此的相同进程ems任何可能发生的事情最终都会发生,所以你迟早会遇到这种情况


八卦之所以是异步的,是因为:它是一种处理草率、不可靠、低效的网络拓扑的草率、不可靠、低效的方法。试图打电话而不是强制转换,不仅会破坏八卦式消息中继的目的,还会将您推入不可能的死锁区域,从而改变网络拓扑的性质f从异步到同步的协议。

Genser.call
的时间为5000毫秒。因此,可能发生的情况是,参与者的消息队列中充满了数百万条消息,当到达
call
时,调用参与者已超时

您可以使用

do_insert(Message, State = #s{peers = Peers, digest = Digest}) ->
    MessageID = zuuid:v1(),
    NewDigest = maps:put(MessageID, 1, Digest),
    ok = broadcast(Message, Peers),
    ok = schedule_resend(MessageID, Message),
    State#s{digest = NewDigest}.

do_relay(ID,
         Message,
         State = #s{peers = Peers, digest = Digest, dead = Dead}) ->
    case maps:find(ID, Digest) of
        {ok, Count} when Count >= 10 ->
            NewDigest = maps:remove(ID, Digest),
            NewDead = sets:add_element(ID, Dead),
            ok = broadcast(Message, Peers),
            State#s{digest = NewDigest, dead = NewDead};
        {ok, Count} ->
            NewDigest = maps:put(ID, Count + 1),
            ok = broadcast(ID, Message, Peers),
            ok = schedule_resend(ID, Message),
            State#s{digest = NewDigest};
        error ->
            case set:is_element(ID, Dead) of
                true ->
                    State;
                false ->
                    NewDigest = maps:put(ID, 1),
                    ok = broadcast(Message, Peers),
                    ok = schedule_resend(ID, Message),
                    State#s{digest = NewDigest}
            end
    end.

broadcast(ID, Message, Peers) ->
    Forward = fun(P) -> relay(P, ID, Message),
    lists:foreach(Forward, Peers).

schedule_resend(ID, Message) ->
    _ = erlang:send_after(1000, self(), {redo_relay, ID, Message}),
    ok.
do_greet(Peer, State = #s{peers = Peers}) ->
    case lists:member(Peer, Peers) of
        false -> State#s{peers = [Peer | Peers]};
        true  -> State
    end.

do_make_introduction(Peer, State = #s{peers = Peers}) ->
    ok = greet(Peer),
    do_greet(Peer, State).
try do
  c = RumourActor.get_message(recipient, [])
catch
  :exit, reason ->
    # handle timeout
 A.fun1():
   body of A before blocking call
   result = blockingcall()
   do things based on result
 A.send():
   body of A before blocking call
   nonblockingcall(A.receive) #A.receive is where B should send results
   do other things

 A.receive(result):
   do things based on result